/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.modules; // THIS CLASS OUGHT NOT USE NbBundle NOR org.openide CLASSES // OUTSIDE OF openide-util.jar! UI AND FILESYSTEM/DATASYSTEM // INTERACTIONS SHOULD GO ELSEWHERE. import java.util.*; import org.openide.util.Utilities; /** A dependency a module can have. * @author Jesse Glick * @since 1.24 */ public final class Dependency { /** Dependency on another module. */ public final static int TYPE_MODULE = 1; /** Dependency on a package. */ public final static int TYPE_PACKAGE = 2; /** Dependency on Java. */ public final static int TYPE_JAVA = 3; /** Dependency on the IDE. */ public final static int TYPE_IDE = 4; /** Dependency on a token. * @see ModuleInfo#getProvides * @since 2.3 */ public final static int TYPE_REQUIRES = 5; /** Comparison by specification version. */ public final static int COMPARE_SPEC = 1; /** Comparison by implementation version. */ public final static int COMPARE_IMPL = 2; /** No comparison, just require the dependency to be present. */ public final static int COMPARE_ANY = 3; /** Name, for purposes of dependencies, of the IDE. */ public static final String IDE_NAME = System.getProperty("org.openide.major.version", "IDE"); // NOI18N /** Specification version of the IDE. */ public static final SpecificationVersion IDE_SPEC = makeSpec(System.getProperty("org.openide.specification.version")); // NOI18N /** Implementation version of the IDE. */ public static final String IDE_IMPL = System.getProperty("org.openide.version"); // NOI18N /** Name, for purposes of dependencies, of the Java platform. */ public static final String JAVA_NAME = "Java"; // NOI18N /** Specification version of the Java platform. */ public static final SpecificationVersion JAVA_SPEC = ibmHack(makeSpec(System.getProperty("java.specification.version"))); // NOI18N /** Implementation version of the Java platform. */ public static final String JAVA_IMPL = System.getProperty("java.version"); // NOI18N /** Name, for purposes of dependencies, of the Java VM. */ public static final String VM_NAME = "VM"; // NOI18N /** Specification version of the Java VM. */ public static final SpecificationVersion VM_SPEC = makeSpec(System.getProperty("java.vm.specification.version")); // NOI18N /** Implementation version of the Java VM. */ public static final String VM_IMPL = System.getProperty("java.vm.version"); // NOI18N private final int type, comparison; private final String name, version; private Dependency(int type, String name, int comparison, String version) { this.type = type; this.name = name.intern(); this.comparison = comparison; this.version = (version != null) ? version.intern() : null; } /** Verify the format of a code name. * Caller specifies whether a slash plus release version is permitted in this context. */ private static void checkCodeName(String codeName, boolean slashOK) throws IllegalArgumentException { String base; int slash = codeName.indexOf('/'); // NOI18N if (slash == -1) { base = codeName; } else { if (! slashOK) { throw new IllegalArgumentException("No slash permitted in: " + codeName); // NOI18N } base = codeName.substring(0, slash); String rest = codeName.substring(slash + 1); int dash = rest.indexOf('-'); // NOI18N try { if (dash == -1) { int release = Integer.parseInt(rest); if (release < 0) throw new IllegalArgumentException("Negative release number: " + codeName); // NOI18N } else { int release = Integer.parseInt(rest.substring(0, dash)); int releaseMax = Integer.parseInt(rest.substring(dash + 1)); if (release < 0) throw new IllegalArgumentException("Negative release number: " + codeName); // NOI18N if (releaseMax <= release) throw new IllegalArgumentException("Release number range must be increasing: " + codeName); // NOI18N } } catch (NumberFormatException e) { throw new IllegalArgumentException(e.toString()); } } // Now check that the rest is a valid package. StringTokenizer tok = new StringTokenizer(base, ".", true); // NOI18N if (tok.countTokens() % 2 == 0) { throw new NumberFormatException("Even number of pieces: " + base); // NOI18N } boolean expectingPath = true; while (tok.hasMoreTokens()) { if (expectingPath) { expectingPath = false; if (! Utilities.isJavaIdentifier(tok.nextToken())) { throw new IllegalArgumentException("Bad package component in " + base); // NOI18N } } else { if (! ".".equals(tok.nextToken())) { // NOI18N throw new NumberFormatException("Expected dot in code name: " + base); // NOI18N } expectingPath = true; } } } /** Parse dependencies from tags. * @param type like Dependency.type * @param body actual text of tag body; if <code>null</code>, returns nothing * @return a set of dependencies * @throws IllegalArgumentException if they are malformed or inconsistent */ public static Set create(int type, String body) throws IllegalArgumentException { if (body == null) { return Collections.EMPTY_SET; } Set deps = new HashSet(5); // Set<Dependency> // First split on commas. StringTokenizer tok = new StringTokenizer(body, ","); // NOI18N if (! tok.hasMoreTokens()) { throw new IllegalArgumentException("No deps given: \"" + body + "\""); // NOI18N } Map depsByKey = new HashMap(10); // Map<DependencyKey,Dependency> while (tok.hasMoreTokens()) { String onedep = tok.nextToken(); StringTokenizer tok2 = new StringTokenizer(onedep, " \t\n\r"); // NOI18N if (! tok2.hasMoreTokens()) { throw new IllegalArgumentException("No name in dependency: " + onedep); // NOI18N } String name = tok2.nextToken(); int comparison; String version; if (tok2.hasMoreTokens()) { String compthing = tok2.nextToken(); if (compthing.equals(">")) { // NOI18N comparison = Dependency.COMPARE_SPEC; } else if (compthing.equals("=")) { // NOI18N comparison = Dependency.COMPARE_IMPL; } else { throw new IllegalArgumentException("Strange comparison string: " + compthing); // NOI18N } if (! tok2.hasMoreTokens()) { throw new IllegalArgumentException("Comparison string without version: " + onedep); // NOI18N } version = tok2.nextToken(); if (tok2.hasMoreTokens()) { throw new IllegalArgumentException("Trailing garbage in dependency: " + onedep); // NOI18N } if (comparison == Dependency.COMPARE_SPEC) { try { new SpecificationVersion(version); } catch (NumberFormatException nfe) { throw new IllegalArgumentException(nfe.toString()); } } } else { comparison = Dependency.COMPARE_ANY; version = null; } if (type == Dependency.TYPE_MODULE) { checkCodeName (name, true); if (name.indexOf('-') != -1 && comparison == Dependency.COMPARE_IMPL) { throw new IllegalArgumentException("Cannot have an implementation dependency on a ranged release version: " + onedep); // NOI18N } } else if (type == Dependency.TYPE_PACKAGE) { int idx = name.indexOf('['); if (idx != -1) { if (idx > 0) { checkCodeName(name.substring(0, idx), false); } if (name.charAt(name.length() - 1) != ']') { throw new IllegalArgumentException("No close bracket on package dep: " + name); // NOI18N } checkCodeName(name.substring(idx + 1, name.length() - 1), false); } else { checkCodeName(name, false); } if (idx == 0 && comparison != Dependency.COMPARE_ANY) { throw new IllegalArgumentException("Cannot use a version comparison on a package dependency when only a sample class is given"); // NOI18N } if (idx > 0 && name.substring(idx + 1, name.length() - 1).indexOf('.') != -1) { throw new IllegalArgumentException("Cannot have a sample class with dots when package is specified"); // NOI18N } } else if (type == Dependency.TYPE_JAVA) { if (! (name.equals(JAVA_NAME) || name.equals(VM_NAME))) {// NOI18N throw new IllegalArgumentException("Java dependency must be on \"Java\" or \"VM\": " + name); // NOI18N } if (comparison == Dependency.COMPARE_ANY) { throw new IllegalArgumentException("Must give a comparison for a Java dep: " + body); // NOI18N } } else if (type == Dependency.TYPE_IDE) { if (! (name.equals ("IDE"))) { // NOI18N int slash = name.indexOf ("/"); // NOI18N boolean ok; if (slash == -1) { ok = false; } else { if (! name.substring(0, slash).equals("IDE")) { // NOI18N ok = false; } try { int v = Integer.parseInt (name.substring (slash + 1)); ok = (v >= 0); } catch (NumberFormatException e) { ok = false; } } if (! ok) { throw new IllegalArgumentException("Invalid IDE dependency: " + name); // NOI18N } } if (comparison == Dependency.COMPARE_ANY) { throw new IllegalArgumentException("Must give a comparison for an IDE dep: " + body); // NOI18N } } else if (type == Dependency.TYPE_REQUIRES) { if (comparison != Dependency.COMPARE_ANY) { throw new IllegalArgumentException("Cannot give a comparison for a token requires dep: " + body); // NOI18N } checkCodeName(name, false); } else { throw new IllegalArgumentException("unknown type"); // NOI18N } Dependency nue = new Dependency(type, name, comparison, version); DependencyKey key = new DependencyKey(nue); if (depsByKey.containsKey(key)) { throw new IllegalArgumentException("Dependency " + nue + " duplicates the similar dependency " + depsByKey.get(key)); // NOI18N } else { deps.add(nue); depsByKey.put(key, nue); } } return deps; } /** Key for checking for duplicates among dependencies. * The unique characteristics of a dependency are: * 1. The basic name. No release versions, no sample classes for packages * (though if you specify only the class and not the package, this is different). * 2. The type of dependency (module, package, etc.). * Sample things which ought not be duplicated: * 1. Sample classes within a package. * 2. The same module with different release versions (use ranged releases as needed). * 3. Impl & spec comparisons (the impl comparison is stricter anyway). * 4. Different versions of the same thing (makes no sense). */ private static final class DependencyKey { private final int type; private final String name; public DependencyKey(Dependency d) { type = d.getType(); switch (type) { case TYPE_MODULE: case TYPE_IDE: String codeName = d.getName(); int idx = codeName.lastIndexOf('/'); if (idx == -1) { name = codeName; } else { name = codeName.substring(0, idx); } break; case TYPE_PACKAGE: String pkgName = d.getName(); idx = pkgName.indexOf('['); if (idx != -1) { if (idx == 0) { // [org.apache.jasper.Constants] // Keep the [] only to differentiate it from a package name: name = pkgName; } else { // org.apache.jasper[Constants] name = pkgName.substring(0, idx); } } else { // org.apache.jasper name = pkgName; } break; default: // TYPE_REQUIRES, TYPE_JAVA name = d.getName(); break; } //System.err.println("Key for " + d + " is " + this); } public int hashCode() { return name.hashCode(); } public boolean equals(Object o) { return (o instanceof DependencyKey) && ((DependencyKey)o).name.equals(name) && ((DependencyKey)o).type == type; } public String toString() { return "DependencyKey[" + name + "," + type + "]"; // NOI18N } } /** Get the type. */ public final int getType() { return type; } /** Get the name of the depended-on object. */ public final String getName() { return name; } /** Get the comparison type. */ public final int getComparison() { return comparison; } /** Get the version to compare against (or null). */ public final String getVersion() { return version; } /** Overridden to compare contents. */ public boolean equals(Object o) { if (o.getClass() != Dependency.class) return false; Dependency d = (Dependency) o; return type == d.type && comparison == d.comparison && name.equals(d.name) && Utilities.compareObjects(version, d.version); } /** Overridden to hash by contents. */ public int hashCode() { return 772067 ^ type ^ name.hashCode(); } /** Unspecified string representation for debugging. */ public String toString() { StringBuffer buf = new StringBuffer(100); if (type == TYPE_MODULE) { buf.append("module "); // NOI18N } else if (type == TYPE_PACKAGE) { buf.append("package "); // NOI18N } else if (type == TYPE_REQUIRES) { buf.append("token "); // NOI18N } buf.append(name); if (comparison == COMPARE_IMPL) { buf.append(" = "); // NOI18N buf.append(version); } else if (comparison == COMPARE_SPEC) { buf.append(" > "); // NOI18N buf.append(version); } return buf.toString(); } /** Try to make a specification version from a string. * Deal with errors gracefully and try to recover something from it. * E.g. "1.4.0beta" is technically erroneous; correct to "1.4.0". */ private static SpecificationVersion makeSpec(String vers) { if (vers != null) { try { return new SpecificationVersion(vers); } catch (NumberFormatException nfe) { System.err.println("WARNING: invalid specification version: " + vers); // NOI18N } do { vers = vers.substring(0, vers.length() - 1); try { return new SpecificationVersion(vers); } catch (NumberFormatException nfe) { // ignore } } while (vers.length() > 0); } // Nothing decent in it at all; use zero. return new SpecificationVersion("0"); // NOI18N } /** Workaround for bug in IBM JDK 1.3. * It claims to be 1.2 as far as Java spec version goes. * See issue #12647. */ private static SpecificationVersion ibmHack(SpecificationVersion v) { if (v.equals(new SpecificationVersion("1.2")) && // NOI18N "IBM Corporation".equals(System.getProperty("java.vendor")) && // NOI18N "1.3.0".equals(System.getProperty("java.version"))) { // NOI18N System.err.println("WARNING - this IBM JDK claims java.specification.version=1.2 but is really 1.3"); // NOI18N return new SpecificationVersion("1.3"); // NOI18N } else { return v; } } }