com.sun.enterprise.tools.verifier.hk2.PackageAnalyser Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.enterprise.tools.verifier.hk2;
import com.sun.enterprise.module.ModuleDefinition;
import com.sun.enterprise.module.ModuleDependency;
import com.sun.enterprise.module.Repository;
import com.sun.enterprise.tools.verifier.apiscan.classfile.ClassFile;
import com.sun.enterprise.tools.verifier.apiscan.classfile.ClassFileLoader;
import com.sun.enterprise.tools.verifier.apiscan.classfile.ClassFileLoaderFactory;
import com.sun.enterprise.tools.verifier.apiscan.classfile.Util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.jvnet.hk2.osgiadapter.OSGiDirectoryBasedRepository;
/**
* A class that inspects module definitions of a bundle and processes them
* to come up with package dependency matrix, split-packages, etc.
* Does not handle version information, so it assumes a package or a bundle
* has only one version at any given time in a distribution.
*
* @author [email protected]
*/
public class PackageAnalyser {
private Set bundles;
private Logger logger;
/**
* A dats structure to capture bundle details needed for our
* processing.
*/
public static class Bundle {
ModuleDefinition md;
/**
* Packages exported by this bundle
*/
Set exportedPkgs = new HashSet();
/**
* Packages needed by this bundle. This is NOT necessarily same as
* what is mentioned in Import-Package header. We introspect the classes
* of a bundle and generate this list.
*/
Set requiredPkgs = new HashSet();
/**
* List of all the required bundles.
*/
Set requiredBundles = new HashSet();
Bundle(ModuleDefinition md) {
this.md = md;
}
@Override
public int hashCode() {
return md.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Bundle) {
return this.md.equals(Bundle.class.cast(obj).md);
}
return false;
}
public String getName() {
return md.getName();
}
}
/**
* A wire represents a connection between an exporter bundle to an importer
* bundle for a particular package.
*/
public static class Wire {
/**
* Package that is being wired
*/
String pkg;
/**
* The bundle that exports this package
*/
Bundle exporter;
/**
* The bundle that imports this package
*/
Bundle importer;
public Wire(String pkg, Bundle importer, Bundle exporter) {
this.exporter = exporter;
this.importer = importer;
this.pkg = pkg;
}
public String getPkg() {
return pkg;
}
public Bundle getExporter() {
return exporter;
}
public Bundle getImporter() {
return importer;
}
@Override
public int hashCode() {
return pkg.hashCode();
}
@Override
public boolean equals(Object obj) {
boolean b = false;
if (obj instanceof Wire) {
Wire other = Wire.class.cast(obj);
b = pkg.equals(other.pkg);
if (b) {
if (exporter != null) {
b = exporter.equals(other.exporter);
} else {
b = other.exporter == null;
}
if (b) {
if (importer != null) {
b = importer.equals(other.importer);
} else {
b = other.importer == null;
}
}
}
}
return b;
}
@Override
public String toString() {
return "Wire [Package = " + pkg + ", Importer = " + importer.md.getName() + ", Exporter = " + exporter.md.getName() + "]";
}
}
/**
* Holds information about a split-package.
* A split-package is a package whose contents come from multiple bundles.
* Note that, a package can be exported by multiple bundles. It leads to
* problematic scenarios when they symmetric difference of set of classes
* for a package from the two bundles is not an empty set. In other words,
* they should contain identical set of classes from the same package.
*/
public static class SplitPackage {
/*
* TODO(Sahoo): Report packages that are really split. Currently,
* it reports a package as split if it is exported by multiple bundles.
*/
/**
* name of package
*/
String name;
/**
* Bundles exporting this package
*/
Set exporters = new HashSet();
public SplitPackage(String name, Set exporters) {
this.name = name;
this.exporters = exporters;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof SplitPackage) {
return name.equals(SplitPackage.class.cast(obj));
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("name " + name + " (" + exporters.size() + " times):\n");
for (Bundle b : exporters) {
sb.append(b.md.getName() + "\n");
}
return sb.toString();
}
}
private Repository moduleRepository;
public PackageAnalyser(Repository moduleRepository) {
this(moduleRepository, Logger.getAnonymousLogger());
}
public PackageAnalyser(Repository repo, Logger logger) {
this.moduleRepository = repo;
this.logger = logger;
}
/**
* Analyse the dependency of a bundle and updates it in the given bundle object.
*
* @param bundle to be analysed
*/
public void analyse(Bundle bundle) throws IOException {
bundle.requiredBundles = computeRequiredBundles(bundle);
bundle.exportedPkgs = computeExportedPackages(bundle);
bundle.requiredPkgs = computeRequiredPackages(bundle);
}
private Set computeRequiredPackages(Bundle bundle) throws IOException {
Set requiredPkgs = new HashSet();
File moduleFile = new File(bundle.md.getLocations()[0]);
String classpath = moduleFile.getAbsolutePath();
JarFile moduleJar = new JarFile(moduleFile);
ClassFileLoader cfl = ClassFileLoaderFactory.newInstance(new Object[]{classpath});
final String classExt = ".class";
for (Enumeration entries = moduleJar.entries(); entries.hasMoreElements();) {
JarEntry je = entries.nextElement();
if (je.getName().endsWith(classExt)) {
String className = Util.convertToExternalClassName(je.getName().substring(0, je.getName().length() - classExt.length()));
ClassFile cf = null;
try {
cf = cfl.load(className);
for (String c : cf.getAllReferencedClassNames()) {
requiredPkgs.add(Util.getPackageName(c));
}
} catch (IOException e) {
logger.logp(Level.FINE, "PackageAnalyser", "computeRequiredPackages", "Skipping analysis of {0} as the following exception was thrown:\n {1}", new Object[]{className, e});
}
}
}
return requiredPkgs;
}
private Set computeExportedPackages(Bundle bundle) {
Set exportedPkgs = new HashSet();
String exportedPkgsAttr = bundle.md.getManifest().getMainAttributes().getValue("Export-Package");
if (exportedPkgsAttr == null) return exportedPkgs;
// The string looks like
// Export-Package: p1;p2;version=1.4;uses:="q1,q2...,qn",p3;uses:="q1,q2";p4;p5;version=...
// First remove the regions that appear between a pair of quotes (""), as that
// can confuse the tokenizer.
// Then, tokenize using comma(,) as that separates one group of packages from another.
// Next, tokenize based on semicolon(;), but except that include everything upto the first
// token that contained '='.
while (true) {
int i1 = exportedPkgsAttr.indexOf('\"');
if (i1 == -1) break;
int i2 = exportedPkgsAttr.indexOf('\"', i1 + 1);
StringBuilder sb = new StringBuilder();
sb.append(exportedPkgsAttr.substring(0, i1));
sb.append(exportedPkgsAttr.substring(i2 + 1));
exportedPkgsAttr = sb.toString();
}
StringTokenizer st = new StringTokenizer(exportedPkgsAttr, ",", false);
while (st.hasMoreTokens()) {
String pkgGroups = st.nextToken().trim();
int idx = pkgGroups.indexOf(';');
StringTokenizer st2 = new StringTokenizer(pkgGroups, ";", false);
while (st2.hasMoreTokens()) {
String pkg = st2.nextToken();
if (pkg.indexOf('=') != -1) break;
exportedPkgs.add(pkg);
}
}
return exportedPkgs;
}
private Set computeRequiredBundles(Bundle bundle) {
Set requiredBundles = new HashSet();
for (ModuleDependency dep : bundle.md.getDependencies()) {
ModuleDefinition md = moduleRepository.find(dep.getName(), dep.getVersion());
if (md != null) {
requiredBundles.add(new Bundle(md));
} else {
System.out.println("WARNING: Missing dependency: [" + dep + "] for module [" + bundle.getName() + "]");
}
}
return requiredBundles;
}
public Collection analyseWirings() throws IOException {
List moduleDefs =
moduleDefs = moduleRepository.findAll();
bundles = new HashSet();
for (ModuleDefinition moduleDef : moduleDefs) {
Bundle bundle = new Bundle(moduleDef);
bundles.add(bundle);
analyse(bundle);
}
Set wires = new HashSet();
for (Bundle importer : bundles) {
for (String pkg : importer.requiredPkgs) {
for (Bundle exporter : bundles) {
if (exporter.exportedPkgs.contains(pkg)) {
Wire w = new Wire(pkg, importer, exporter);
wires.add(w);
}
}
}
}
List sorted = new ArrayList(wires);
Collections.sort(sorted, new Comparator() {
Collator collator = Collator.getInstance();
public int compare(Wire o1, Wire o2) {
return collator.compare(o1.pkg, o2.pkg);
}
});
return sorted;
}
/**
* Inspects bundles and reports spli-packages.
* Before calling this method, you must call {@link this#analyseWirings()}
* The colection is already sorted.
*
* @return set of split-packages, en empty set if none is found.
*/
public Collection findSplitPackages() {
assert (bundles != null);
Map> packages = new HashMap>();
for (Bundle b : bundles) {
for (String p : b.exportedPkgs) {
Set exporters = packages.get(p);
if (exporters == null) {
exporters = new HashSet();
packages.put(p, exporters);
}
exporters.add(b);
}
}
Set splitPkgs = new HashSet();
for (Map.Entry> entry : packages.entrySet()) {
if (entry.getValue().size() > 1) {
splitPkgs.add(new SplitPackage(entry.getKey(), entry.getValue()));
}
}
List sortedSplitPkgs = new ArrayList(splitPkgs);
Collections.sort(sortedSplitPkgs, new Comparator() {
Collator collator = Collator.getInstance();
public int compare(SplitPackage o1, SplitPackage o2) {
return collator.compare(o1.name, o2.name);
}
});
return sortedSplitPkgs;
}
public Collection findAllExportedPackages() {
Set packages = new HashSet();
for (Bundle b : bundles) {
packages.addAll(b.exportedPkgs);
}
List sorted = new ArrayList(packages);
Collections.sort(sorted, new Comparator() {
Collator collator = Collator.getInstance();
public int compare(String o1, String o2) {
return collator.compare(o1, o2);
}
});
return sorted;
}
public Set findAllBundles() {
return bundles;
}
public void generateWiringReport(Collection exportedPkgs, Collection wires, PrintStream out) {
out.println("");
out.println("");
out.println("");
for (String p : exportedPkgs) {
StringBuilder sb = new StringBuilder();
sb.append("\t\n");
sb.append("\t\t\n");
Set exporters = new HashSet();
for (PackageAnalyser.Wire w : wires) {
if (w.getPkg().equals(p)) {
exporters.add(w.getExporter().getName());
}
}
for (String e : exporters) {
sb.append(e + " ");
}
sb.append("\n\t\t \n");
sb.append("\t\t\n");
for (PackageAnalyser.Wire w : wires) {
if (w.getPkg().equals(p)) {
sb.append(w.getImporter().getName() + " ");
}
}
sb.append("\n\t\t \n");
sb.append("\t ");
out.println(sb);
}
out.println(" ");
}
public void generateBundleReport(PrintStream out) {
out.println("");
out.println("");
out.println("");
for (Bundle b : bundles) {
StringBuilder sb = new StringBuilder();
sb.append("\t\n");
sb.append("\t\t\n");
List sorted = new ArrayList(b.exportedPkgs);
Collections.sort(sorted, new Comparator() {
Collator collator = Collator.getInstance();
public int compare(String o1, String o2) {
return collator.compare(o1, o2);
}
});
int i = 0;
for (String p : sorted) {
sb.append("\t\t\t" + p);
if (++i < sorted.size()) {
sb.append(",\\");
}
sb.append("\n");
}
sb.append("\t\t \n");
sb.append("\t ");
out.println(sb);
}
out.println(" ");
}
public static void main(String[] args) throws Exception {
if (args.length != 4) {
System.out.println("Usage: java " + PackageAnalyser.class.getName() +
"
© 2015 - 2025 Weber Informatics LLC | Privacy Policy