blob: 8a73966e53dfe17347897366d5627102d2fcfef8 [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.nbbuild;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.resources.FileResource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Converts a set of NetBeans modules into OSGi bundles.
* Since there are some aspects of the translation that must be sensitive to
* context in order to preserve as closely as possible the semantics of the
* NetBeans module system, processing proceeds in two phases:
* <ol>
* <li>Each module in the input is opened and scanned for packages which it defines
* (including in its {@code Class-Path} extensions), and for packages which it
* (statically) refers to (not including packages it itself defines, or any
* packages in the {@code java.*} namespace). {@code OpenIDE-Module-Hide-Classpath-Packages}
* declarations are also tracked, and a list of packages which seem to be
* exported from this module (if any) is collected.
* <li>Each module in the input is reopened. Now it is actually converted to an
* OSGi bundle. Package import and export information from the first phase is
* used to decide how to represent this bundle's imports:
* <ol>
* <li>Packages hidden by this bundle, or one of its direct dependencies, are
* never to be represented in the OSGi manifest.
* <li>Packages exported by one of this bundle's dependencies can also be skipped,
* since {@code Require-Bundle} will pick them all up.
* <li>Packages in the {@code org.osgi.*} namespace can be imported.
* <li>Packages from the known Java Platform API can be imported.
* <li>All other packages can be dynamically imported. Perhaps these will be
* available somehow at runtime, perhaps from the system bundle.
* </ol>
* </ol>
*/
public class MakeOSGi extends Task {
private File destdir;
private List<ResourceCollection> modules = new ArrayList<>();
/**
* Mandatory destination directory. Bundles will be created here.
*/
public void setDestdir(File destdir) {
this.destdir = destdir;
}
/**
* Adds a set of module JARs.
* It is permitted for them to be JARs anywhere on disk,
* but it is best if they are in a cluster structure
* with ../update_tracking/*.xml present
* so that associated files can be included in the bundle.
*/
public void add(ResourceCollection modules) {
this.modules.add(modules);
}
static class Info {
final Set<String> importedPackages = new TreeSet<>();
final Set<String> exportedPackages = new TreeSet<>();
final Set<String> hiddenPackages = new TreeSet<>();
final Set<String> hiddenSubpackages = new TreeSet<>();
}
public @Override void execute() throws BuildException {
if (destdir == null) {
throw new BuildException("missing destdir");
}
List<File> jars = new ArrayList<>();
List<File> fragments = new ArrayList<>();
Map<String,Info> infos = new HashMap<>();
log("Prescanning JARs...");
for (ResourceCollection rc : modules) {
Iterator<?> it = rc.iterator();
while (it.hasNext()) {
File jar = ((FileResource) it.next()).getFile();
log("Prescanning " + jar, Project.MSG_VERBOSE);
if (jar.getParentFile().getName().equals("locale")) {
fragments.add(jar);
continue;
}
try {
try (JarFile jf = new JarFile(jar)) {
Info info = new Info();
String cnb = prescan(jf, info, this);
if (cnb == null) {
log(jar + " does not appear to be either a module or a bundle; skipping", Project.MSG_WARN);
} else if (SKIPPED_PSEUDO_MODULES.contains(cnb)) {
log("Skipping " + jar);
} else if (infos.containsKey(cnb)) {
log(jar + " appears to not be the only module named " + cnb, Project.MSG_WARN);
} else {
infos.put(cnb, info);
jars.add(jar);
}
}
} catch (Exception x) {
throw new BuildException("Could not prescan " + jar + ": " + x, x, getLocation());
}
}
}
for (File jar : jars) {
try {
process(jar, infos);
} catch (Exception x) {
throw new BuildException("Could not process " + jar + ": " + x, x, getLocation());
}
}
for (File jar : fragments) {
try {
processFragment(jar);
} catch (Exception x) {
throw new BuildException("Could not process " + jar + ": " + x, x, getLocation());
}
}
}
static String prescan(JarFile module, Info info, Task task) throws Exception {
Manifest manifest = module.getManifest();
if (manifest == null) {
return null;
}
Attributes attr = manifest.getMainAttributes();
String cnb = attr.getValue("OpenIDE-Module");
if ("org.netbeans.libs.osgi".equals(cnb)) {
// Otherwise get e.g. CCE: org.netbeans.core.osgi.Activator cannot be cast to org.osgi.framework.BundleActivator
return cnb;
}
Set<String> availablePackages = new TreeSet<>();
scanClasses(module, info.importedPackages, availablePackages, task);
File antlib = new File(module.getName().replaceFirst("([/\\\\])modules([/\\\\][^/\\\\]+)", "$1ant$1nblib$2"));
if (antlib.isFile()) {
// ant/nblib/org-netbeans-modules-debugger-jpda-ant.jar references com.sun.jdi.* packages.
// AntBridge.MainClassLoader.findClass will refuse to load these,
// since it is expected that the module loader, thus also AuxClassLoader, can load them.
// So we need to DynamicImport-Package these packages so that will be true.
Set<String> antlibPackages = new HashSet<>();
try (JarFile antlibJF = new JarFile(antlib)) {
scanClasses(antlibJF, antlibPackages, new HashSet<>(), task);
}
for (String antlibImport : antlibPackages) {
if (!antlibImport.startsWith("org.apache.tools.") && !availablePackages.contains(antlibImport)) {
info.importedPackages.add(antlibImport);
}
}
}
if (cnb != null) {
cnb = cnb.replaceFirst("/\\d+$", "");
String hide = attr.getValue("OpenIDE-Module-Hide-Classpath-Packages");
if (hide != null) {
for (String piece : hide.split("[, ]+")) {
if (piece.isEmpty()) {
continue;
}
if (piece.endsWith(".*")) {
info.hiddenPackages.add(piece.substring(0, piece.length() - ".*".length()));
} else if (piece.endsWith(".**")) {
info.hiddenSubpackages.add(piece.substring(0, piece.length() - ".**".length()));
} else {
throw new IOException("Bad OpenIDE-Module-Hide-Classpath-Packages piece: " + piece);
}
}
}
String pp = attr.getValue("OpenIDE-Module-Public-Packages");
String implVersion = attr.getValue("OpenIDE-Module-Implementation-Version");
if (implVersion != null && implVersion.matches("\\d+") &&
/* not just e.g. 201005242201 */ attr.getValue("OpenIDE-Module-Build-Version") != null) {
// Since we have no idea who might be using these packages, have to make everything public.
info.exportedPackages.addAll(availablePackages);
pp = null;
}
if (pp != null && !pp.equals("-")) {
for (String p : pp.split("[, ]+")) {
if (p.isEmpty()) {
continue;
}
if (p.endsWith(".*")) {
info.exportedPackages.add(p.substring(0, p.length() - ".*".length()));
} else {
if (!p.endsWith(".**")) {
throw new IllegalArgumentException("Invalid package export: " + p);
}
for (String actual : availablePackages) {
if (actual.equals(p.substring(0, p.length() - ".**".length()))
|| actual.startsWith(p.substring(0, p.length() - "**".length()))) {
info.exportedPackages.add(actual);
}
}
}
}
}
} else { // #180201
cnb = JarWithModuleAttributes.extractCodeName(attr);
if (cnb == null) {
return null;
}
String exportPackage = attr.getValue("Export-Package");
if (exportPackage != null) {
// http://stackoverflow.com/questions/1757065/java-splitting-a-comma-separated-string-but-ignoring-commas-in-quotes
for (String piece : exportPackage.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)")) {
info.exportedPackages.add(piece.trim().replaceFirst(";.+", ""));
}
}
}
return cnb;
}
private File findDestFile(String bundleName, String bundleVersion) throws IOException {
File destFile = new File(destdir, bundleName + (bundleVersion != null ? "-" + bundleVersion : "") + ".jar");
for (File stale : destdir.listFiles()) {
if (stale.getName().matches("\\Q" + bundleName + "\\E(-.+)?[.]jar") && !stale.equals(destFile)) {
log("Deleting copy under old name: " + stale);
if (!stale.delete()) {
throw new IOException("Could not delete: " + stale);
}
}
}
return destFile;
}
private void process(File module, Map<String,Info> infos) throws Exception {
try (JarFile jar = new JarFile(module)) {
Manifest netbeans = jar.getManifest();
Attributes netbeansAttr = netbeans.getMainAttributes();
if (netbeansAttr.getValue("Bundle-SymbolicName") != null) { // #180201
File bundleFile = findDestFile(JarWithModuleAttributes.extractCodeName(netbeansAttr), netbeansAttr.getValue("Bundle-Version"));
if (bundleFile.lastModified() > module.lastModified()) {
log("Skipping " + module + " since " + bundleFile + " is newer", Project.MSG_VERBOSE);
return;
}
Copy copy = new Copy();
copy.setProject(getProject());
copy.setOwningTarget(getOwningTarget());
copy.setFile(module);
copy.setTofile(bundleFile);
copy.setVerbose(true);
copy.execute();
return;
}
Manifest osgi = new Manifest();
Attributes osgiAttr = osgi.getMainAttributes();
translate(netbeansAttr, osgiAttr, infos);
String cnb = osgiAttr.getValue("Bundle-SymbolicName").replaceFirst(";.+", "");
File bundleFile = findDestFile(cnb, osgiAttr.getValue("Bundle-Version"));
if (bundleFile.lastModified() > module.lastModified()) {
log("Skipping " + module + " since " + bundleFile + " is newer", Project.MSG_VERBOSE);
return;
}
log("Processing " + module + " into " + bundleFile);
String dynamicImports = osgiAttr.getValue("DynamicImport-Package");
if (dynamicImports != null) {
log(cnb + " has imports of no known origin: " + dynamicImports, Project.MSG_WARN);
log("(you may need to define org.osgi.framework.system.packages.extra in your OSGi container)");
}
Properties localizedStrings = new Properties();
String locbundle = netbeansAttr.getValue("OpenIDE-Module-Localizing-Bundle");
if (locbundle != null) {
try (InputStream is = jar.getInputStream(jar.getEntry(locbundle))) {
localizedStrings.load(is);
}
osgiAttr.putValue("Bundle-Localization", locbundle.replaceFirst("[.]properties$", ""));
}
handleDisplayAttribute(localizedStrings, netbeansAttr, osgiAttr,
"OpenIDE-Module-Name", "Bundle-Name");
handleDisplayAttribute(localizedStrings, netbeansAttr, osgiAttr,
"OpenIDE-Module-Display-Category", "Bundle-Category");
handleDisplayAttribute(localizedStrings, netbeansAttr, osgiAttr,
"OpenIDE-Module-Short-Description", "Bundle-Description");
Map<String,File> bundledFiles = findBundledFiles(module, cnb);
// XXX any use for OpenIDE-Module-Long-Description?
String classPath = netbeansAttr.getValue("Class-Path");
if (classPath != null) {
StringBuilder bundleCP = new StringBuilder();
for (String rawEntry : classPath.split("[, ]+")) {
String entry = URLDecoder.decode(rawEntry, "UTF-8");
if (entry.startsWith("${java.home}")) {
continue;
}
String clusterPath = new URI(module.getParentFile().getName() + "/" + entry).normalize().toString();
if (bundledFiles.containsKey(clusterPath)) {
bundleCP.append("/OSGI-INF/files/").append(clusterPath).append(",");
} else {
log("Class-Path entry " + entry + " from " + module + " does not correspond to any apparent cluster file", Project.MSG_WARN);
}
}
osgiAttr.putValue("Bundle-Classpath", bundleCP + ".");
}
StringBuilder execFiles = null;
for (Map.Entry<String,File> bundledFile : bundledFiles.entrySet()) {
// XXX would be better to use ${nbm.executable.files} as specified by project
// (since building bundles on Windows will never set this even if running on Unix)
// but this information is available only during the project's build or in its NBM
if (bundledFile.getValue().canExecute()) {
String name = bundledFile.getKey();
if (execFiles == null) {
execFiles = new StringBuilder(name);
} else {
execFiles.append(',').append(name);
}
}
}
if (execFiles != null) {
osgiAttr.putValue("NetBeans-Executable-Files", execFiles.toString());
}
// XXX modules/lib/*.dll/so => Bundle-NativeCode (but syntax is rather complex)
try (OutputStream bundle = new FileOutputStream(bundleFile);
ZipOutputStream zos = new JarOutputStream(bundle, osgi)) {
Set<String> parents = new HashSet<>();
Enumeration<? extends ZipEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String path = entry.getName();
if (path.endsWith("/") || path.equals("META-INF/MANIFEST.MF")) {
continue;
}
try (InputStream is = jar.getInputStream(entry)) {
writeEntry(zos, path, is, parents);
}
}
for (Map.Entry<String,File> bundledFile : bundledFiles.entrySet()) {
try (InputStream is = new FileInputStream(bundledFile.getValue())) {
writeEntry(zos, "OSGI-INF/files/" + bundledFile.getKey(), is, parents);
}
}
zos.finish();
}
}
}
/**
* Translate NetBeans module metadata to OSGi equivalents.
* @param netbeans manifest header to be read
* @param osgi manifest header to be written
* @param infos information about imported and exported packages among all processed JARs
*/
void translate(Attributes netbeans, Attributes osgi, Map<String,Info> infos) throws Exception {
osgi.putValue("Manifest-Version", "1.0"); // workaround for JDK bug
osgi.putValue("Bundle-ManifestVersion", "2");
String codename = netbeans.getValue("OpenIDE-Module");
if (codename == null) {
throw new IllegalArgumentException("Does not appear to be a NetBeans module");
}
String cnb = codename.replaceFirst("/\\d+$", "");
osgi.putValue("Bundle-SymbolicName", cnb);
if (cnb.equals("org.netbeans.core.osgi")) {
osgi.putValue("Bundle-Activator", "org.netbeans.core.osgi.Activator");
}
Info myInfo = infos.get(cnb);
String spec = netbeans.getValue("OpenIDE-Module-Specification-Version");
String bundleVersion = null;
if (spec != null) {
bundleVersion = threeDotsWithMajor(spec, codename);
String buildVersion = netbeans.getValue("OpenIDE-Module-Build-Version");
if (buildVersion == null) {
buildVersion = netbeans.getValue("OpenIDE-Module-Implementation-Version");
}
if (buildVersion != null) {
bundleVersion += "." + buildVersion.replaceAll("[^a-zA-Z0-9_-]", "_");
}
osgi.putValue("Bundle-Version", bundleVersion);
}
// OpenIDE-Module-Friends is ignored since OSGi has no apparent equivalent
// (could use mandatory export constraints but friends would then
// need to use Import-Package to access, rather than Require-Bundle,
// which would require knowing which packages are being imported by that dep)
if (!myInfo.exportedPackages.isEmpty()) {
StringBuilder b = new StringBuilder();
for (String p : myInfo.exportedPackages) {
if (b.length() > 0) {
b.append(", ");
}
b.append(p);
}
osgi.putValue("Export-Package", b.toString());
}
for (String attrToCopy : new String[] {"OpenIDE-Module-Layer", "OpenIDE-Module-Install"}) {
String val = netbeans.getValue(attrToCopy);
if (val != null) {
osgi.putValue(attrToCopy, val);
}
}
StringBuilder requireBundles = new StringBuilder();
if (!STARTUP_PSEUDO_MODULES.contains(cnb)) {
// do not need to import any API, just need it to be started:
requireBundles.append("org.netbeans.core.osgi");
}
Set<String> imports = new TreeSet<>(myInfo.importedPackages);
hideImports(imports, myInfo);
String dependencies = netbeans.getValue("OpenIDE-Module-Module-Dependencies");
if (dependencies != null) {
for (String dependency : dependencies.split(" *, *")) {
String depCnb = translateDependency(requireBundles, dependency);
if (depCnb == null) {
continue;
}
Info imported = infos.get(depCnb);
if (imported != null) {
imports.removeAll(imported.exportedPackages);
hideImports(imports, imported);
} else {
log("dependency " + depCnb + " of " + cnb + " not found in batch; imports may not be correct", Project.MSG_WARN);
}
}
}
if (requireBundles.length() > 0) {
osgi.putValue("Require-Bundle", requireBundles.toString());
}
StringBuilder staticImports = new StringBuilder();
StringBuilder dynamicImports = new StringBuilder();
for (String pkg : imports) {
StringBuilder b = isOSGiOrJavaPlatform(pkg) ? staticImports : dynamicImports;
// JRE-specific dependencies will not be exported by Felix by default.
// But Felix can be configured to offer any desired packages from the framework.
// Do not use DynamicImport-Package where not really needed, as it can lead to deadlocks in Felix:
// ModuleImpl.findClassOrResourceByDelegation -> Felix.acquireGlobalLock
if (b.length() > 0) {
b.append(", ");
}
b.append(pkg);
}
if (staticImports.length() > 0) {
osgi.putValue("Import-Package", staticImports.toString());
}
if (dynamicImports.length() > 0) {
osgi.putValue("DynamicImport-Package", dynamicImports.toString());
}
// ignore OpenIDE-Module-Package-Dependencies; rarely used, and bytecode analysis is probably more accurate anyway
String javaDeps = netbeans.getValue("OpenIDE-Module-Java-Dependencies");
if (javaDeps != null) {
Matcher m = Pattern.compile("Java > (1.[6-9])").matcher(javaDeps); // 1.5 is not supported anyway
if (m.matches()) {
osgi.putValue("Bundle-RequiredExecutionEnvironment", "JavaSE-" + m.group(1));
}
}
for (String tokenAttr : new String[] {"OpenIDE-Module-Provides", "OpenIDE-Module-Needs"}) {
String v = netbeans.getValue(tokenAttr);
if (v != null) {
osgi.putValue(tokenAttr, v);
}
}
String v = netbeans.getValue("OpenIDE-Module-Requires");
if (v != null) {
StringBuilder b = null;
for (String tok : v.split("[, ]+")) {
if (!tok.matches("org.openide.modules.ModuleFormat\\d+")) {
if (b == null) {
b = new StringBuilder(tok);
} else {
b.append(", ").append(tok);
}
}
}
if (b != null) {
osgi.putValue("OpenIDE-Module-Requires", b.toString());
}
}
// autoload, eager status are ignored since OSGi has no apparent equivalent
}
private boolean isOSGiOrJavaPlatform(String pkg) {
if (pkg.startsWith("org.osgi.")) {
return true;
}
return JAVA_PLATFORM_PACKAGES.contains(pkg);
}
private static void hideImports(Set<String> imports, Info info) {
imports.removeAll(info.hiddenPackages);
Iterator<String> it = imports.iterator();
while (it.hasNext()) {
String p = it.next();
for (String prefix : info.hiddenSubpackages) {
if (p.equals(prefix) || p.startsWith(prefix + ".")) {
it.remove();
break;
}
}
}
}
private static void writeEntry(ZipOutputStream zos, String path, InputStream data, Set<String> parents) throws IOException {
int size = Math.max(data.available(), 100);
ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
byte[] buf = new byte[size];
int read;
while ((read = data.read(buf)) != -1) {
baos.write(buf, 0, read);
}
writeEntry(zos, path, baos.toByteArray(), parents);
}
private static void writeEntry(ZipOutputStream zos, String path, byte[] data, Set<String> parents) throws IOException {
assert path.length() > 0 && !path.endsWith("/") && !path.startsWith("/") && path.indexOf("//") == -1 : path;
for (int i = 0; i < path.length(); i++) {
if (path.charAt(i) == '/') {
String parent = path.substring(0, i + 1);
if (parents.add(parent)) {
ZipEntry ze = new ZipEntry(parent);
ze.setMethod(ZipEntry.STORED);
ze.setSize(0);
ze.setCrc(0);
zos.putNextEntry(ze);
zos.closeEntry();
}
}
}
ZipEntry ze = new ZipEntry(path);
ze.setMethod(ZipEntry.STORED);
ze.setSize(data.length);
CRC32 crc = new CRC32();
crc.update(data);
ze.setCrc(crc.getValue());
zos.putNextEntry(ze);
zos.write(data, 0, data.length);
zos.closeEntry();
}
// copied from NetigsoModuleFactory
private static String threeDotsWithMajor(String version, String withMajor) {
int indx = withMajor.indexOf('/');
int major = 0;
if (indx > 0) {
major = Integer.parseInt(withMajor.substring(indx + 1));
}
String[] segments = (version + ".0.0.0").split("\\.");
assert segments.length >= 3 && segments[0].length() > 0;
return (Integer.parseInt(segments[0]) + major * 100) + "." + segments[1] + "." + segments[2];
}
static String translateDependency(StringBuilder b, String dependency) throws IllegalArgumentException {
Matcher m = Pattern.compile("([^/ >=]+)(?:/(\\d+)(?:-(\\d+))?)? *(?:(=|>) *(.+))?").matcher(dependency);
if (!m.matches()) {
throw new IllegalArgumentException("bad dep: " + dependency);
}
String depCnb = m.group(1);
if (SKIPPED_PSEUDO_MODULES.contains(depCnb)) {
return null;
}
String depMajLo = m.group(2);
String depMajHi = m.group(3);
String comparison = m.group(4);
String version = m.group(5);
if (b.length() > 0) {
b.append(", ");
}
b.append(depCnb);
if (!"=".equals(comparison)) {
if (version == null) {
version = "0";
}
String targetVersion = threeDotsWithMajor(version, depMajLo == null ? "" : "x/" + depMajLo);
b.append(";bundle-version=\"[").append(targetVersion).append(",");
b.append(100 * ((depMajHi != null ? Integer.parseInt(depMajHi) : depMajLo != null ? Integer.parseInt(depMajLo) : 0) + 1));
b.append(")\"");
}
return depCnb;
}
private void handleDisplayAttribute(Properties props, Attributes netbeans, Attributes osgi, String netbeansHeader, String osgiHeader) throws IOException {
String val = netbeans.getValue(netbeansHeader);
if (val != null) {
osgi.putValue(osgiHeader, val);
} else if (props.containsKey(netbeansHeader)) {
osgi.putValue(osgiHeader, "%" + netbeansHeader);
}
}
private Map<String,File> findBundledFiles(File module, String cnb) throws Exception {
Map<String,File> result = new HashMap<>();
if (module.getParentFile().getName().matches("modules|core|lib")) {
File cluster = module.getParentFile().getParentFile();
File updateTracking = new File(new File(cluster, "update_tracking"), cnb.replace('.', '-') + ".xml");
if (updateTracking.isFile()) {
Document doc = XMLUtil.parse(new InputSource(updateTracking.toURI().toString()), false, false, null, null);
NodeList nl = doc.getElementsByTagName("file");
for (int i = 0; i < nl.getLength(); i++) {
String path = ((Element) nl.item(i)).getAttribute("name");
if (path.matches("config/(Modules|ModuleAutoDeps)/.+[.]xml|lib/nbexec.*")) {
continue;
}
File f = new File(cluster, path);
if (f.equals(module)) {
continue;
}
if (f.isFile()) {
result.put(path, f);
} else {
log("did not find " + f + " specified in " + updateTracking, Project.MSG_WARN);
}
}
} else {
log("did not find expected " + updateTracking, Project.MSG_WARN);
}
} else {
log("JAR " + module + " not found in expected cluster layout", Project.MSG_WARN);
}
return result;
}
private static void scanClasses(JarFile module, Set<String> importedPackages, Set<String> availablePackages, Task task) throws Exception {
Map<String, byte[]> classfiles = new TreeMap<>();
VerifyClassLinkage.read(module, classfiles, new HashSet<>(Collections.singleton(new File(module.getName()))), task, null);
for (Map.Entry<String,byte[]> entry : classfiles.entrySet()) {
String available = entry.getKey();
int idx = available.lastIndexOf('.');
if (idx != -1) {
availablePackages.add(available.substring(0, idx));
}
for (String clazz : VerifyClassLinkage.dependencies(entry.getValue())) {
if (classfiles.containsKey(clazz)) {
// Part of the same module; probably not an external import.
continue;
}
if (clazz.startsWith("java.")) {
// No need to declare as an import.
continue;
}
idx = clazz.lastIndexOf('.');
if (idx != -1) {
importedPackages.add(clazz.substring(0, idx));
}
}
}
}
private void processFragment(File fragment) throws Exception {
String cnb = findFragmentHost(fragment);
File bundleFile = new File(destdir, fragment.getName());
if (bundleFile.lastModified() > fragment.lastModified()) {
log("Skipping " + fragment + " since " + bundleFile + " is newer", Project.MSG_VERBOSE);
return;
}
log("Processing " + fragment + " into " + bundleFile);
Manifest mf = new Manifest();
Attributes attr = mf.getMainAttributes();
attr.putValue("Manifest-Version", "1.0"); // workaround for JDK bug
attr.putValue("Bundle-ManifestVersion", "2");
attr.putValue("Bundle-SymbolicName", cnb + "-branding");
attr.putValue("Fragment-Host", cnb);
try (JarFile jar = new JarFile(fragment);
OutputStream bundle = new FileOutputStream(bundleFile);
ZipOutputStream zos = new JarOutputStream(bundle, mf)) {
Set<String> parents = new HashSet<>();
Enumeration<? extends ZipEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String path = entry.getName();
if (path.endsWith("/") || path.equals("META-INF/MANIFEST.MF")) {
continue;
} try (InputStream is = jar.getInputStream(entry)) {
writeEntry(zos, path, is, parents);
}
}
zos.finish();
}
}
static String findFragmentHost(File jar) {
String cnb = jar.getName().replaceFirst("(_[a-z][a-z0-9]*)*[.]jar$", "").replace('-', '.');
// Historical naming patterns:
if (cnb.equals("core") && jar.getParentFile().getParentFile().getName().equals("core")) {
return "org.netbeans.core.startup";
} else if (cnb.equals("boot") && jar.getParentFile().getParentFile().getName().equals("lib")) {
return "org.netbeans.bootstrap";
} else {
return cnb;
}
}
/**
* List of packages guaranteed to be in the Java platform.
* Taken from Felix's default.properties@1347814#jre-1.6 but same as JDK 6's package-list.
* FELIX-2572 updated this to not include some packages actually in the JRE
* (rt.jar and src.zip); can cause problems in some cases (cf. bug #210325 comment #11).
*/
private static final Set<String> JAVA_PLATFORM_PACKAGES = new TreeSet<String>(Arrays.asList(
"javax.accessibility",
"javax.activation",
"javax.activity",
"javax.annotation",
"javax.annotation.processing",
"javax.crypto",
"javax.crypto.interfaces",
"javax.crypto.spec",
"javax.imageio",
"javax.imageio.event",
"javax.imageio.metadata",
"javax.imageio.plugins.bmp",
"javax.imageio.plugins.jpeg",
"javax.imageio.spi",
"javax.imageio.stream",
"javax.jws",
"javax.jws.soap",
"javax.lang.model",
"javax.lang.model.element",
"javax.lang.model.type",
"javax.lang.model.util",
"javax.management",
"javax.management.loading",
"javax.management.modelmbean",
"javax.management.monitor",
"javax.management.openmbean",
"javax.management.relation",
"javax.management.remote",
"javax.management.remote.rmi",
"javax.management.timer",
"javax.naming",
"javax.naming.directory",
"javax.naming.event",
"javax.naming.ldap",
"javax.naming.spi",
"javax.net",
"javax.net.ssl",
"javax.print",
"javax.print.attribute",
"javax.print.attribute.standard",
"javax.print.event",
"javax.rmi",
"javax.rmi.CORBA",
"javax.rmi.ssl",
"javax.script",
"javax.security.auth",
"javax.security.auth.callback",
"javax.security.auth.kerberos",
"javax.security.auth.login",
"javax.security.auth.spi",
"javax.security.auth.x500",
"javax.security.cert",
"javax.security.sasl",
"javax.sound.midi",
"javax.sound.midi.spi",
"javax.sound.sampled",
"javax.sound.sampled.spi",
"javax.sql",
"javax.sql.rowset",
"javax.sql.rowset.serial",
"javax.sql.rowset.spi",
"javax.swing",
"javax.swing.border",
"javax.swing.colorchooser",
"javax.swing.event",
"javax.swing.filechooser",
"javax.swing.plaf",
"javax.swing.plaf.basic",
"javax.swing.plaf.metal",
"javax.swing.plaf.multi",
"javax.swing.plaf.synth",
"javax.swing.table",
"javax.swing.text",
"javax.swing.text.html",
"javax.swing.text.html.parser",
"javax.swing.text.rtf",
"javax.swing.tree",
"javax.swing.undo",
"javax.tools",
"javax.transaction",
"javax.transaction.xa",
"javax.xml",
"javax.xml.bind",
"javax.xml.bind.annotation",
"javax.xml.bind.annotation.adapters",
"javax.xml.bind.attachment",
"javax.xml.bind.helpers",
"javax.xml.bind.util",
"javax.xml.crypto",
"javax.xml.crypto.dom",
"javax.xml.crypto.dsig",
"javax.xml.crypto.dsig.dom",
"javax.xml.crypto.dsig.keyinfo",
"javax.xml.crypto.dsig.spec",
"javax.xml.datatype",
"javax.xml.namespace",
"javax.xml.parsers",
"javax.xml.soap",
"javax.xml.stream",
"javax.xml.stream.events",
"javax.xml.stream.util",
"javax.xml.transform",
"javax.xml.transform.dom",
"javax.xml.transform.sax",
"javax.xml.transform.stax",
"javax.xml.transform.stream",
"javax.xml.validation",
"javax.xml.ws",
"javax.xml.ws.handler",
"javax.xml.ws.handler.soap",
"javax.xml.ws.http",
"javax.xml.ws.soap",
"javax.xml.ws.spi",
"javax.xml.ws.wsaddressing",
"javax.xml.xpath",
"org.ietf.jgss",
"org.omg.CORBA",
"org.omg.CORBA.DynAnyPackage",
"org.omg.CORBA.ORBPackage",
"org.omg.CORBA.TypeCodePackage",
"org.omg.CORBA.portable",
"org.omg.CORBA_2_3",
"org.omg.CORBA_2_3.portable",
"org.omg.CosNaming",
"org.omg.CosNaming.NamingContextExtPackage",
"org.omg.CosNaming.NamingContextPackage",
"org.omg.Dynamic",
"org.omg.DynamicAny",
"org.omg.DynamicAny.DynAnyFactoryPackage",
"org.omg.DynamicAny.DynAnyPackage",
"org.omg.IOP",
"org.omg.IOP.CodecFactoryPackage",
"org.omg.IOP.CodecPackage",
"org.omg.Messaging",
"org.omg.PortableInterceptor",
"org.omg.PortableInterceptor.ORBInitInfoPackage",
"org.omg.PortableServer",
"org.omg.PortableServer.CurrentPackage",
"org.omg.PortableServer.POAManagerPackage",
"org.omg.PortableServer.POAPackage",
"org.omg.PortableServer.ServantLocatorPackage",
"org.omg.PortableServer.portable",
"org.omg.SendingContext",
"org.omg.stub.java.rmi",
"org.w3c.dom",
"org.w3c.dom.bootstrap",
"org.w3c.dom.events",
"org.w3c.dom.ls",
"org.xml.sax",
"org.xml.sax.ext",
"org.xml.sax.helpers"
));
private static final Set<String> SKIPPED_PSEUDO_MODULES = new HashSet<String>(Arrays.asList(
"org.eclipse.osgi",
"org.netbeans.core.netigso",
"org.netbeans.modules.netbinox",
"org.netbeans.libs.osgi",
"org.netbeans.libs.felix"
));
private static final Set<String> STARTUP_PSEUDO_MODULES = new HashSet<String>(Arrays.asList(
"org.openide.util.lookup",
"org.openide.util.ui",
"org.openide.util",
"org.openide.modules",
"org.netbeans.bootstrap",
"org.openide.filesystems",
"org.openide.filesystems.compat8",
"org.netbeans.core.startup",
"org.netbeans.core.startup.base",
"org.netbeans.core.osgi",
"org.eclipse.osgi"
));
}