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-2007 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.BufferedInputStream;
0045: import java.io.File;
0046: import java.io.FileInputStream;
0047: import java.io.FileOutputStream;
0048: import java.io.IOException;
0049: import java.io.InputStream;
0050: import java.io.ObjectInput;
0051: import java.io.ObjectInputStream;
0052: import java.io.ObjectOutput;
0053: import java.io.ObjectOutputStream;
0054: import java.io.OutputStream;
0055: import java.io.Serializable;
0056: import java.util.ArrayList;
0057: import java.util.Arrays;
0058: import java.util.HashMap;
0059: import java.util.HashSet;
0060: import java.util.Hashtable;
0061: import java.util.Iterator;
0062: import java.util.List;
0063: import java.util.Map;
0064: import java.util.Set;
0065: import java.util.StringTokenizer;
0066: import java.util.TreeSet;
0067: import java.util.jar.Attributes;
0068: import java.util.jar.JarFile;
0069: import org.apache.tools.ant.BuildListener;
0070: import org.apache.tools.ant.Project;
0071: import org.apache.tools.ant.taskdefs.Property;
0072: import org.apache.tools.ant.types.Path;
0073: import org.apache.tools.ant.util.FileUtils;
0074: import org.w3c.dom.Document;
0075: import org.w3c.dom.Element;
0076: import org.xml.sax.InputSource;
0077:
0078: /**
0079: * Scans for known modules.
0080: * Precise algorithm summarized in issue #42681 and issue #58966.
0081: * @author Jesse Glick
0082: */
0083: final class ModuleListParser {
0084:
0085: private static Map<File, Map<String, Entry>> SOURCE_SCAN_CACHE = new HashMap<File, Map<String, Entry>>();
0086: private static Map<File, Map<String, Entry>> SUITE_SCAN_CACHE = new HashMap<File, Map<String, Entry>>();
0087: private static Map<File, Entry> STANDALONE_SCAN_CACHE = new HashMap<File, Entry>();
0088: private static Map<File, Map<String, Entry>> BINARY_SCAN_CACHE = new HashMap<File, Map<String, Entry>>();
0089:
0090: /** Clear caches. Cf. #71130. */
0091: public static void resetCaches() {
0092: SOURCE_SCAN_CACHE.clear();
0093: SUITE_SCAN_CACHE.clear();
0094: STANDALONE_SCAN_CACHE.clear();
0095: BINARY_SCAN_CACHE.clear();
0096: }
0097:
0098: /** Synch with org.netbeans.modules.apisupport.project.universe.ModuleList.FOREST: */
0099: private static final String[] FOREST = {
0100: /*root*/null, "contrib",
0101: // do not scan in misc; any real modules would have been put in contrib
0102: // Will there be other subtrees in the future (not using suites)?
0103: };
0104:
0105: /**
0106: * Find all NBM projects in a root, possibly from cache.
0107: */
0108: private static Map<String, Entry> scanNetBeansOrgSources(File root,
0109: Hashtable<String, String> properties, Project project)
0110: throws IOException {
0111: Map<String, Entry> entries = SOURCE_SCAN_CACHE.get(root);
0112: if (entries == null) {
0113: // Similar to #62221: if just invoked from a module in standard clusters, only scan those clusters (faster):
0114: Set<String> standardModules = new HashSet<String>();
0115: boolean doFastScan = false;
0116: String basedir = properties.get("basedir");
0117: if (basedir != null) {
0118: File basedirF = new File(basedir);
0119: String clusterList = properties.get("nb.clusters.list");
0120: if (clusterList == null) {
0121: String config = properties.get("cluster.config");
0122: if (config != null) {
0123: clusterList = properties.get("clusters.config."
0124: + config + ".list");
0125: }
0126: }
0127: if (clusterList != null) {
0128: StringTokenizer tok = new StringTokenizer(
0129: clusterList, ", ");
0130: while (tok.hasMoreTokens()) {
0131: String clusterName = tok.nextToken();
0132: String moduleList = properties.get(clusterName);
0133: if (moduleList != null) {
0134: StringTokenizer tok2 = new StringTokenizer(
0135: moduleList, ", ");
0136: while (tok2.hasMoreTokens()) {
0137: String module = tok2.nextToken();
0138: standardModules.add(module);
0139: doFastScan |= new File(root, module
0140: .replace('/',
0141: File.separatorChar))
0142: .equals(basedirF);
0143: }
0144: }
0145: }
0146: }
0147: }
0148: File scanCache = new File(root, "nbbuild"
0149: + File.separatorChar + "nbproject"
0150: + File.separatorChar + "private"
0151: + File.separatorChar + "scan-cache-"
0152: + (doFastScan ? "standard" : "full") + ".ser");
0153: if (scanCache.isFile()) {
0154: if (project != null) {
0155: project
0156: .log("Loading module list from "
0157: + scanCache);
0158: }
0159: try {
0160: InputStream is = new FileInputStream(scanCache);
0161: try {
0162: ObjectInput oi = new ObjectInputStream(
0163: new BufferedInputStream(is));
0164: @SuppressWarnings("unchecked")
0165: Map<File, Long[]> timestampsAndSizes = (Map) oi
0166: .readObject();
0167: boolean matches = true;
0168: for (Map.Entry<File, Long[]> entry : timestampsAndSizes
0169: .entrySet()) {
0170: File f = entry.getKey();
0171: if (f.lastModified() != entry.getValue()[0]
0172: || f.length() != entry.getValue()[1]) {
0173: if (project != null) {
0174: project
0175: .log("Cache ignored due to modifications in "
0176: + f);
0177: }
0178: matches = false;
0179: break;
0180: }
0181: }
0182: if (doFastScan) {
0183: @SuppressWarnings("unchecked")
0184: Set<String> storedStandardModules = (Set) oi
0185: .readObject();
0186: if (!standardModules
0187: .equals(storedStandardModules)) {
0188: Set<String> added = new TreeSet<String>(
0189: standardModules);
0190: added.removeAll(storedStandardModules);
0191: Set<String> removed = new TreeSet<String>(
0192: storedStandardModules);
0193: removed.removeAll(standardModules);
0194: project
0195: .log("Cache ignored due to changes in modules among standard clusters: + "
0196: + added
0197: + " - "
0198: + removed);
0199: matches = false;
0200: }
0201: }
0202: File myProjectXml = project
0203: .resolveFile("nbproject/project.xml");
0204: if (myProjectXml.isFile()
0205: && !timestampsAndSizes
0206: .containsKey(myProjectXml)) {
0207: project
0208: .log("Cache ignored since it has no mention of "
0209: + myProjectXml);
0210: matches = false; // #118098
0211: }
0212: if (matches) {
0213: @SuppressWarnings("unchecked")
0214: Map<String, Entry> _entries = (Map) oi
0215: .readObject();
0216: entries = _entries;
0217: if (project != null) {
0218: project.log("Loaded modules: "
0219: + entries.keySet(),
0220: Project.MSG_VERBOSE);
0221: }
0222: }
0223: } finally {
0224: is.close();
0225: }
0226: } catch (Exception x) {
0227: if (project != null) {
0228: project.log("Error loading " + scanCache + ": "
0229: + x, Project.MSG_WARN);
0230: }
0231: }
0232: }
0233: if (entries == null) {
0234: entries = new HashMap<String, Entry>();
0235: Map<File, Long[]> timestampsAndSizes = new HashMap<File, Long[]>();
0236: registerTimestampAndSize(new File(root, "nbbuild"
0237: + File.separatorChar + "cluster.properties"),
0238: timestampsAndSizes);
0239: registerTimestampAndSize(new File(root, "nbbuild"
0240: + File.separatorChar + "build.properties"),
0241: timestampsAndSizes);
0242: registerTimestampAndSize(
0243: new File(root, "nbbuild" + File.separatorChar
0244: + "user.build.properties"),
0245: timestampsAndSizes);
0246: if (doFastScan) {
0247: if (project != null) {
0248: project.log("Scanning for modules in " + root
0249: + " among standard clusters");
0250: }
0251: for (String module : standardModules) {
0252: scanPossibleProject(new File(root, module
0253: .replace('/', File.separatorChar)),
0254: entries, properties, module,
0255: ParseProjectXml.TYPE_NB_ORG, project,
0256: timestampsAndSizes);
0257: }
0258: } else {
0259: // Might be an extra module (e.g. something in contrib); need to scan everything.
0260: if (project != null) {
0261: project.log("Scanning for modules in " + root);
0262: project.log("Quick scan mode disabled since "
0263: + basedir
0264: + " not among standard modules of "
0265: + root + " which are "
0266: + standardModules, Project.MSG_VERBOSE);
0267: }
0268: for (String tree : FOREST) {
0269: File dir = tree == null ? root : new File(root,
0270: tree);
0271: File[] kids = dir.listFiles();
0272: if (kids == null) {
0273: continue;
0274: }
0275: for (File kid : kids) {
0276: if (!kid.isDirectory()) {
0277: continue;
0278: }
0279: String name = kid.getName();
0280: String path = tree == null ? name : tree
0281: + "/" + name;
0282: scanPossibleProject(kid, entries,
0283: properties, path,
0284: ParseProjectXml.TYPE_NB_ORG,
0285: project, timestampsAndSizes);
0286: }
0287:
0288: }
0289: }
0290: if (project != null) {
0291: project.log("Found modules: " + entries.keySet(),
0292: Project.MSG_VERBOSE);
0293: project.log("Cache depends on files: "
0294: + timestampsAndSizes.keySet(),
0295: Project.MSG_DEBUG);
0296: }
0297: scanCache.getParentFile().mkdirs();
0298: OutputStream os = new FileOutputStream(scanCache);
0299: try {
0300: ObjectOutput oo = new ObjectOutputStream(os);
0301: oo.writeObject(timestampsAndSizes);
0302: if (doFastScan) {
0303: oo.writeObject(standardModules);
0304: }
0305: oo.writeObject(entries);
0306: oo.flush();
0307: } finally {
0308: os.close();
0309: }
0310: }
0311: SOURCE_SCAN_CACHE.put(root, entries);
0312: }
0313: return entries;
0314: }
0315:
0316: private static void registerTimestampAndSize(File f,
0317: Map<File, Long[]> timestampsAndSizes) {
0318: if (timestampsAndSizes != null) {
0319: timestampsAndSizes.put(f, new Long[] { f.lastModified(),
0320: f.length() });
0321: }
0322: }
0323:
0324: /**
0325: * Check a single dir to see if it is an NBM project, and if so, register it.
0326: */
0327: private static boolean scanPossibleProject(File dir,
0328: Map<String, Entry> entries,
0329: Hashtable<String, String> properties, String path,
0330: int moduleType, Project project,
0331: Map<File, Long[]> timestampsAndSizes) throws IOException {
0332: File nbproject = new File(dir, "nbproject");
0333: File projectxml = new File(nbproject, "project.xml");
0334: if (!projectxml.isFile()) {
0335: return false;
0336: }
0337: registerTimestampAndSize(projectxml, timestampsAndSizes);
0338: Document doc;
0339: try {
0340: doc = XMLUtil.parse(new InputSource(projectxml.toURI()
0341: .toString()), false, true, /*XXX*/null, null);
0342: } catch (Exception e) { // SAXException, IOException (#60295: e.g. encoding problem in XML)
0343: // Include \n so that following line can be hyperlinked
0344: throw (IOException) new IOException(
0345: "Error parsing project file\n" + projectxml + ": "
0346: + e.getMessage()).initCause(e);
0347: }
0348: Element typeEl = XMLUtil.findElement(doc.getDocumentElement(),
0349: "type", ParseProjectXml.PROJECT_NS);
0350: if (!XMLUtil.findText(typeEl).equals(
0351: "org.netbeans.modules.apisupport.project")) {
0352: return false;
0353: }
0354: Element configEl = XMLUtil.findElement(
0355: doc.getDocumentElement(), "configuration",
0356: ParseProjectXml.PROJECT_NS);
0357: Element dataEl = ParseProjectXml.findNBMElement(configEl,
0358: "data");
0359: if (dataEl == null) {
0360: if (project != null) {
0361: project
0362: .log(
0363: projectxml.toString()
0364: + ": warning: module claims to be a NBM project but is missing <data xmlns=\""
0365: + ParseProjectXml.NBM_NS3
0366: + "\">; maybe an old NB 4.[01] project?",
0367: Project.MSG_WARN);
0368: }
0369: return false;
0370: }
0371: Element cnbEl = ParseProjectXml.findNBMElement(dataEl,
0372: "code-name-base");
0373: String cnb = XMLUtil.findText(cnbEl);
0374: if (moduleType == ParseProjectXml.TYPE_NB_ORG
0375: && project != null) {
0376: String expectedDirName = abbreviate(cnb);
0377: String actualDirName = dir.getName();
0378: if (!actualDirName.equals(expectedDirName)) {
0379: throw new IOException(
0380: "Expected module to be in dir named "
0381: + expectedDirName
0382: + " but was actually found in dir named "
0383: + actualDirName);
0384: }
0385: }
0386: // Clumsy but the best way I know of to evaluate properties.
0387: Project fakeproj = new Project();
0388: if (project != null) {
0389: // Try to debug any problems in the following definitions (cf. #59849).
0390: Iterator it = project.getBuildListeners().iterator();
0391: while (it.hasNext()) {
0392: fakeproj.addBuildListener((BuildListener) it.next());
0393: }
0394: }
0395: fakeproj.setBaseDir(dir); // in case ${basedir} is used somewhere
0396: Property faketask = new Property();
0397: faketask.setProject(fakeproj);
0398: switch (moduleType) {
0399: case ParseProjectXml.TYPE_NB_ORG:
0400: // do nothing here
0401: break;
0402: case ParseProjectXml.TYPE_SUITE:
0403: faketask.setFile(new File(nbproject,
0404: "private/suite-private.properties"));
0405: faketask.execute();
0406: faketask.setFile(new File(nbproject, "suite.properties"));
0407: faketask.execute();
0408: faketask
0409: .setFile(new File(
0410: fakeproj
0411: .replaceProperties("${suite.dir}/nbproject/private/platform-private.properties")));
0412: faketask.execute();
0413: faketask
0414: .setFile(new File(
0415: fakeproj
0416: .replaceProperties("${suite.dir}/nbproject/platform.properties")));
0417: faketask.execute();
0418: break;
0419: case ParseProjectXml.TYPE_STANDALONE:
0420: faketask.setFile(new File(nbproject,
0421: "private/platform-private.properties"));
0422: faketask.execute();
0423: faketask
0424: .setFile(new File(nbproject, "platform.properties"));
0425: faketask.execute();
0426: break;
0427: default:
0428: assert false : moduleType;
0429: }
0430: File privateProperties = new File(nbproject,
0431: "private/private.properties".replace('/',
0432: File.separatorChar));
0433: registerTimestampAndSize(privateProperties, timestampsAndSizes);
0434: faketask.setFile(privateProperties);
0435: faketask.execute();
0436: File projectProperties = new File(nbproject,
0437: "project.properties");
0438: registerTimestampAndSize(projectProperties, timestampsAndSizes);
0439: faketask.setFile(projectProperties);
0440: faketask.execute();
0441: faketask.setFile(null);
0442: faketask.setName("module.jar.dir");
0443: faketask.setValue("modules");
0444: faketask.execute();
0445: assert fakeproj.getProperty("module.jar.dir") != null : fakeproj
0446: .getProperties();
0447: faketask.setName("module.jar.basename");
0448: faketask.setValue(cnb.replace('.', '-') + ".jar");
0449: faketask.execute();
0450: faketask.setName("module.jar");
0451: faketask
0452: .setValue(fakeproj
0453: .replaceProperties("${module.jar.dir}/${module.jar.basename}"));
0454: faketask.execute();
0455: switch (moduleType) {
0456: case ParseProjectXml.TYPE_NB_ORG:
0457: assert path != null;
0458: // Find the associated cluster.
0459: for (Map.Entry<String, String> entry : properties
0460: .entrySet()) {
0461: String val = entry.getValue();
0462: String[] modules = val.split(", *");
0463: if (Arrays.asList(modules).contains(path)) {
0464: String key = entry.getKey();
0465: String clusterDir = properties.get(key + ".dir");
0466: if (clusterDir != null) {
0467: faketask.setName("cluster.dir");
0468: faketask.setValue(clusterDir);
0469: faketask.execute();
0470: break;
0471: }
0472: }
0473: }
0474: faketask.setName("cluster.dir");
0475: faketask.setValue("extra"); // fallback
0476: faketask.execute();
0477: faketask.setName("netbeans.dest.dir");
0478: faketask.setValue(properties.get("netbeans.dest.dir"));
0479: faketask.execute();
0480: faketask.setName("cluster");
0481: faketask
0482: .setValue(fakeproj
0483: .replaceProperties("${netbeans.dest.dir}/${cluster.dir}"));
0484: faketask.execute();
0485: break;
0486: case ParseProjectXml.TYPE_SUITE:
0487: assert path == null;
0488: faketask.setName("suite.dir");
0489: faketask.setValue(properties.get("suite.dir"));
0490: faketask.execute();
0491: faketask.setName("cluster");
0492: faketask.setValue(fakeproj
0493: .replaceProperties("${suite.dir}/build/cluster"));
0494: faketask.execute();
0495: break;
0496: case ParseProjectXml.TYPE_STANDALONE:
0497: assert path == null;
0498: faketask.setName("cluster");
0499: faketask.setValue(fakeproj
0500: .replaceProperties("${basedir}/build/cluster"));
0501: faketask.execute();
0502: break;
0503: default:
0504: assert false : moduleType;
0505: }
0506: File jar = fakeproj.resolveFile(fakeproj
0507: .replaceProperties("${cluster}/${module.jar}"));
0508: List<File> exts = new ArrayList<File>();
0509: for (Element ext : XMLUtil.findSubElements(dataEl)) {
0510: if (!ext.getLocalName().equals("class-path-extension")) {
0511: continue;
0512: }
0513: Element binaryOrigin = ParseProjectXml.findNBMElement(ext,
0514: "binary-origin");
0515: File origBin = null;
0516: if (binaryOrigin != null) {
0517: String reltext = XMLUtil.findText(binaryOrigin);
0518: String nball = properties.get("nb_all");
0519: if (nball != null) {
0520: faketask.setName("nb_all");
0521: faketask.setValue(nball);
0522: faketask.execute();
0523: }
0524: fakeproj.setBaseDir(dir);
0525: origBin = fakeproj.resolveFile(fakeproj
0526: .replaceProperties(reltext));
0527: }
0528:
0529: File resultBin = null;
0530: if (origBin == null || !origBin.exists()) {
0531: Element runtimeRelativePath = ParseProjectXml
0532: .findNBMElement(ext, "runtime-relative-path");
0533: if (runtimeRelativePath == null) {
0534: throw new IOException(
0535: "Have malformed <class-path-extension> in "
0536: + projectxml);
0537: }
0538: String reltext = XMLUtil.findText(runtimeRelativePath);
0539: // No need to evaluate property refs in it - it is *not* substitutable-text in the schema.
0540: resultBin = new File(jar.getParentFile(), reltext
0541: .replace('/', File.separatorChar));
0542: }
0543:
0544: if (origBin != null) {
0545: exts.add(origBin);
0546: }
0547:
0548: if (resultBin != null) {
0549: exts.add(resultBin);
0550: }
0551: }
0552: List<String> prereqs = new ArrayList<String>();
0553: List<String> rundeps = new ArrayList<String>();
0554: Element depsEl = ParseProjectXml.findNBMElement(dataEl,
0555: "module-dependencies");
0556: if (depsEl == null) {
0557: throw new IOException("Malformed project file "
0558: + projectxml);
0559: }
0560: Element testDepsEl = ParseProjectXml.findNBMElement(dataEl,
0561: "test-dependencies");
0562: //compileDeps = Collections.emptyList();
0563: String compileTestDeps[] = null;
0564: if (testDepsEl != null) {
0565: for (Element depssEl : XMLUtil.findSubElements(testDepsEl)) {
0566: String testtype = ParseProjectXml.findTextOrNull(
0567: depssEl, "name");
0568:
0569: if (testtype == null || testtype.equals("unit")) {
0570: List<String> compileDepsList = new ArrayList<String>();
0571: for (Element dep : XMLUtil.findSubElements(depssEl)) {
0572: if (dep.getTagName().equals("test-dependency")) {
0573: if (ParseProjectXml.findNBMElement(dep,
0574: "test") != null) {
0575: compileDepsList.add(ParseProjectXml
0576: .findTextOrNull(dep,
0577: "code-name-base"));
0578: }
0579: }
0580: }
0581: compileTestDeps = new String[compileDepsList.size()];
0582: compileDepsList.toArray(compileTestDeps);
0583: }
0584:
0585: }
0586: }
0587: for (Element dep : XMLUtil.findSubElements(depsEl)) {
0588: Element cnbEl2 = ParseProjectXml.findNBMElement(dep,
0589: "code-name-base");
0590: if (cnbEl2 == null) {
0591: throw new IOException("Malformed project file "
0592: + projectxml);
0593: }
0594: String cnb2 = XMLUtil.findText(cnbEl2);
0595: rundeps.add(cnb2);
0596: if (ParseProjectXml.findNBMElement(dep,
0597: "build-prerequisite") == null) {
0598: continue;
0599: }
0600: prereqs.add(cnb2);
0601: }
0602: String cluster = fakeproj.getProperty("cluster.dir"); // may be null
0603: Entry entry = new Entry(cnb, jar, exts.toArray(new File[exts
0604: .size()]), dir, path, prereqs
0605: .toArray(new String[prereqs.size()]), cluster, rundeps
0606: .toArray(new String[rundeps.size()]), compileTestDeps);
0607: if (entries.containsKey(cnb)) {
0608: throw new IOException("Duplicated module " + cnb
0609: + ": found in " + entries.get(cnb) + " and "
0610: + entry);
0611: } else {
0612: entries.put(cnb, entry);
0613: }
0614: return true;
0615: }
0616:
0617: static String abbreviate(String cnb) {
0618: return cnb.replaceFirst("^org\\.netbeans\\.modules\\.", ""). // NOI18N
0619: replaceFirst(
0620: "^org\\.netbeans\\.(libs|lib|api|spi|core)\\.",
0621: "$1."). // NOI18N
0622: replaceFirst("^org\\.netbeans\\.", "o.n."). // NOI18N
0623: replaceFirst("^org\\.openide\\.", "openide."). // NOI18N
0624: replaceFirst("^org\\.", "o."). // NOI18N
0625: replaceFirst("^com\\.sun\\.", "c.s."). // NOI18N
0626: replaceFirst("^com\\.", "c."); // NOI18N
0627: }
0628:
0629: /**
0630: * Find all modules in a binary build, possibly from cache.
0631: */
0632: private static Map<String, Entry> scanBinaries(
0633: Hashtable<String, String> properties, Project project)
0634: throws IOException {
0635: String buildS = properties.get("netbeans.dest.dir");
0636: File basedir = new File(properties.get("basedir"));
0637: if (buildS == null) {
0638: throw new IOException(
0639: "No definition of netbeans.dest.dir in " + basedir);
0640: }
0641: // Resolve against basedir, and normalize ../ sequences and so on in case they are used.
0642: // Neither operation is likely to be needed, but just in case.
0643: File build = FileUtils.getFileUtils().normalize(
0644: FileUtils.getFileUtils().resolveFile(basedir, buildS)
0645: .getAbsolutePath());
0646: if (!build.isDirectory()) {
0647: throw new IOException("No such netbeans.dest.dir: " + build);
0648: }
0649: Map<String, Entry> entries = BINARY_SCAN_CACHE.get(build);
0650: if (entries == null) {
0651: if (project != null) {
0652: project.log("Scanning for modules in " + build);
0653: }
0654: entries = new HashMap<String, Entry>();
0655: doScanBinaries(build, entries);
0656: if (project != null) {
0657: project.log("Found modules: " + entries.keySet(),
0658: Project.MSG_VERBOSE);
0659: }
0660: BINARY_SCAN_CACHE.put(build, entries);
0661: }
0662: return entries;
0663: }
0664:
0665: private static final String[] MODULE_DIRS = { "modules",
0666: "modules/eager", "modules/autoload", "lib", "core", };
0667:
0668: /**
0669: * Look for all possible modules in a NB build.
0670: * Checks modules/{,autoload/,eager/}*.jar as well as well-known core/*.jar and lib/boot.jar in each cluster.
0671: * XXX would be slightly more precise to check config/Modules/*.xml rather than scan for module JARs.
0672: */
0673: private static void doScanBinaries(File build,
0674: Map<String, Entry> entries) throws IOException {
0675: File[] clusters = build.listFiles();
0676: if (clusters == null) {
0677: throw new IOException("Cannot examine dir " + build);
0678: }
0679: for (int i = 0; i < clusters.length; i++) {
0680: for (int j = 0; j < MODULE_DIRS.length; j++) {
0681: File dir = new File(clusters[i], MODULE_DIRS[j]
0682: .replace('/', File.separatorChar));
0683: if (!dir.isDirectory()) {
0684: continue;
0685: }
0686: File[] jars = dir.listFiles();
0687: if (jars == null) {
0688: throw new IOException("Cannot examine dir " + dir);
0689: }
0690: for (int k = 0; k < jars.length; k++) {
0691: File m = jars[k];
0692: if (!m.getName().endsWith(".jar")) {
0693: continue;
0694: }
0695: JarFile jf = new JarFile(m);
0696: try {
0697: Attributes attr = jf.getManifest()
0698: .getMainAttributes();
0699: String codename = attr
0700: .getValue("OpenIDE-Module");
0701: if (codename == null) {
0702: continue;
0703: }
0704: String codenamebase;
0705: int slash = codename.lastIndexOf('/');
0706: if (slash == -1) {
0707: codenamebase = codename;
0708: } else {
0709: codenamebase = codename.substring(0, slash);
0710: }
0711:
0712: String cp = attr.getValue("Class-Path");
0713: File[] exts;
0714: if (cp == null) {
0715: exts = new File[0];
0716: } else {
0717: String[] pieces = cp.split(" +");
0718: exts = new File[pieces.length];
0719: for (int l = 0; l < pieces.length; l++) {
0720: exts[l] = new File(dir, pieces[l]
0721: .replace('/',
0722: File.separatorChar));
0723: }
0724: }
0725: String moduleDependencies = attr
0726: .getValue("OpenIDE-Module-Module-Dependencies");
0727:
0728: Entry entry = new Entry(
0729: codenamebase,
0730: m,
0731: exts,
0732: dir,
0733: null,
0734: null,
0735: clusters[i].getName(),
0736: parseRuntimeDependencies(moduleDependencies),
0737: null);
0738: if (entries.containsKey(codenamebase)) {
0739: throw new IOException("Duplicated module "
0740: + codenamebase + ": found in "
0741: + entries.get(codenamebase)
0742: + " and " + entry);
0743: } else {
0744: entries.put(codenamebase, entry);
0745: }
0746: } finally {
0747: jf.close();
0748: }
0749: }
0750: }
0751: }
0752: }
0753:
0754: private static Map<String, Entry> scanSuiteSources(
0755: Hashtable<String, String> properties, Project project)
0756: throws IOException {
0757: File basedir = new File(properties.get("basedir"));
0758: String suiteDir = properties.get("suite.dir");
0759: if (suiteDir == null) {
0760: throw new IOException("No definition of suite.dir in "
0761: + basedir);
0762: }
0763: File suite = FileUtils.getFileUtils().resolveFile(basedir,
0764: suiteDir);
0765: if (!suite.isDirectory()) {
0766: throw new IOException("No such suite " + suite);
0767: }
0768: Map<String, Entry> entries = SUITE_SCAN_CACHE.get(suite);
0769: if (entries == null) {
0770: if (project != null) {
0771: project.log("Scanning for modules in suite " + suite);
0772: }
0773: entries = new HashMap<String, Entry>();
0774: doScanSuite(entries, suite, properties, project);
0775: if (project != null) {
0776: project.log("Found modules: " + entries.keySet(),
0777: Project.MSG_VERBOSE);
0778: }
0779: SUITE_SCAN_CACHE.put(suite, entries);
0780: }
0781: return entries;
0782: }
0783:
0784: private static void doScanSuite(Map<String, Entry> entries,
0785: File suite, Hashtable<String, String> properties,
0786: Project project) throws IOException {
0787: Project fakeproj = new Project();
0788: fakeproj.setBaseDir(suite); // in case ${basedir} is used somewhere
0789: Property faketask = new Property();
0790: faketask.setProject(fakeproj);
0791: faketask.setFile(new File(suite,
0792: "nbproject/private/private.properties".replace('/',
0793: File.separatorChar)));
0794: faketask.execute();
0795: faketask.setFile(new File(suite, "nbproject/project.properties"
0796: .replace('/', File.separatorChar)));
0797: faketask.execute();
0798: String modulesS = fakeproj.getProperty("modules");
0799: if (modulesS == null) {
0800: throw new IOException("No definition of modules in "
0801: + suite);
0802: }
0803: String[] modules = Path.translatePath(fakeproj, modulesS);
0804: for (int i = 0; i < modules.length; i++) {
0805: File module = new File(modules[i]);
0806: if (!module.isDirectory()) {
0807: throw new IOException("No such module " + module
0808: + " referred to from " + suite);
0809: }
0810: if (!scanPossibleProject(module, entries, properties, null,
0811: ParseProjectXml.TYPE_SUITE, project, null)) {
0812: throw new IOException("No valid module found in "
0813: + module + " referred to from " + suite);
0814: }
0815: }
0816: }
0817:
0818: private static Entry scanStandaloneSource(
0819: Hashtable<String, String> properties, Project project)
0820: throws IOException {
0821: if (properties.get("project") == null)
0822: return null; //Not a standalone module
0823: File basedir = new File(properties.get("project"));
0824: Entry entry = STANDALONE_SCAN_CACHE.get(basedir);
0825: if (entry == null) {
0826: Map<String, Entry> entries = new HashMap<String, Entry>();
0827: if (!scanPossibleProject(basedir, entries, properties,
0828: null, ParseProjectXml.TYPE_STANDALONE, project,
0829: null)) {
0830: throw new IOException("No valid module found in "
0831: + basedir);
0832: }
0833: assert entries.size() == 1;
0834: entry = entries.values().iterator().next();
0835: STANDALONE_SCAN_CACHE.put(basedir, entry);
0836: }
0837: return entry;
0838: }
0839:
0840: /** all module entries, indexed by cnb */
0841: private final Map<String, Entry> entries;
0842:
0843: /**
0844: * Initiates scan if not already parsed.
0845: * Properties interpreted:
0846: * <ol>
0847: * <li> ${nb_all} - location of NB sources (used only for netbeans.org modules)
0848: * <li> ${netbeans.dest.dir} - location of NB build
0849: * <li> ${basedir} - directory of this project (used only for standalone modules)
0850: * <li> ${suite.dir} - directory of the suite (used only for suite modules)
0851: * <li> ${nb.cluster.TOKEN} - list of module paths included in cluster TOKEN (comma-separated) (used only for netbeans.org modules)
0852: * <li> ${nb.cluster.TOKEN.dir} - directory in ${netbeans.dest.dir} where cluster TOKEN is built (used only for netbeans.org modules)
0853: * <li> ${project} - basedir for standalone modules
0854: * </ol>
0855: * @param properties some properties to be used (see above)
0856: * @param type the type of project
0857: * @param project a project ref, only for logging (may be null with no loss of semantics)
0858: */
0859: public ModuleListParser(Hashtable<String, String> properties,
0860: int type, Project project) throws IOException {
0861: String nball = properties.get("nb_all");
0862: if (type != ParseProjectXml.TYPE_NB_ORG) {
0863: // External module.
0864: File basedir = new File(properties.get("basedir"));
0865: if (nball != null && project != null) {
0866: project
0867: .log(
0868: "You must *not* declare <suite-component/> or <standalone/> for a netbeans.org module in "
0869: + basedir
0870: + "; fix project.xml to use the /2 schema",
0871: Project.MSG_WARN);
0872: }
0873: entries = scanBinaries(properties, project);
0874: if (type == ParseProjectXml.TYPE_SUITE) {
0875: entries.putAll(scanSuiteSources(properties, project));
0876: } else {
0877: assert type == ParseProjectXml.TYPE_STANDALONE;
0878: Entry e = scanStandaloneSource(properties, project);
0879: entries.put(e.getCnb(), e);
0880: }
0881: } else {
0882: // netbeans.org module.
0883: if (nball == null) {
0884: throw new IOException(
0885: "You must declare either <suite-component/> or <standalone/> for an external module in "
0886: + new File(properties.get("basedir")));
0887: }
0888: // If scan.binaries property is set or it runs from tests we scan binaries otherwise sources.
0889: boolean xtest = properties.get("xtest.home") != null
0890: && properties.get("xtest.testtype") != null;
0891: if (properties.get("scan.binaries") != null || xtest) {
0892: entries = scanBinaries(properties, project);
0893: // module itself has to be added because it doesn't have to be in binaries
0894: Entry e = scanStandaloneSource(properties, project);
0895: if (e != null) {
0896: // xtest gets module jar and cluster from binaries
0897: if (e.clusterName == null && xtest) {
0898: Entry oldEntry = entries.get(e.getCnb());
0899: if (oldEntry != null) {
0900: e = new Entry(e.getCnb(),
0901: oldEntry.getJar(), e
0902: .getClassPathExtensions(),
0903: e.sourceLocation,
0904: e.netbeansOrgPath,
0905: e.buildPrerequisites, oldEntry
0906: .getClusterName(),
0907: e.runtimeDependencies, e
0908: .getTestDependencies());
0909: }
0910: }
0911: entries.put(e.getCnb(), e);
0912: }
0913: if (!xtest) {
0914: // to allow building of depend modules on top of binary
0915: entries.putAll(scanNetBeansOrgSources(new File(
0916: nball), properties, project));
0917: }
0918: } else {
0919: entries = scanNetBeansOrgSources(new File(nball),
0920: properties, project);
0921: }
0922: }
0923: }
0924:
0925: /**
0926: * Find all entries in this list.
0927: * @return a set of all known entries
0928: */
0929: public Set<Entry> findAll() {
0930: return new HashSet<Entry>(entries.values());
0931: }
0932:
0933: /**
0934: * Find one entry by code name base.
0935: * @param cnb the desired code name base
0936: * @return the matching entry or null
0937: */
0938: public Entry findByCodeNameBase(String cnb) {
0939: return entries.get(cnb);
0940: }
0941:
0942: /** parse Openide-Module-Module-Dependencies entry
0943: * @return array of code name bases
0944: */
0945: private static String[] parseRuntimeDependencies(
0946: String moduleDependencies) {
0947: if (moduleDependencies == null) {
0948: return new String[0];
0949: }
0950: List<String> cnds = new ArrayList<String>();
0951: StringTokenizer toks = new StringTokenizer(moduleDependencies,
0952: ",");
0953: while (toks.hasMoreTokens()) {
0954: String token = toks.nextToken().trim();
0955: // substring cnd/x
0956: int slIdx = token.indexOf('/');
0957: if (slIdx != -1) {
0958: token = token.substring(0, slIdx);
0959: }
0960: // substring cnd' 'xx
0961: slIdx = token.indexOf(' ');
0962: if (slIdx != -1) {
0963: token = token.substring(0, slIdx);
0964: }
0965: // substring cnd >
0966: slIdx = token.indexOf('>');
0967: if (slIdx != -1) {
0968: token = token.substring(0, slIdx);
0969: }
0970: token = token.trim();
0971: if (token.length() > 0) {
0972: cnds.add(token);
0973: }
0974: }
0975: return cnds.toArray(new String[cnds.size()]);
0976: }
0977:
0978: /**
0979: * One entry in the file.
0980: */
0981: @SuppressWarnings("serial")
0982: // really want it to be incompatible if format changes
0983: public static final class Entry implements Serializable {
0984:
0985: // Synch with org.netbeans.modules.apisupport.project.universe.ModuleList:
0986: private final String cnb;
0987: private final File jar;
0988: private final File[] classPathExtensions;
0989: private final File sourceLocation;
0990: private final String netbeansOrgPath;
0991: private final String[] buildPrerequisites;
0992: private final String clusterName;
0993: private final String[] runtimeDependencies;
0994: // dependencies on other tests
0995: private final String[] testDependencies;
0996:
0997: Entry(String cnb, File jar, File[] classPathExtensions,
0998: File sourceLocation, String netbeansOrgPath,
0999: String[] buildPrerequisites, String clusterName,
1000: String[] runtimeDependencies, String[] testDependencies) {
1001: this .cnb = cnb;
1002: this .jar = jar;
1003: this .classPathExtensions = classPathExtensions;
1004: this .sourceLocation = sourceLocation;
1005: this .netbeansOrgPath = netbeansOrgPath;
1006: this .buildPrerequisites = buildPrerequisites;
1007: this .clusterName = clusterName;
1008: this .runtimeDependencies = runtimeDependencies;
1009: this .testDependencies = testDependencies;
1010: }
1011:
1012: /**
1013: * Get the code name base, e.g. org.netbeans.modules.ant.grammar.
1014: */
1015: public String getCnb() {
1016: return cnb;
1017: }
1018:
1019: /**
1020: * Get the absolute JAR location, e.g. .../ide5/modules/org-netbeans-modules-ant-grammar.jar.
1021: */
1022: public File getJar() {
1023: return jar;
1024: }
1025:
1026: /**
1027: * Get a list of extensions to the class path of this module (may be empty).
1028: */
1029: public File[] getClassPathExtensions() {
1030: return classPathExtensions;
1031: }
1032:
1033: /**
1034: * Get the path within netbeans.org, if this is a netbeans.org module (else null).
1035: */
1036: public String getNetbeansOrgPath() {
1037: return netbeansOrgPath;
1038: }
1039:
1040: /**
1041: * Get a list of declared build prerequisites (or null for sourceless entries).
1042: * Each entry is a code name base.
1043: */
1044: public String[] getBuildPrerequisites() {
1045: return buildPrerequisites;
1046: }
1047:
1048: /** Get runtime dependencies, OpenIDE-Module-Dependencies entry.
1049: */
1050: public String[] getRuntimeDependencies() {
1051: return runtimeDependencies;
1052: }
1053:
1054: /**
1055: * Return the name of the cluster in which this module resides.
1056: * If this entry represents an external module in source form,
1057: * then the cluster will be null. If the module represents a netbeans.org
1058: * module or a binary module in a platform, then the cluster name will
1059: * be the (base) name of the directory containing the "modules" subdirectory
1060: * (sometimes "lib" or "core") where the JAR is.
1061: */
1062: public String getClusterName() {
1063: return clusterName;
1064: }
1065:
1066: public String[] getTestDependencies() {
1067: return testDependencies;
1068: }
1069:
1070: public @Override
1071: String toString() {
1072: return (sourceLocation != null ? sourceLocation : jar)
1073: .getAbsolutePath();
1074: }
1075:
1076: }
1077:
1078: }
|