| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.openide.modules; |
| |
| import java.io.Serializable; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import java.util.regex.Pattern; |
| import org.openide.util.BaseUtilities; |
| |
| /** A dependency a module can have. Since version 7.10 this class is |
| * {@link Serializable}. |
| * |
| * @author Jesse Glick |
| * @since 1.24 |
| */ |
| public final class Dependency implements Serializable { |
| static final long serialVersionUID = 9548259318L; |
| |
| /** 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. |
| * @deprecated This type of dependency should no longer be used. |
| */ |
| @Deprecated |
| public final static int TYPE_IDE = 4; |
| |
| /** Dependency on a token. |
| * @see ModuleInfo#getProvides |
| * @since 2.3 |
| */ |
| public final static int TYPE_REQUIRES = 5; |
| |
| /** Dependency on a token, but without need to have token provider be initialised sooner. |
| * @see ModuleInfo#getProvides |
| * @since 7.1 |
| */ |
| public final static int TYPE_NEEDS = 6; |
| |
| /** An advisory dependency on a token. If at least one provider of such token is |
| * available, it is enabled. If there is no such provider, then nothing is done |
| * or reported. |
| * |
| * @see ModuleInfo#getProvides |
| * @since 7.1 |
| */ |
| public final static int TYPE_RECOMMENDS = 7; |
| |
| /** 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; |
| |
| /** @deprecated request dependencies on direct modules */ |
| @Deprecated |
| public static final String IDE_NAME = System.getProperty("org.openide.major.version", "IDE"); // NOI18N |
| |
| /** @deprecated request dependencies on direct modules */ |
| @Deprecated |
| public static final SpecificationVersion IDE_SPEC = makeSpec( |
| System.getProperty("org.openide.specification.version") |
| ); // NOI18N |
| |
| /** @deprecated request dependencies on direct modules */ |
| @Deprecated |
| 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 = 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; |
| private final int comparison; |
| private final String name; |
| private final String 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. |
| if (!FQN.matcher(base).matches()) { |
| throw new IllegalArgumentException("Malformed dot-separated identifier: " + base); |
| } |
| } |
| private static final Pattern FQN = Pattern.compile( |
| "(?:\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)(?:[.]\\p{javaJavaIdentifierPart}+)*" // NOI18N |
| ); |
| |
| /** Parse dependencies from tags. Since version 7.32 it can parse |
| * code names that contain numbers like |
| * <code>org.apache.servicemix.specs.jsr303_api_1.0.0</code>. |
| * |
| * @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<Dependency> create(int type, String body) throws IllegalArgumentException { |
| if (body == null) { |
| return Collections.emptySet(); |
| } |
| |
| Set<Dependency> deps = new HashSet<Dependency>(5); |
| |
| // First split on commas. |
| StringTokenizer tok = new StringTokenizer(body, ","); // NOI18N |
| |
| if (!tok.hasMoreTokens()) { |
| throw new IllegalArgumentException("No deps given: \"" + body + "\""); // NOI18N |
| } |
| |
| Map<DependencyKey, Dependency> depsByKey = new HashMap<DependencyKey, Dependency>(11); |
| |
| 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 + " in " + body); // NOI18N |
| } |
| |
| if (!tok2.hasMoreTokens()) { |
| throw new IllegalArgumentException("Comparison string without version: " + onedep + " in " + body); // NOI18N |
| } |
| |
| version = tok2.nextToken(); |
| |
| if (tok2.hasMoreTokens()) { |
| throw new IllegalArgumentException("Trailing garbage in dependency: " + onedep + " in " + body); // 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 if (type == Dependency.TYPE_NEEDS) { |
| if (comparison != Dependency.COMPARE_ANY) { |
| throw new IllegalArgumentException("Cannot give a comparison for a token needs dep: " + body); // NOI18N |
| } |
| |
| checkCodeName(name, false); |
| } else if (type == Dependency.TYPE_RECOMMENDS) { |
| if (comparison != Dependency.COMPARE_ANY) { |
| throw new IllegalArgumentException("Cannot give a comparison for a token needs 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; |
| } |
| |
| /** 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. */ |
| @Override |
| 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) && |
| BaseUtilities.compareObjects(version, d.version); |
| } |
| |
| /** Overridden to hash by contents. */ |
| @Override |
| public int hashCode() { |
| return 772067 ^ type ^ name.hashCode(); |
| } |
| |
| /** Unspecified string representation for debugging. */ |
| @Override |
| public String toString() { |
| StringBuilder buf = new StringBuilder(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("requires "); // NOI18N |
| } else if (type == TYPE_NEEDS) { |
| buf.append("needs "); // NOI18N |
| } else if (type == TYPE_RECOMMENDS) { |
| buf.append("recommends "); // 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 |
| } |
| |
| /** 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 |
| } |
| } |
| } |