blob: 7da76fd45366c61da0d046b1234b20e7641e556d [file] [log] [blame]
/*
* 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.netbeans;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.Module.PackageExport;
import org.openide.modules.Dependency;
import org.openide.modules.PatchFor;
import org.openide.modules.SpecificationVersion;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
/** Information about essential properties of a module.
*
* @author Jaroslav Tulach <jtulach@netbeans.org>
*/
class ModuleData {
private static final PackageExport[] ZERO_PACKAGE_ARRAY = new PackageExport[0];
private static final String[] ZERO_STRING_ARRAY = new String[0];
private final String codeName;
private final String codeNameBase;
private final int codeNameRelease;
private final String implVersion;
private final String buildVersion;
private final Set<String> friendNames;
private final SpecificationVersion specVers;
private final PackageExport[] publicPackages;
private final String[] provides;
private final Dependency[] dependencies;
private final Set<String> coveredPackages;
private final String agentClass;
private final String fragmentHostCodeName;
ModuleData(Manifest mf, Module forModule) throws InvalidException {
Attributes attr = mf.getMainAttributes();
// Code name
codeName = attr.getValue("OpenIDE-Module"); // NOI18N
if (codeName == null) {
InvalidException e = new InvalidException("Not a module: no OpenIDE-Module tag in manifest of " + /* #17629: important! */ this, mf); // NOI18N
// #29393: plausible user mistake, deal with it politely.
Exceptions.attachLocalizedMessage(e,
NbBundle.getMessage(Module.class,
"EXC_not_a_module",
this.toString()));
throw e;
}
forModule.assignData(this);
try {
// This has the side effect of checking syntax:
if (codeName.indexOf(',') != -1) {
throw new InvalidException("Illegal code name syntax parsing OpenIDE-Module: " + codeName); // NOI18N
}
Object[] cnParse = Util.parseCodeName(codeName);
codeNameBase = (String) cnParse[0];
Set<?> deps = forModule.getManager().loadDependencies(codeNameBase);
boolean verifyCNBs = deps == null;
if (verifyCNBs) {
Dependency.create(Dependency.TYPE_MODULE, codeName);
}
codeNameRelease = (cnParse[1] != null) ? ((Integer) cnParse[1]).intValue() : -1;
if (cnParse[2] != null) {
throw new NumberFormatException(codeName);
}
// Spec vers
String specVersS = attr.getValue("OpenIDE-Module-Specification-Version"); // NOI18N
if (specVersS != null) {
try {
specVers = new SpecificationVersion(specVersS);
} catch (NumberFormatException nfe) {
throw (InvalidException) new InvalidException("While parsing OpenIDE-Module-Specification-Version: " + nfe.toString()).initCause(nfe); // NOI18N
}
} else {
specVers = null;
}
String iv = attr.getValue("OpenIDE-Module-Implementation-Version"); // NOI18N
implVersion = iv == null ? "" : iv;
String bld = attr.getValue("OpenIDE-Module-Build-Version"); // NOI18N
buildVersion = bld == null ? implVersion : bld;
this.provides = computeProvides(forModule, attr, verifyCNBs, false);
// Exports
String exportsS = attr.getValue("OpenIDE-Module-Public-Packages"); // NOI18N
if (exportsS != null) {
if (exportsS.trim().equals("-")) { // NOI18N
publicPackages = ZERO_PACKAGE_ARRAY;
} else {
StringTokenizer tok = new StringTokenizer(exportsS, ", "); // NOI18N
List<Module.PackageExport> exports = new ArrayList<Module.PackageExport>(Math.max(tok.countTokens(), 1));
while (tok.hasMoreTokens()) {
String piece = tok.nextToken();
if (piece.endsWith(".*")) { // NOI18N
String pkg = piece.substring(0, piece.length() - 2);
if (verifyCNBs) {
Dependency.create(Dependency.TYPE_MODULE, pkg);
}
if (pkg.lastIndexOf('/') != -1) {
throw new IllegalArgumentException("Illegal OpenIDE-Module-Public-Packages: " + exportsS); // NOI18N
}
exports.add(new Module.PackageExport(pkg.replace('.', '/') + '/', false));
} else if (piece.endsWith(".**")) { // NOI18N
String pkg = piece.substring(0, piece.length() - 3);
if (verifyCNBs) {
Dependency.create(Dependency.TYPE_MODULE, pkg);
}
if (pkg.lastIndexOf('/') != -1) {
throw new IllegalArgumentException("Illegal OpenIDE-Module-Public-Packages: " + exportsS); // NOI18N
}
exports.add(new Module.PackageExport(pkg.replace('.', '/') + '/', true));
} else {
throw new IllegalArgumentException("Illegal OpenIDE-Module-Public-Packages: " + exportsS); // NOI18N
}
}
if (exports.isEmpty()) {
throw new IllegalArgumentException("Illegal OpenIDE-Module-Public-Packages: " + exportsS); // NOI18N
}
publicPackages = exports.toArray(new Module.PackageExport[exports.size()]);
}
} else {
// XXX new link?
Util.err.log(Level.WARNING, "module {0} does not declare OpenIDE-Module-Public-Packages "
+ "in its manifest, so all packages are considered public by default: "
+ "http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/api.html#how-vers",
codeNameBase
);
publicPackages = null;
}
{
HashSet<String> set = null;
// friends
String friends = attr.getValue("OpenIDE-Module-Friends"); // NOI18N
if (friends != null) {
StringTokenizer tok = new StringTokenizer(friends, ", "); // NOI18N
set = new HashSet<String>();
while (tok.hasMoreTokens()) {
String piece = tok.nextToken();
if (piece.indexOf('/') != -1) {
throw new IllegalArgumentException("May specify only module code name bases in OpenIDE-Module-Friends, not major release versions: " + piece); // NOI18N
}
if (verifyCNBs) {
// Indirect way of checking syntax:
Dependency.create(Dependency.TYPE_MODULE, piece);
}
// OK, add it.
set.add(piece);
}
if (set.isEmpty()) {
throw new IllegalArgumentException("Empty OpenIDE-Module-Friends: " + friends); // NOI18N
}
if (publicPackages == null || publicPackages.length == 0) {
throw new IllegalArgumentException("No use specifying OpenIDE-Module-Friends without any public packages: " + friends); // NOI18N
}
}
this.friendNames = set;
}
this.dependencies = initDeps(forModule, deps, attr);
String classLoader = attr.getValue(PatchFor.MANIFEST_FRAGMENT_HOST); // NOI18N
if (classLoader != null) {
Object[] clParse = Util.parseCodeName(classLoader);
String frag = (String)clParse[0];
if (frag != null) {
if ((frag = frag.trim()).isEmpty()) {
frag = null;
}
}
this.fragmentHostCodeName = frag;
if (verifyCNBs && frag != null) {
// Indirect way of checking syntax:
Dependency.create(Dependency.TYPE_MODULE, fragmentHostCodeName);
}
} else {
fragmentHostCodeName = null;
}
} catch (IllegalArgumentException iae) {
throw (InvalidException) new InvalidException("While parsing " + codeName + " a dependency attribute: " + iae.toString()).initCause(iae); // NOI18N
}
this.coveredPackages = new HashSet<String>();
this.agentClass = attr.getValue("Agent-Class");
}
ModuleData(Manifest mf, NetigsoModule m) throws InvalidException {
final String symbName = getMainAttribute(mf, "Bundle-SymbolicName"); // NOI18N
if (symbName == null) {
throw new InvalidException("Not an OSGi bundle: " + m);
}
m.assignData(this);
this.codeName = symbName.replace('-', '_');
int slash = codeName.lastIndexOf('/');
if (slash != -1) {
this.codeNameRelease = Integer.parseInt(symbName.substring(slash + 1));
} else {
this.codeNameRelease = -1;
}
String v = getMainAttribute(mf, "Bundle-Version"); // NOI18N
if (v == null) {
Logger.getLogger(ModuleData.class.getName()).log(Level.WARNING, "No Bundle-Version for {0}", m);
this.specVers = new SpecificationVersion(v = "0.0");
} else {
this.specVers = computeVersion(v);
}
this.codeNameBase = codeName;
String iv = getMainAttribute(mf, "OpenIDE-Module-Implementation-Version"); // NOI18N
this.implVersion = iv == null ? v : iv;
String bld = getMainAttribute(mf, "OpenIDE-Module-Build-Version"); // NOI18N
this.buildVersion = bld == null ? implVersion : bld;
this.friendNames = Collections.emptySet();
this.publicPackages = null;
this.provides = computeProvides(m, mf.getMainAttributes(), false, true);
this.dependencies = computeImported(mf.getMainAttributes());
this.coveredPackages = new HashSet<String>();
this.agentClass = getMainAttribute(mf, "Agent-Class"); // NOI18N
this.fragmentHostCodeName = null;
}
ModuleData(ObjectInput dis) throws IOException {
try {
this.codeName = dis.readUTF();
this.codeNameBase = dis.readUTF();
this.codeNameRelease = dis.readInt();
this.coveredPackages = readStrings(dis, new HashSet<String>(), true);
this.dependencies = (Dependency[]) dis.readObject();
this.implVersion = dis.readUTF();
this.buildVersion = dis.readUTF();
this.provides = readStrings(dis);
this.friendNames = readStrings(dis, new HashSet<String>(), false);
this.specVers = new SpecificationVersion(dis.readUTF());
this.publicPackages = Module.PackageExport.read(dis);
this.agentClass = dis.readUTF();
String s = dis.readUTF();
if (s != null) {
s = s.trim();
}
this.fragmentHostCodeName = s == null || s.isEmpty() ? null : s;
} catch (ClassNotFoundException cnfe) {
throw new IOException(cnfe);
}
}
void write(ObjectOutput dos) throws IOException {
dos.writeUTF(codeName);
dos.writeUTF(codeNameBase);
dos.writeInt(codeNameRelease);
writeStrings(dos, coveredPackages);
dos.writeObject(dependencies);
dos.writeUTF(implVersion);
dos.writeUTF(buildVersion);
writeStrings(dos, provides);
writeStrings(dos, friendNames);
dos.writeUTF(specVers != null ? specVers.toString() : "0");
Module.PackageExport.write(dos, publicPackages);
dos.writeUTF(agentClass == null ? "" : agentClass);
dos.writeUTF(fragmentHostCodeName == null ? "" : fragmentHostCodeName);
}
private Dependency[] computeImported(Attributes attr) {
String pkgs = attr.getValue("Import-Package"); // NOI18N
List<Dependency> arr = null;
if (pkgs != null) {
arr = new ArrayList<Dependency>();
StringTokenizer tok = createTokenizer(pkgs); // NOI18N
while (tok.hasMoreElements()) {
String dep = beforeSemicolon(tok);
arr.addAll(Dependency.create(Dependency.TYPE_RECOMMENDS, dep));
}
}
String recomm = attr.getValue("Require-Bundle"); // NOI18N
if (recomm != null) {
if (arr == null) {
arr = new ArrayList<Dependency>();
}
StringTokenizer tok = createTokenizer(recomm); // NOI18N
while (tok.hasMoreElements()) {
String dep = beforeSemicolon(tok);
arr.addAll(Dependency.create(Dependency.TYPE_RECOMMENDS, "cnb." + dep)); // NOI18N
}
}
return arr == null ? null : arr.toArray(new Dependency[0]);
}
private static StringTokenizer createTokenizer(String osgiDep) {
for (;;) {
int first = osgiDep.indexOf('"');
if (first == -1) {
break;
}
int second = osgiDep.indexOf('"', first + 1);
if (second == -1) {
break;
}
osgiDep = osgiDep.substring(0, first - 1) + osgiDep.substring(second + 1);
}
return new StringTokenizer(osgiDep, ",");
}
private static String beforeSemicolon(StringTokenizer tok) {
String dep = tok.nextToken().trim();
int semicolon = dep.indexOf(';');
if (semicolon >= 0) {
dep = dep.substring(0, semicolon);
}
return dep.replace('-', '_');
}
private String[] computeExported(boolean useOSGi, Collection<String> arr, Attributes attr) {
if (!useOSGi) {
return arr.toArray(ZERO_STRING_ARRAY);
}
String pkgs = attr.getValue("Export-Package"); // NOI18N
if (pkgs == null) {
return arr.toArray(ZERO_STRING_ARRAY);
}
StringTokenizer tok = createTokenizer(pkgs); // NOI18N
while (tok.hasMoreElements()) {
arr.add(beforeSemicolon(tok));
}
return arr.toArray(ZERO_STRING_ARRAY);
}
private String[] computeProvides(
Module forModule, Attributes attr, boolean verifyCNBs, boolean useOSGi
) throws InvalidException, IllegalArgumentException {
Set<String> arr = new LinkedHashSet<String>();
// Token provides
String providesS = attr.getValue("OpenIDE-Module-Provides"); // NOI18N
if (providesS != null) {
StringTokenizer tok = new StringTokenizer(providesS, ", "); // NOI18N
int expCount = tok.countTokens();
while (tok.hasMoreTokens()) {
String provide = tok.nextToken();
if (provide.indexOf(',') != -1) {
throw new InvalidException("Illegal code name syntax parsing OpenIDE-Module-Provides: " + provide); // NOI18N
}
if (verifyCNBs) {
Dependency.create(Dependency.TYPE_MODULE, provide);
}
if (provide.lastIndexOf('/') != -1) throw new IllegalArgumentException("Illegal OpenIDE-Module-Provides: " + provide); // NOI18N
arr.add(provide);
}
if (arr.size() != expCount) {
throw new IllegalArgumentException("Duplicate entries in OpenIDE-Module-Provides: " + providesS); // NOI18N
}
}
String[] additionalProvides = forModule.getManager().refineProvides (forModule);
if (additionalProvides != null) {
arr.addAll (Arrays.asList (additionalProvides));
}
arr.add("cnb." + getCodeNameBase()); // NOI18N
return computeExported(useOSGi, arr, attr);
}
/**
* Initializes dependencies of this module
*
* @param knownDeps Set<Dependency> of this module known from different
* source, can be null
* @param attr attributes in manifest to parse if knownDeps is null
*/
private Dependency[] initDeps(Module forModule, Set<?> knownDeps, Attributes attr)
throws IllegalStateException, IllegalArgumentException {
if (knownDeps != null) {
return knownDeps.toArray(new Dependency[knownDeps.size()]);
}
// deps
Set<Dependency> deps = new HashSet<Dependency>(20);
// First convert IDE/1 -> org.openide/1, so we never have to deal with
// "IDE deps" internally:
@SuppressWarnings(value = "deprecation")
Set<Dependency> openideDeps = Dependency.create(Dependency.TYPE_IDE, attr.getValue("OpenIDE-Module-IDE-Dependencies")); // NOI18N
if (!openideDeps.isEmpty()) {
// If empty, leave it that way; NbInstaller will add it anyway.
Dependency d = openideDeps.iterator().next();
String name = d.getName();
if (!name.startsWith("IDE/")) {
throw new IllegalStateException("Weird IDE dep: " + name); // NOI18N
}
deps.addAll(Dependency.create(Dependency.TYPE_MODULE, "org.openide/" + name.substring(4) + " > " + d.getVersion())); // NOI18N
if (deps.size() != 1) {
throw new IllegalStateException("Should be singleton: " + deps); // NOI18N
}
Util.err.log(Level.WARNING, "the module {0} uses OpenIDE-Module-IDE-Dependencies which is deprecated. See http://openide.netbeans.org/proposals/arch/modularize.html", codeNameBase); // NOI18N
}
deps.addAll(Dependency.create(Dependency.TYPE_JAVA, attr.getValue("OpenIDE-Module-Java-Dependencies"))); // NOI18N
deps.addAll(Dependency.create(Dependency.TYPE_MODULE, attr.getValue("OpenIDE-Module-Module-Dependencies"))); // NOI18N
String pkgdeps = attr.getValue("OpenIDE-Module-Package-Dependencies"); // NOI18N
if (pkgdeps != null) {
// XXX: Util.err.log(ErrorManager.WARNING, "Warning: module " + codeNameBase + " uses the OpenIDE-Module-Package-Dependencies
// manifest attribute, which is now deprecated: XXX URL TBD");
deps.addAll(Dependency.create(Dependency.TYPE_PACKAGE, pkgdeps)); // NOI18N
}
deps.addAll(Dependency.create(Dependency.TYPE_REQUIRES, attr.getValue("OpenIDE-Module-Requires"))); // NOI18N
deps.addAll(Dependency.create(Dependency.TYPE_NEEDS, attr.getValue("OpenIDE-Module-Needs"))); // NOI18N
deps.addAll(Dependency.create(Dependency.TYPE_RECOMMENDS, attr.getValue("OpenIDE-Module-Recommends"))); // NOI18N
forModule.refineDependencies(deps);
return deps.toArray(new Dependency[deps.size()]);
}
final String getFragmentHostCodeName() {
return fragmentHostCodeName;
}
final String getCodeName() {
return codeName;
}
final String getCodeNameBase() {
return codeNameBase;
}
final int getCodeNameRelease() {
return codeNameRelease;
}
final String[] getProvides() {
return provides;
}
final SpecificationVersion getSpecificationVersion() {
return specVers;
}
final PackageExport[] getPublicPackages() {
return publicPackages;
}
final Set<String> getFriendNames() {
return friendNames;
}
final Dependency[] getDependencies() {
return dependencies;
}
final String getBuildVersion() {
return buildVersion.isEmpty() ? null : buildVersion;
}
final String getImplementationVersion() {
return implVersion.isEmpty() ? null : implVersion;
}
void registerCoveredPackages(Set<String> known) {
assert coveredPackages.isEmpty();
coveredPackages.addAll(known);
}
Set<String> getCoveredPackages() {
return coveredPackages.isEmpty() ? null : coveredPackages;
}
private <T extends Collection<String>> T readStrings(
DataInput dis, T set, boolean returnEmpty
) throws IOException {
int cnt = dis.readInt();
if (!returnEmpty && cnt == 0) {
return null;
}
while (cnt-- > 0) {
set.add(dis.readUTF());
}
return set;
}
private String[] readStrings(ObjectInput dis) throws IOException {
List<String> arr = new ArrayList<String>();
readStrings(dis, arr, false);
return arr.toArray(new String[arr.size()]);
}
private void writeStrings(DataOutput dos, Collection<String> set)
throws IOException {
if (set == null) {
dos.writeInt(0);
return;
}
dos.writeInt(set.size());
for (String s : set) {
dos.writeUTF(s);
}
}
private void writeStrings(ObjectOutput dos, String[] provides) throws IOException {
writeStrings(dos, Arrays.asList(provides));
}
private static String getMainAttribute(Manifest manifest, String attr) {
String s = manifest.getMainAttributes().getValue(attr);
if (s == null) {
return null;
}
int semicolon = s.indexOf(';');
if (semicolon == -1) {
return s;
} else {
return s.substring(0, semicolon);
}
}
private static SpecificationVersion computeVersion(String v) {
int pos = -1;
for (int i = 0; i < 3; i++) {
pos = v.indexOf('.', pos + 1);
if (pos == -1) {
return new SpecificationVersion(v);
}
}
return new SpecificationVersion(v.substring(0, pos));
}
final String getAgentClass() {
return agentClass == null || agentClass.isEmpty() ? null : agentClass;
}
}