0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.nbbuild;
0043:
0044: import java.io.BufferedReader;
0045: import java.io.File;
0046: import java.io.FileReader;
0047: import java.io.FileWriter;
0048: import java.io.IOException;
0049: import java.io.PrintWriter;
0050: import java.util.ArrayList;
0051: import java.util.Collections;
0052: import java.util.Enumeration;
0053: import java.util.HashMap;
0054: import java.util.HashSet;
0055: import java.util.List;
0056: import java.util.Map;
0057: import java.util.Set;
0058: import java.util.StringTokenizer;
0059: import java.util.TreeMap;
0060: import java.util.TreeSet;
0061: import java.util.jar.JarEntry;
0062: import java.util.jar.JarFile;
0063: import java.util.jar.Manifest;
0064: import java.util.regex.Pattern;
0065: import org.apache.tools.ant.BuildException;
0066: import org.apache.tools.ant.DirectoryScanner;
0067: import org.apache.tools.ant.Project;
0068: import org.apache.tools.ant.Task;
0069: import org.apache.tools.ant.types.EnumeratedAttribute;
0070: import org.apache.tools.ant.types.FileSet;
0071:
0072: /** This task implements the module dependencies verification proposal
0073: * that is described at
0074: * http://openide.netbeans.org/proposals/arch/clusters.html#verify-solution
0075: */
0076: public class ModuleDependencies extends Task {
0077: private List<Input> inputs = new ArrayList<Input>();
0078: private List<Output> outputs = new ArrayList<Output>();
0079: private Set<ModuleInfo> modules;
0080: private Pattern regexp;
0081:
0082: public ModuleDependencies() {
0083: }
0084:
0085: public void setGenerate(String regexpList) {
0086: regexp = Pattern.compile(regexpList);
0087: }
0088:
0089: public Input createInput() throws BuildException {
0090: Input input = new Input();
0091: inputs.add(input);
0092: return input;
0093: }
0094:
0095: public Output createOutput() throws BuildException {
0096: Output output = new Output();
0097: outputs.add(output);
0098: return output;
0099: }
0100:
0101: public @Override
0102: void execute() throws BuildException {
0103: if (outputs.size() == 0)
0104: throw new BuildException(
0105: "At least one <output> tag has to be specified");
0106:
0107: try {
0108: readModuleInfo();
0109:
0110: for (Output o : outputs) {
0111: if (o.type == null)
0112: throw new BuildException(
0113: "<output> needs attribute type");
0114: if (o.file == null)
0115: throw new BuildException(
0116: "<output> needs attribute file");
0117:
0118: getProject().log(
0119: "Generating " + o.type + " to " + o.file);
0120:
0121: if ("public-packages".equals(o.type.getValue())) {
0122: generatePublicPackages(o.file, true, false);
0123: continue;
0124: }
0125: if ("friend-packages".equals(o.type.getValue())) {
0126: generatePublicPackages(o.file, false, false);
0127: continue;
0128: }
0129: if ("shared-packages".equals(o.type.getValue())) {
0130: generateSharedPackages(o.file);
0131: continue;
0132: }
0133: if ("modules".equals(o.type.getValue())) {
0134: generateListOfModules(o.file);
0135: continue;
0136: }
0137: if ("dependencies".equals(o.type.getValue())) {
0138: generateDependencies(o.file, false);
0139: continue;
0140: }
0141: if ("implementation-dependencies".equals(o.type
0142: .getValue())) {
0143: generateDependencies(o.file, true);
0144: continue;
0145: }
0146: if ("group-dependencies".equals(o.type.getValue())) {
0147: generateGroupDependencies(o.file, false);
0148: continue;
0149: }
0150: if ("group-implementation-dependencies".equals(o.type
0151: .getValue())) {
0152: generateGroupDependencies(o.file, true);
0153: continue;
0154: }
0155: if ("group-friend-packages".equals(o.type.getValue())) {
0156: generatePublicPackages(o.file, false, true);
0157: continue;
0158: }
0159: if ("kits".equals(o.type.getValue())) {
0160: generateKits(o.file);
0161: continue;
0162: }
0163: if ("kit-dependencies".equals(o.type.getValue())) {
0164: generateKitDependencies(o.file);
0165: continue;
0166: }
0167: }
0168:
0169: } catch (IOException ex) {
0170: throw new BuildException(ex);
0171: }
0172: }
0173:
0174: private void readModuleInfo() throws IOException {
0175: modules = new TreeSet<ModuleInfo>();
0176:
0177: if (inputs.isEmpty()) {
0178: throw new BuildException(
0179: "At least one <input> tag is needed");
0180: }
0181: for (Input input : inputs) {
0182: if (input.jars == null)
0183: throw new BuildException(
0184: "<input> needs a subelement <jars>");
0185: if (input.name == null)
0186: throw new BuildException("<input> needs attribute name");
0187:
0188: Project p = getProject();
0189: DirectoryScanner scan = input.jars.getDirectoryScanner(p);
0190: for (String incl : scan.getIncludedFiles()) {
0191: File f = new File(scan.getBasedir(), incl);
0192: getProject()
0193: .log("Processing " + f, Project.MSG_VERBOSE);
0194: JarFile file = new JarFile(f);
0195:
0196: Manifest manifest = file.getManifest();
0197: if (manifest == null) {
0198: // process only manifest files
0199: continue;
0200: }
0201:
0202: String module = manifest.getMainAttributes().getValue(
0203: "OpenIDE-Module");
0204:
0205: if (module == null) {
0206: // skip this one
0207: continue;
0208: }
0209:
0210: ModuleInfo m;
0211: {
0212: String codebasename;
0213: int majorVersion;
0214: // base name
0215: int slash = module.indexOf('/');
0216: if (slash == -1) {
0217: codebasename = module;
0218: majorVersion = -1;
0219: } else {
0220: codebasename = module.substring(0, slash);
0221: majorVersion = Integer.valueOf(module
0222: .substring(slash + 1));
0223: }
0224: m = new ModuleInfo(input.name, f, codebasename);
0225: m.majorVersion = majorVersion;
0226: }
0227:
0228: String showInAutoUpdate = file.getManifest()
0229: .getMainAttributes().getValue(
0230: "AutoUpdate-Show-In-Client");
0231: if (showInAutoUpdate == null) {
0232: m.showInAutoupdate = true;
0233: } else {
0234: m.showInAutoupdate = Boolean
0235: .parseBoolean(showInAutoUpdate);
0236: }
0237:
0238: m.publicPackages = file.getManifest()
0239: .getMainAttributes().getValue(
0240: "OpenIDE-Module-Public-Packages");
0241:
0242: {
0243: m.specificationVersion = file
0244: .getManifest()
0245: .getMainAttributes()
0246: .getValue(
0247: "OpenIDE-Module-Specification-Version");
0248: }
0249:
0250: m.implementationVersion = file
0251: .getManifest()
0252: .getMainAttributes()
0253: .getValue(
0254: "OpenIDE-Module-Implementation-Version");
0255:
0256: TreeSet<Dependency> depends = new TreeSet<Dependency>();
0257: TreeSet<Dependency> provides = new TreeSet<Dependency>();
0258: addDependencies(depends, file.getManifest(),
0259: Dependency.REQUIRES, "OpenIDE-Module-Requires");
0260: addDependencies(provides, file.getManifest(),
0261: Dependency.PROVIDES, "OpenIDE-Module-Provides");
0262: {
0263: String ideDeps = file.getManifest()
0264: .getMainAttributes().getValue(
0265: "OpenIDE-Module-IDE-Dependencies"); // IDE/1 > 4.25
0266: if (ideDeps != null) {
0267: StringTokenizer tok = new StringTokenizer(
0268: ideDeps, "> ");
0269: if (tok.countTokens() != 2
0270: || !tok.nextToken().equals("IDE/1")) {
0271: throw new BuildException(
0272: "Wrong OpenIDE-Module-IDE-Dependencies: "
0273: + ideDeps);
0274: }
0275: }
0276: }
0277: addDependencies(depends, file.getManifest(),
0278: Dependency.REQUIRES,
0279: "OpenIDE-Module-Module-Dependencies");
0280: /* org.netbeans.api.java/1,org.netbeans.modules.queries/0,
0281: org.netbeans.modules.javacore/1,org.netbeans.jmi.javamodel/1 > 1.11,org.netbeans.api.mdr/1,
0282: org.netbeans.modules.mdr/1= 1.0.0,org.netbeans.modules.
0283: jmiutils/1 = 1.0.0,javax.jmi.reflect/1,
0284: org.openide.loaders,org.openide.src > 1.0
0285: */
0286: m.depends = depends;
0287: m.provides = provides;
0288: {
0289: String friends = file.getManifest()
0290: .getMainAttributes().getValue(
0291: "OpenIDE-Module-Friends");
0292: if (friends != null) {
0293: TreeSet<String> set = new TreeSet<String>();
0294: StringTokenizer tok = new StringTokenizer(
0295: friends, ", ");
0296: while (tok.hasMoreElements()) {
0297: set.add(tok.nextToken());
0298: }
0299: m.friends = set;
0300: }
0301: }
0302: String essential = file.getManifest()
0303: .getMainAttributes().getValue(
0304: "AutoUpdate-Essential-Module");
0305: m.isEssential = essential == null ? false : Boolean
0306: .parseBoolean(file.getManifest()
0307: .getMainAttributes().getValue(
0308: "AutoUpdate-Essential-Module"));
0309: m.isAutoload = determineParameter(f, "autoload");
0310: m.isEager = determineParameter(f, "eager");
0311: modules.add(m);
0312: }
0313: }
0314: }
0315:
0316: private boolean determineParameter(File moduleFile, String parameter)
0317: throws IOException {
0318: String name = moduleFile.getName();
0319: name = name.substring(0, name.length() - 3) + "xml";
0320: File configFile = new File(moduleFile.getParentFile()
0321: .getParentFile(), "config/Modules/" + name);
0322: log("config " + configFile, Project.MSG_DEBUG);
0323: if (!configFile.exists())
0324: return true; // probably a classpath module, treat like autoload
0325: final String fragment = "<param name=\"" + parameter
0326: + "\">true</param>";
0327: BufferedReader br = new BufferedReader(new FileReader(
0328: configFile));
0329: try {
0330: String line;
0331: while ((line = br.readLine()) != null) {
0332: if (line.indexOf(fragment) != -1) {
0333: log("autoload module: " + moduleFile,
0334: Project.MSG_DEBUG);
0335: return true;
0336: }
0337: }
0338: } finally {
0339: br.close();
0340: }
0341: return false;
0342: }
0343:
0344: private void generatePublicPackages(File output,
0345: boolean justPublic, boolean justInterCluster)
0346: throws BuildException, IOException {
0347: TreeSet<String> packages = new TreeSet<String>();
0348: TreeMap<ModuleInfo, TreeSet<String>> friendExports = new TreeMap<ModuleInfo, TreeSet<String>>();
0349:
0350: {
0351: for (ModuleInfo m : modules) {
0352: if (justPublic) {
0353: if (m.friends != null) {
0354: continue;
0355: }
0356: }
0357: if (regexp != null
0358: && !regexp.matcher(m.group).matches()) {
0359: continue;
0360: }
0361:
0362: String s = m.publicPackages;
0363: HashMap<String, Boolean> pkgs = null;
0364: if (s != null) {
0365: pkgs = new HashMap<String, Boolean>();
0366: StringTokenizer tok = new StringTokenizer(s, ",");
0367: while (tok.hasMoreElements()) {
0368: String p = tok.nextToken().trim();
0369: if (p.equals("-")) {
0370: continue;
0371: }
0372:
0373: if (p.endsWith(".*")) {
0374: pkgs.put(p.substring(0, p.length() - 2)
0375: .replace('.', '/'), Boolean.FALSE);
0376: continue;
0377: }
0378: if (p.endsWith(".**")) {
0379: pkgs.put(p.substring(0, p.length() - 3)
0380: .replace('.', '/'), Boolean.TRUE);
0381: continue;
0382: }
0383: throw new BuildException(
0384: "Unknown package format: " + p + " in "
0385: + m.file);
0386: }
0387: }
0388:
0389: if (justPublic) {
0390: iterateThruPackages(m.file, pkgs, packages);
0391: if (pkgs != null && packages.size() < pkgs.size()) {
0392: throw new BuildException(
0393: "Not enough packages found. The declared packages are: "
0394: + s + " but only " + packages
0395: + " were found in " + m.file);
0396: }
0397: } else {
0398: TreeSet<String> modulePkgs = new TreeSet<String>();
0399: iterateThruPackages(m.file, pkgs, modulePkgs);
0400: friendExports.put(m, modulePkgs);
0401: }
0402:
0403: }
0404: }
0405:
0406: PrintWriter w = new PrintWriter(new FileWriter(output));
0407: if (justPublic) {
0408: for (String out : packages) {
0409: w.println(out.replace('/', '.'));
0410: }
0411: } else {
0412: int maxFriends = Integer.MAX_VALUE;
0413: if (justInterCluster) {
0414: String maxFriendsString = this .getProject()
0415: .getProperty("deps.max.friends");
0416: if (maxFriendsString != null) {
0417: maxFriends = Integer.parseInt(maxFriendsString);
0418: }
0419: }
0420:
0421: for (Map.Entry<ModuleInfo, TreeSet<String>> entry : friendExports
0422: .entrySet()) {
0423: ModuleInfo info = entry.getKey();
0424: if (info.friends == null) {
0425: continue;
0426: }
0427: log("Friends for " + info.getName(false),
0428: Project.MSG_DEBUG);
0429: int cntFriends = 0;
0430: boolean printed = false;
0431: for (String n : info.friends) {
0432: ModuleInfo friend = findModuleInfo(n);
0433: if (justInterCluster && friend != null
0434: && friend.group.equals(info.group)) {
0435: continue;
0436: }
0437:
0438: if (!printed) {
0439: w.print("MODULE ");
0440: w.println(info.getName(false));
0441: printed = true;
0442: }
0443:
0444: if (friend != null) {
0445: w.print(" FRIEND ");
0446: w.println(friend.getName(false));
0447: } else {
0448: w.print(" EXTERNAL ");
0449: w.println(n);
0450: }
0451: cntFriends++;
0452: }
0453: if (cntFriends > maxFriends) {
0454: throw new BuildException(
0455: "Too many intercluster friends ("
0456: + cntFriends + ") for module "
0457: + info.getName(false));
0458: }
0459:
0460: if (cntFriends > 0) {
0461: for (String out : entry.getValue()) {
0462: w.print(" PACKAGE ");
0463: w.println(out.replace('/', '.'));
0464: }
0465: }
0466: }
0467: }
0468: w.close();
0469: }
0470:
0471: private void iterateThruPackages(File f, HashMap pkgs,
0472: TreeSet<String> packages) throws IOException {
0473: JarFile file = new JarFile(f);
0474: Enumeration en = file.entries();
0475: LOOP: while (en.hasMoreElements()) {
0476: JarEntry e = (JarEntry) en.nextElement();
0477: if (e.getName().endsWith(".class")) {
0478: int last = e.getName().lastIndexOf('/');
0479: if (last == -1) {
0480: // skip default pkg
0481: continue;
0482: }
0483: String p = e.getName().substring(0, last);
0484:
0485: if (pkgs == null) {
0486: packages.add(p);
0487: continue;
0488: }
0489:
0490: Boolean b = (Boolean) pkgs.get(p);
0491: if (b != null) {
0492: packages.add(p);
0493: continue;
0494: }
0495:
0496: String parent = p;
0497: while (parent.length() > 0) {
0498: int prev = parent.lastIndexOf('/');
0499: if (prev == -1) {
0500: parent = "";
0501: } else {
0502: parent = parent.substring(0, prev);
0503: }
0504:
0505: b = (Boolean) pkgs.get(parent);
0506: if (Boolean.TRUE.equals(b)) {
0507: packages.add(p);
0508: continue LOOP;
0509: }
0510: }
0511: }
0512: }
0513:
0514: java.util.jar.Manifest m = file.getManifest();
0515: if (m != null) {
0516: String value = m.getMainAttributes().getValue("Class-Path");
0517: if (value != null) {
0518: StringTokenizer tok = new StringTokenizer(value, " ");
0519: while (tok.hasMoreElements()) {
0520: File sub = new File(f.getParentFile(), tok
0521: .nextToken());
0522: if (sub.isFile()) {
0523: iterateThruPackages(sub, pkgs, packages);
0524: }
0525: }
0526: }
0527: }
0528:
0529: file.close();
0530: }
0531:
0532: private void generateListOfModules(File output)
0533: throws BuildException, IOException {
0534: PrintWriter w = new PrintWriter(new FileWriter(output));
0535: for (ModuleInfo m : modules) {
0536: if (regexp != null && !regexp.matcher(m.group).matches()) {
0537: continue;
0538: }
0539: w.print("MODULE ");
0540: w.print(m.getName(true));
0541: w.println();
0542: }
0543: w.close();
0544: }
0545:
0546: private void generateKits(File output) throws BuildException,
0547: IOException {
0548: PrintWriter w = new PrintWriter(new FileWriter(output));
0549: // calculate transitive closure of kits
0550: TreeMap<String, TreeSet<String>> allKitDeps = transitiveClosureOfKits();
0551: // calculate transitive closure of modules
0552: TreeMap<String, TreeSet<String>> allModuleDeps = transitiveClosureOfModules();
0553: // create a map of <module, kits that depend on it>
0554: TreeMap<ModuleInfo, Set<String>> dependingKits = new TreeMap<ModuleInfo, Set<String>>();
0555: for (ModuleInfo m : modules) {
0556: if (regexp != null && !regexp.matcher(m.group).matches()) {
0557: continue;
0558: }
0559: if (m.showInAutoupdate) {
0560: // this is a kit
0561: Set<String> dep = allModuleDeps.get(m.codebasename);
0562: for (String ds : dep) {
0563: if (regexp != null
0564: && !regexp.matcher(m.group).matches()) {
0565: continue;
0566: }
0567: //log ("ds " + dep);
0568: ModuleInfo theModuleOneIsDependingOn = findModuleInfo(ds);
0569: if (!theModuleOneIsDependingOn.showInAutoupdate
0570: && !theModuleOneIsDependingOn.isAutoload
0571: && !theModuleOneIsDependingOn.isEager
0572: && !theModuleOneIsDependingOn.isEssential) {
0573: // regular module, not a kit
0574: Set<String> kits = dependingKits
0575: .get(theModuleOneIsDependingOn);
0576: if (kits == null) {
0577: kits = new TreeSet<String>();
0578: dependingKits.put(
0579: theModuleOneIsDependingOn, kits);
0580: }
0581: kits.add(m.getName(false));
0582: // w.print(" REQUIRES " + theModuleOneIsDependingOn.getName());
0583: // w.println();
0584: }
0585: }
0586: }
0587: }
0588: // now check that there is one canonical kit that "contains" the module
0589: // at the same time create a map of <kit, set of <module>>
0590: TreeMap<String, TreeSet<String>> allKits = new TreeMap<String, TreeSet<String>>();
0591: for (ModuleInfo module : dependingKits.keySet()) {
0592: Set<String> kits = dependingKits.get(module);
0593: // candidate for the lowest kit
0594: String lowestKitCandidate = null;
0595: for (String kit : kits) {
0596: if (lowestKitCandidate == null) {
0597: lowestKitCandidate = kit;
0598: log(" initial lowest kit candidate for "
0599: + module.getName(false) + " : "
0600: + lowestKitCandidate, Project.MSG_DEBUG);
0601: } else {
0602: if (dependsOnTransitively(lowestKitCandidate, kit,
0603: allKitDeps)) {
0604: lowestKitCandidate = kit;
0605: log(" new lowest kit candidate for "
0606: + module.getName(false) + " : "
0607: + lowestKitCandidate, Project.MSG_DEBUG);
0608: }
0609: }
0610: }
0611: // check that all kits depend on the lowest kit candidate
0612: boolean passed = true;
0613: for (String kit : kits) {
0614: if (!kit.equals(lowestKitCandidate)
0615: && !dependsOnTransitively(kit,
0616: lowestKitCandidate, allKitDeps)) {
0617: log("lowest kit not found for "
0618: + module.getName(false) + " : "
0619: + lowestKitCandidate + ", " + kit
0620: + " do not have a dependency",
0621: Project.MSG_VERBOSE);
0622: passed = false;
0623: break;
0624: }
0625: }
0626: if (passed) {
0627: dependingKits.put(module, Collections
0628: .singleton(lowestKitCandidate));
0629: registerModuleInKit(module, lowestKitCandidate, allKits);
0630: } else {
0631: w
0632: .print("Warning: ambiguous module ownership - module ");
0633: w.print(module.getName(false));
0634: w.print(" is contained in kits ");
0635: w.println();
0636: for (String kit : kits) {
0637: registerModuleInKit(module, kit, allKits);
0638: w.print(" " + kit);
0639: w.println();
0640: }
0641: w.println("No dependency between ");
0642: for (String kit : kits) {
0643: if (!kit.equals(lowestKitCandidate)
0644: && !dependsOnTransitively(kit,
0645: lowestKitCandidate, allKitDeps)) {
0646: w.println(" " + lowestKitCandidate + ", "
0647: + kit);
0648: }
0649: }
0650: }
0651: }
0652: // now actually print out the kit contents
0653: for (String kit : allKits.keySet()) {
0654: w.print("KIT ");
0655: w.print(kit);
0656: w.println();
0657: for (String m : allKits.get(kit)) {
0658: w.print(" CONTAINS " + m);
0659: w.println();
0660: }
0661: }
0662:
0663: w.close();
0664: }
0665:
0666: private boolean dependsOnTransitively(String kit1, String kit2,
0667: TreeMap<String, TreeSet<String>> dependingKits) {
0668: TreeSet kits = dependingKits.get(kit1);
0669: if (kits == null) {
0670: return false;
0671: }
0672: return kits.contains(kit2);
0673: }
0674:
0675: private static void registerModuleInKit(ModuleInfo module,
0676: String kit, TreeMap<String, TreeSet<String>> allKits) {
0677: TreeSet<String> modules = allKits.get(kit);
0678: if (modules == null) {
0679: modules = new TreeSet<String>();
0680: allKits.put(kit, modules);
0681: }
0682: modules.add(module.getName(false));
0683: }
0684:
0685: private TreeMap<String, TreeSet<String>> transitiveClosureOfModules() {
0686: TreeMap<String, TreeSet<String>> moduleDepsAll = new TreeMap<String, TreeSet<String>>();
0687: // populate with modules first
0688: for (ModuleInfo m : modules) {
0689: TreeSet<String> deps = new TreeSet<String>();
0690: moduleDepsAll.put(m.codebasename, deps);
0691: for (Dependency d : m.depends) {
0692: if (!d.isSpecial()) {
0693: ModuleInfo theModuleOneIsDependingOn = findModuleInfo(
0694: d, m);
0695: deps.add(theModuleOneIsDependingOn.codebasename);
0696: }
0697: }
0698: }
0699: transitiveClosure(moduleDepsAll);
0700: return moduleDepsAll;
0701: }
0702:
0703: private TreeMap<String, TreeSet<String>> transitiveClosureOfKits() {
0704: TreeMap<String, TreeSet<String>> kitDepsAll = new TreeMap<String, TreeSet<String>>();
0705: // populate with kits first
0706: for (ModuleInfo m : modules) {
0707: if (m.showInAutoupdate) {
0708: TreeSet<String> deps = new TreeSet<String>();
0709: kitDepsAll.put(m.getName(false), deps);
0710: for (Dependency d : m.depends) {
0711: if (!d.isSpecial()) {
0712: ModuleInfo theModuleOneIsDependingOn = findModuleInfo(
0713: d, m);
0714: if (theModuleOneIsDependingOn.showInAutoupdate) {
0715: deps.add(theModuleOneIsDependingOn
0716: .getName(false));
0717: }
0718: }
0719: }
0720: }
0721: }
0722: transitiveClosure(kitDepsAll);
0723: return kitDepsAll;
0724: }
0725:
0726: /** Computes the transitive closure of the dependency map passed as a parameter.
0727: *
0728: * @param deps the dependency map, will contain the transitive closure when the method exits
0729: */
0730: private void transitiveClosure(
0731: TreeMap<String, TreeSet<String>> allDeps) {
0732: // add transitively all dependencies
0733: boolean needAnotherIteration = true;
0734: while (needAnotherIteration) {
0735: needAnotherIteration = false;
0736: for (String m : allDeps.keySet()) {
0737: TreeSet<String> deps = allDeps.get(m);
0738: for (String d : new TreeSet<String>(deps)) {
0739: for (String d2 : allDeps.get(d)) {
0740: if (!deps.contains(d2)) {
0741: log("transitive closure: need to add " + d2
0742: + " to " + m, Project.MSG_DEBUG);
0743: deps.add(d2);
0744: needAnotherIteration = true;
0745: }
0746: }
0747: }
0748:
0749: }
0750: }
0751: }
0752:
0753: private void generateKitDependencies(File output)
0754: throws BuildException, IOException {
0755: PrintWriter w = new PrintWriter(new FileWriter(output));
0756: for (ModuleInfo m : modules) {
0757: if (regexp != null && !regexp.matcher(m.group).matches()) {
0758: continue;
0759: }
0760: if (m.showInAutoupdate) {
0761: w.print("KIT ");
0762: w.print(m.getName(false));
0763: w.println();
0764: for (Dependency d : m.depends) {
0765: if (regexp != null
0766: && !regexp.matcher(m.group).matches()) {
0767: continue;
0768: }
0769: if (!d.isSpecial()) {
0770: ModuleInfo theModuleOneIsDependingOn = findModuleInfo(
0771: d, m);
0772: if (theModuleOneIsDependingOn.showInAutoupdate) {
0773: w.print(" REQUIRES "
0774: + theModuleOneIsDependingOn
0775: .getName(false));
0776: w.println();
0777: }
0778: }
0779: }
0780: }
0781: }
0782: w.close();
0783: }
0784:
0785: private void generateSharedPackages(File output)
0786: throws BuildException, IOException {
0787: TreeMap<String, List<ModuleInfo>> packages = new TreeMap<String, List<ModuleInfo>>();
0788:
0789: for (ModuleInfo m : modules) {
0790: HashSet<String> pkgs = new HashSet<String>();
0791: iterateSharedPackages(m.file, pkgs);
0792: for (String s : pkgs) {
0793: List<ModuleInfo> l = packages.get(s);
0794: if (l == null) {
0795: l = new ArrayList<ModuleInfo>();
0796: packages.put(s, l);
0797: }
0798: l.add(m);
0799: }
0800: }
0801:
0802: PrintWriter w = new PrintWriter(new FileWriter(output));
0803: for (Map.Entry<String, List<ModuleInfo>> entry : packages
0804: .entrySet()) {
0805: String out = entry.getKey();
0806: List<ModuleInfo> cnt = entry.getValue();
0807: if (cnt.size() > 1) {
0808: log("Package " + out + " is shared between:",
0809: Project.MSG_VERBOSE);
0810: boolean doPrint = regexp == null;
0811: for (ModuleInfo m : cnt) {
0812: log(" " + m.codebasename, Project.MSG_VERBOSE);
0813: if (regexp != null
0814: && regexp.matcher(m.group).matches()) {
0815: doPrint = true;
0816: }
0817: }
0818: if (doPrint) {
0819: w.println(out.replace('/', '.'));
0820: }
0821: }
0822: }
0823: w.close();
0824: }
0825:
0826: private void iterateSharedPackages(File f, Set<String> myPkgs)
0827: throws IOException {
0828: JarFile file = new JarFile(f);
0829: Enumeration<JarEntry> en = file.entries();
0830: LOOP: while (en.hasMoreElements()) {
0831: JarEntry e = en.nextElement();
0832: if (e.getName().endsWith("/")) {
0833: continue;
0834: }
0835: if (e.getName().startsWith("META-INF/")) {
0836: continue;
0837: }
0838:
0839: int last = e.getName().lastIndexOf('/');
0840: String pkg = last == -1 ? "" : e.getName().substring(0,
0841: last);
0842: myPkgs.add(pkg);
0843: log("Found package " + pkg + " in " + f, Project.MSG_DEBUG);
0844: }
0845:
0846: Manifest m = file.getManifest();
0847: if (m != null) {
0848: String value = m.getMainAttributes().getValue("Class-Path");
0849: if (value != null) {
0850: StringTokenizer tok = new StringTokenizer(value, " ");
0851: while (tok.hasMoreElements()) {
0852: File sub = new File(f.getParentFile(), tok
0853: .nextToken());
0854: if (sub.isFile()) {
0855: iterateSharedPackages(sub, myPkgs);
0856: }
0857: }
0858: }
0859: }
0860:
0861: file.close();
0862: }
0863:
0864: private void generateDependencies(File output,
0865: boolean implementationOnly) throws BuildException,
0866: IOException {
0867: PrintWriter w = new PrintWriter(new FileWriter(output));
0868: for (ModuleInfo m : modules) {
0869: boolean first = true;
0870: for (Dependency d : m.depends) {
0871: if (d.getName().startsWith(
0872: "org.openide.modules.ModuleFormat")) {
0873: continue; // just clutter
0874: }
0875: String print = " REQUIRES ";
0876: if (d.exact && d.compare != null) {
0877: // ok, impl deps
0878: } else {
0879: if (implementationOnly) {
0880: continue;
0881: }
0882: }
0883: if (regexp != null
0884: && !regexp.matcher(m.group).matches()) {
0885: continue;
0886: }
0887:
0888: if (first) {
0889: w.print("MODULE ");
0890: w.print(m.getName(false));
0891: w.println();
0892: first = false;
0893: }
0894: w.print(print);
0895: if (d.isSpecial()) {
0896: w.print(d.getName());
0897: } else {
0898: ModuleInfo theModuleOneIsDependingOn = findModuleInfo(
0899: d, m);
0900: w.print(theModuleOneIsDependingOn.getName(false));
0901: }
0902: w.println();
0903: }
0904: }
0905: w.close();
0906: }
0907:
0908: private void generateGroupDependencies(File output,
0909: boolean implementationOnly) throws BuildException,
0910: IOException {
0911: PrintWriter w = new PrintWriter(new FileWriter(output));
0912:
0913: Map<Dependency, Set<ModuleInfo>> referrers = new HashMap<Dependency, Set<ModuleInfo>>();
0914:
0915: TreeMap<String, Set<Dependency>> groups = new TreeMap<String, Set<Dependency>>();
0916: for (ModuleInfo m : modules) {
0917: if (regexp != null && !regexp.matcher(m.group).matches()) {
0918: continue;
0919: }
0920: Set<Dependency> l = groups.get(m.group);
0921: if (l == null) {
0922: l = new TreeSet<Dependency>();
0923: groups.put(m.group, l);
0924: }
0925: l.addAll(m.depends);
0926: for (Dependency d : m.depends) {
0927: Set<ModuleInfo> r = referrers.get(d);
0928: if (r == null) {
0929: r = new HashSet<ModuleInfo>();
0930: referrers.put(d, r);
0931: }
0932: r.add(m);
0933: }
0934: }
0935:
0936: for (Map.Entry<String, Set<Dependency>> e : groups.entrySet()) {
0937: String groupName = e.getKey();
0938: Set<Dependency> depends = e.getValue();
0939:
0940: boolean first = true;
0941: for (Dependency d : depends) {
0942: String print = " REQUIRES ";
0943: if (d.exact && d.compare != null) {
0944: // ok, impl deps
0945: } else {
0946: if (implementationOnly) {
0947: continue;
0948: }
0949: }
0950:
0951: // special dependencies are ignored
0952: if (d.isSpecial()) {
0953: continue;
0954: }
0955: // dependencies within one group are not important
0956: Set<ModuleInfo> r = referrers.get(d);
0957: ModuleInfo ref = findModuleInfo(d, r.size() == 1 ? r
0958: .iterator().next() : null);
0959: if (groupName.equals(ref.group)) {
0960: continue;
0961: }
0962:
0963: if (first) {
0964: w.print("GROUP ");
0965: w.print(groupName);
0966: w.println();
0967: first = false;
0968: }
0969: w.print(print);
0970: w.print(ref.getName(false));
0971: w.println();
0972: }
0973: }
0974: w.close();
0975: }
0976:
0977: /** For a given dependency finds the module that this dependency refers to.
0978: */
0979: private ModuleInfo findModuleInfo(Dependency dep,
0980: ModuleInfo referrer) throws BuildException {
0981: for (ModuleInfo info : modules) {
0982: if (dep.isDependingOn(info)) {
0983: return info;
0984: }
0985: }
0986:
0987: throw new BuildException(
0988: "Cannot find module that satisfies dependency: "
0989: + dep
0990: + (referrer != null ? " from: " + referrer : ""));
0991: }
0992:
0993: /** For a given codebasename finds module that we depend on
0994: */
0995: private ModuleInfo findModuleInfo(String cnb) throws BuildException {
0996: for (ModuleInfo info : modules) {
0997: if (info.codebasename.equals(cnb)) {
0998: return info;
0999: }
1000: }
1001:
1002: return null;
1003: }
1004:
1005: private static void addDependencies(TreeSet<Dependency> addTo,
1006: java.util.jar.Manifest man, int dependencyType,
1007: String attrName) throws BuildException {
1008: String value = man.getMainAttributes().getValue(attrName);
1009: if (value == null) {
1010: return;
1011: }
1012:
1013: StringTokenizer tok = new StringTokenizer(value, ",");
1014: while (tok.hasMoreElements()) {
1015: String nextDep = tok.nextToken();
1016: StringTokenizer dep = new StringTokenizer(nextDep, "=>",
1017: true);
1018: if (dep.countTokens() == 1) {
1019: addTo.add(new Dependency(dep.nextToken().trim(),
1020: dependencyType, false, null));
1021: continue;
1022: }
1023:
1024: if (dep.countTokens() == 3) {
1025: String name = dep.nextToken().trim();
1026: String equal = dep.nextToken().trim();
1027: String comp = dep.nextToken().trim();
1028: addTo.add(new Dependency(name, dependencyType, equal
1029: .equals("="), comp));
1030: continue;
1031: }
1032:
1033: throw new BuildException("Cannot parse dependency: "
1034: + value);
1035: }
1036: }
1037:
1038: public static final class Input extends Object {
1039: public FileSet jars;
1040: public String name;
1041:
1042: public FileSet createJars() {
1043: if (jars != null)
1044: throw new BuildException();
1045: jars = new FileSet();
1046: return jars;
1047: }
1048:
1049: public void setName(String name) {
1050: this .name = name;
1051: }
1052: }
1053:
1054: public static final class Output extends Object {
1055: public OutputType type;
1056: public File file;
1057:
1058: public void setType(OutputType type) {
1059: this .type = type;
1060: }
1061:
1062: public void setFile(File file) {
1063: this .file = file;
1064: }
1065: }
1066:
1067: public static final class OutputType extends EnumeratedAttribute {
1068: public String[] getValues() {
1069: return new String[] { "public-packages", "friend-packages",
1070: "shared-packages", "modules", "dependencies",
1071: "implementation-dependencies",
1072: "group-dependencies",
1073: "group-implementation-dependencies",
1074: "group-friend-packages", "external-libraries",
1075: "kits", "kit-dependencies", };
1076: }
1077: }
1078:
1079: private static final class ModuleInfo extends Object implements
1080: Comparable<ModuleInfo> {
1081: public final String group;
1082: public final File file;
1083: public final String codebasename;
1084: public String publicPackages;
1085: public Set<String> friends;
1086: public int majorVersion;
1087: public String specificationVersion;
1088: public String implementationVersion;
1089: public Set<Dependency> depends;
1090: public Set<Dependency> provides;
1091: public boolean showInAutoupdate;
1092: public boolean isEssential;
1093: public boolean isAutoload;
1094: public boolean isEager;
1095:
1096: public ModuleInfo(String g, File f, String a) {
1097: this .group = g;
1098: this .file = f;
1099: this .codebasename = a;
1100: }
1101:
1102: public int compareTo(ModuleInfo m) {
1103: return codebasename.compareTo(m.codebasename);
1104: }
1105:
1106: public @Override
1107: boolean equals(Object obj) {
1108: if (obj instanceof ModuleInfo) {
1109: return codebasename
1110: .equals(((ModuleInfo) obj).codebasename);
1111: }
1112: return false;
1113: }
1114:
1115: public @Override
1116: int hashCode() {
1117: return codebasename.hashCode();
1118: }
1119:
1120: public String getName(boolean includeMajorVersion) {
1121: if (!includeMajorVersion || majorVersion == -1) {
1122: return codebasename + " (" + group + ")";
1123: } else {
1124: return codebasename + "/" + majorVersion + " (" + group
1125: + ")";
1126: }
1127: }
1128:
1129: public @Override
1130: String toString() {
1131: return "ModuleInfo[" + getName(false) + "]";
1132: }
1133: } // end of ModuleInfo
1134:
1135: private static final class Dependency extends Object implements
1136: Comparable<Dependency> {
1137: public static final int PROVIDES = 1;
1138: public static final int REQUIRES = 2;
1139:
1140: public final String token;
1141: public final int majorVersionFrom;
1142: public final int majorVersionTo;
1143: public final int type;
1144: public final boolean exact;
1145: public final String compare;
1146:
1147: public Dependency(String token, int type, boolean exact,
1148: String compare) {
1149: // base name
1150: int slash = token.indexOf('/');
1151: if (slash == -1) {
1152: this .token = token;
1153: this .majorVersionFrom = -1;
1154: this .majorVersionTo = -1;
1155: } else {
1156: this .token = token.substring(0, slash);
1157:
1158: String major = token.substring(slash + 1);
1159: int range = major.indexOf('-');
1160: if (range == -1) {
1161: this .majorVersionFrom = Integer.valueOf(major);
1162: this .majorVersionTo = majorVersionFrom;
1163: } else {
1164: this .majorVersionFrom = Integer.valueOf(major
1165: .substring(0, range));
1166: this .majorVersionTo = Integer.valueOf(major
1167: .substring(range + 1));
1168: }
1169: }
1170: this .type = type;
1171: this .exact = exact;
1172: this .compare = compare;
1173: }
1174:
1175: public int compareTo(Dependency m) {
1176: return token.compareTo(m.token);
1177: }
1178:
1179: public @Override
1180: boolean equals(Object obj) {
1181: if (obj instanceof Dependency) {
1182: return token.equals(((Dependency) obj).token);
1183: }
1184: return false;
1185: }
1186:
1187: public @Override
1188: int hashCode() {
1189: return token.hashCode();
1190: }
1191:
1192: /** These dependencies do not represent deps on real modules or
1193: * tokens provided by real modules.
1194: */
1195: public boolean isSpecial() {
1196: return token.startsWith("org.openide.modules.os")
1197: || token
1198: .startsWith("org.openide.modules.ModuleFormat");
1199: }
1200:
1201: public boolean isDependingOn(ModuleInfo info) {
1202: if (info.codebasename.equals(token)) {
1203: return (majorVersionFrom == -1 || majorVersionFrom <= info.majorVersion)
1204: && (majorVersionTo == -1 || info.majorVersion <= majorVersionTo);
1205: }
1206:
1207: for (Dependency d : info.provides) {
1208: if (d.equals(this )) {
1209: return true;
1210: }
1211: }
1212:
1213: return false;
1214: }
1215:
1216: public String getName() {
1217: return token;
1218: }
1219:
1220: public @Override
1221: String toString() {
1222: String t;
1223: switch (type) {
1224: case REQUIRES:
1225: t = "requires ";
1226: break;
1227: case PROVIDES:
1228: t = "provides ";
1229: break;
1230: default:
1231: throw new IllegalStateException("Unknown type: " + type);
1232: }
1233:
1234: return "Dependency[" + t + getName() + "]";
1235: }
1236:
1237: } // end of Dependency
1238: }
|