| /* |
| * 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" |
| )); |
| |
| } |