summaryrefslogtreecommitdiffstats
path: root/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis
diff options
context:
space:
mode:
Diffstat (limited to 'bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis')
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Analyze.java87
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.java162
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeFieldVisitor.java49
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.java168
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.java119
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.java35
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.java62
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ImportCollector.java35
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java79
-rw-r--r--bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Packages.java43
10 files changed, 839 insertions, 0 deletions
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Analyze.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Analyze.java
new file mode 100644
index 00000000000..c59f8559405
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Analyze.java
@@ -0,0 +1,87 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Optional;
+
+import static com.yahoo.container.plugin.util.IO.withFileInputStream;
+
+/**
+ * Main entry point for class analysis
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class Analyze {
+ public static ClassFileMetaData analyzeClass(File classFile) {
+ try {
+ return withFileInputStream(classFile, Analyze::analyzeClass);
+ } catch (RuntimeException e) {
+ throw new RuntimeException("An error occurred when analyzing " + classFile.getPath(), e);
+ }
+ }
+
+ public static ClassFileMetaData analyzeClass(InputStream inputStream) {
+ try {
+ AnalyzeClassVisitor visitor = new AnalyzeClassVisitor();
+ new ClassReader(inputStream).accept(visitor, ClassReader.SKIP_DEBUG);
+ return visitor.result();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static Optional<String> internalNameToClassName(String internalClassName) {
+ if(internalClassName == null) {
+ return Optional.empty();
+ } else {
+ return getClassName(Type.getObjectType(internalClassName));
+ }
+ }
+
+ static Optional<String> getClassName(Type aType) {
+ switch (aType.getSort()) {
+ case Type.ARRAY:
+ return getClassName(aType.getElementType());
+ case Type.OBJECT:
+ return Optional.of(aType.getClassName());
+ default:
+ return Optional.empty();
+ }
+ }
+
+ static AnnotationVisitor visitAnnotationDefault(ImportCollector collector) {
+ return new AnnotationVisitor(Opcodes.ASM6) {
+ @Override
+ public void visit(String name, Object value) {
+ }
+
+ @Override
+ public void visitEnum(String name, String desc, String value) {
+ collector.addImportWithTypeDesc(desc);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return this;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ collector.addImportWithTypeDesc(desc);
+ return this;
+ }
+
+ @Override
+ public void visitEnd() {
+ }
+ };
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.java
new file mode 100644
index 00000000000..d9519fd7986
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.java
@@ -0,0 +1,162 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import com.yahoo.osgi.annotation.ExportPackage;
+import com.yahoo.osgi.annotation.Version;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Picks up classes used in class files.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+class AnalyzeClassVisitor extends ClassVisitor implements ImportCollector {
+ private String name = null;
+ private Set<String> imports = new HashSet<>();
+ private Optional<ExportPackageAnnotation> exportPackageAnnotation = Optional.empty();
+
+ AnalyzeClassVisitor() {
+ super(Opcodes.ASM6);
+ }
+
+ @Override
+ public Set<String> imports() {
+ return imports;
+ }
+
+ @Override
+ public void visitAttribute(Attribute attribute) {
+ addImport(Type.getObjectType(attribute.type));
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ Analyze.getClassName(Type.getReturnType(desc)).ifPresent(imports::add);
+ Arrays.asList(Type.getArgumentTypes(desc)).forEach(argType -> Analyze.getClassName(argType).ifPresent(imports::add));
+ if (exceptions != null) {
+ Arrays.asList(exceptions).forEach(ex -> Analyze.internalNameToClassName(ex).ifPresent(imports::add));
+ }
+
+ AnalyzeSignatureVisitor.analyzeMethod(signature, this);
+ return new AnalyzeMethodVisitor(this);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+ Analyze.getClassName(Type.getType(desc)).ifPresent(imports::add);
+
+ AnalyzeSignatureVisitor.analyzeField(signature, this);
+ return new AnalyzeFieldVisitor(this);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ this.name = Analyze.internalNameToClassName(name)
+ .orElseThrow(() -> new RuntimeException("Unable to resolve class name for " + name));
+
+ addImportWithInternalName(superName);
+ Arrays.asList(interfaces).forEach(this::addImportWithInternalName);
+
+ AnalyzeSignatureVisitor.analyzeClass(signature, this);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ }
+
+ @Override
+ public void visitEnd() {
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T defaultVersionValue(String name) {
+ try {
+ return (T) Version.class.getMethod(name).getDefaultValue();
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Could not locate method " + name);
+ }
+ }
+
+ private AnnotationVisitor visitExportPackage() {
+ return new AnnotationVisitor(Opcodes.ASM6) {
+ private int major = defaultVersionValue("major");
+ private int minor = defaultVersionValue("minor");
+ private int micro = defaultVersionValue("micro");
+ private String qualifier = defaultVersionValue("qualifier");
+
+ @Override
+ public void visit(String name, Object value) {
+ if (name != null) {
+ switch (name) {
+ case "major":
+ major = (int) value;
+ break;
+ case "minor":
+ minor = (int) value;
+ break;
+ case "micro":
+ micro = (int) value;
+ break;
+ case "qualifier":
+ qualifier = (String) value;
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ exportPackageAnnotation = Optional.of(new ExportPackageAnnotation(major, minor, micro, qualifier));
+ }
+
+ @Override
+ public void visitEnum(String name, String desc, String value) {
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return this;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ return this;
+ }
+ };
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (ExportPackage.class.getName().equals(Type.getType(desc).getClassName())) {
+ return visitExportPackage();
+ } else {
+ addImportWithTypeDesc(desc);
+ return Analyze.visitAnnotationDefault(this);
+ }
+ }
+
+ ClassFileMetaData result() {
+ assert (!imports.contains("int"));
+ return new ClassFileMetaData(name, imports, exportPackageAnnotation);
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeFieldVisitor.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeFieldVisitor.java
new file mode 100644
index 00000000000..ea10b6ef0aa
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeFieldVisitor.java
@@ -0,0 +1,49 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author ollivir
+ */
+public class AnalyzeFieldVisitor extends FieldVisitor implements ImportCollector {
+ private final AnalyzeClassVisitor analyzeClassVisitor;
+ private final Set<String> imports = new HashSet<>();
+
+ public AnalyzeFieldVisitor(AnalyzeClassVisitor analyzeClassVisitor) {
+ super(Opcodes.ASM6);
+ this.analyzeClassVisitor = analyzeClassVisitor;
+ }
+
+ @Override
+ public Set<String> imports() {
+ return imports;
+ }
+
+ @Override
+ public void visitAttribute(Attribute attribute) {
+ addImport(Type.getObjectType(attribute.type));
+ }
+
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ addImportWithTypeDesc(desc);
+
+ return Analyze.visitAnnotationDefault(this);
+ }
+
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+ return visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public void visitEnd() {
+ analyzeClassVisitor.addImports(imports);
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.java
new file mode 100644
index 00000000000..b1a92f9c10b
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeMethodVisitor.java
@@ -0,0 +1,168 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Picks up classes used in method bodies.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+class AnalyzeMethodVisitor extends MethodVisitor implements ImportCollector {
+ private final Set<String> imports = new HashSet<>();
+ private final AnalyzeClassVisitor analyzeClassVisitor;
+
+ AnalyzeMethodVisitor(AnalyzeClassVisitor analyzeClassVisitor) {
+ super(Opcodes.ASM6);
+ this.analyzeClassVisitor = analyzeClassVisitor;
+ }
+
+ public Set<String> imports() {
+ return imports;
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+ return visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return Analyze.visitAnnotationDefault(this);
+ }
+
+ @Override
+ public void visitAttribute(Attribute attribute) {
+ addImport(Type.getObjectType(attribute.type));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ addImportWithTypeDesc(desc);
+
+ return Analyze.visitAnnotationDefault(this);
+ }
+
+ @Override
+ public void visitEnd() {
+ super.visitEnd();
+ analyzeClassVisitor.addImports(imports);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ addImportWithTypeDesc(desc);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ addImportWithInternalName(owner);
+ Arrays.asList(Type.getArgumentTypes(desc)).forEach(this::addImport);
+ addImport(Type.getReturnType(desc));
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ addImportWithInternalName(owner);
+ addImportWithTypeDesc(desc);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ addImportWithInternalName(type);
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ if (type != null) { //null means finally block
+ addImportWithInternalName(type);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
+ addImportWithTypeDesc(desc);
+ }
+
+ @Override
+ public void visitLdcInsn(Object constant) {
+ if (constant instanceof Type) {
+ addImport((Type) constant);
+ }
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(String name, String desc, Handle bootstrapMethod, Object... bootstrapMethodArgs) {
+ for (Object arg : bootstrapMethodArgs) {
+ if (arg instanceof Type) {
+ addImport((Type) arg);
+ } else if (arg instanceof Handle) {
+ addImportWithInternalName(((Handle) arg).getOwner());
+ Arrays.asList(Type.getArgumentTypes(desc)).forEach(this::addImport);
+ } else if ((arg instanceof Number) == false && (arg instanceof String) == false) {
+ throw new AssertionError("Unexpected type " + arg.getClass() + " with value '" + arg + "'");
+ }
+ }
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ }
+
+ //only for debugging
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+ super.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+
+ @Override
+ public void visitIincInsn(int variable, int increment) {
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int variable) {
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ }
+
+ @Override
+ public void visitCode() {
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.java
new file mode 100644
index 00000000000..0f5fcf89f6a
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeSignatureVisitor.java
@@ -0,0 +1,119 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+
+class AnalyzeSignatureVisitor extends SignatureVisitor implements ImportCollector {
+ private final AnalyzeClassVisitor analyzeClassVisitor;
+ private Set<String> imports = new HashSet<>();
+
+ AnalyzeSignatureVisitor(AnalyzeClassVisitor analyzeClassVisitor) {
+ super(Opcodes.ASM6);
+ this.analyzeClassVisitor = analyzeClassVisitor;
+ }
+
+ public Set<String> imports() {
+ return imports;
+ }
+
+ @Override
+ public void visitEnd() {
+ super.visitEnd();
+ analyzeClassVisitor.addImports(imports);
+ }
+
+ @Override
+ public void visitClassType(String className) {
+ addImportWithInternalName(className);
+ }
+
+ @Override
+ public void visitFormalTypeParameter(String name) {
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ return this;
+ }
+
+ @Override
+ public void visitBaseType(char descriptor) {
+ }
+
+ @Override
+ public void visitTypeVariable(String name) {
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ return this;
+ }
+
+ @Override
+ public void visitInnerClassType(String name) {
+ }
+
+ @Override
+ public void visitTypeArgument() {
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return this;
+ }
+
+ static void analyzeClass(String signature, AnalyzeClassVisitor analyzeClassVisitor) {
+ if (signature != null) {
+ new SignatureReader(signature).accept(new AnalyzeSignatureVisitor(analyzeClassVisitor));
+ }
+ }
+
+ static void analyzeMethod(String signature, AnalyzeClassVisitor analyzeClassVisitor) {
+ analyzeClass(signature, analyzeClassVisitor);
+ }
+
+ static void analyzeField(String signature, AnalyzeClassVisitor analyzeClassVisitor) {
+ if (signature != null)
+ new SignatureReader(signature).acceptType(new AnalyzeSignatureVisitor(analyzeClassVisitor));
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.java
new file mode 100644
index 00000000000..198618cabc4
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ClassFileMetaData.java
@@ -0,0 +1,35 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * The result of analyzing a .class file.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ClassFileMetaData {
+ private final String name;
+ private final Set<String> referencedClasses;
+ private final Optional<ExportPackageAnnotation> exportPackage;
+
+ public ClassFileMetaData(String name, Set<String> referencedClasses, Optional<ExportPackageAnnotation> exportPackage) {
+ this.name = name;
+ this.referencedClasses = referencedClasses;
+ this.exportPackage = exportPackage;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Set<String> getReferencedClasses() {
+ return referencedClasses;
+ }
+
+ public Optional<ExportPackageAnnotation> getExportPackage() {
+ return exportPackage;
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.java
new file mode 100644
index 00000000000..d2da9b5a226
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ExportPackageAnnotation.java
@@ -0,0 +1,62 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class ExportPackageAnnotation {
+ private final int major;
+ private final int minor;
+ private final int micro;
+ private final String qualifier;
+
+ private static final Pattern QUALIFIER_PATTERN = Pattern.compile("[\\p{Alpha}\\p{Digit}_-]*");
+
+ public ExportPackageAnnotation(int major, int minor, int micro, String qualifier) {
+ this.major = major;
+ this.minor = minor;
+ this.micro = micro;
+ this.qualifier = qualifier;
+
+ requireNonNegative(major, "major");
+ requireNonNegative(minor, "minor");
+ requireNonNegative(micro, "micro");
+ if (QUALIFIER_PATTERN.matcher(qualifier).matches() == false) {
+ throw new IllegalArgumentException(
+ exportPackageError(String.format("qualifier must follow the format (alpha|digit|'_'|'-')* but was '%s'.", qualifier)));
+ }
+ }
+
+ public String osgiVersion() {
+ return String.format("%d.%d.%d", major, minor, micro) + (qualifier.isEmpty() ? "" : "." + qualifier);
+ }
+
+ private static String exportPackageError(String msg) {
+ return "ExportPackage anntotation: " + msg;
+ }
+
+ private static void requireNonNegative(int i, String fieldName) {
+ if (i < 0) {
+ throw new IllegalArgumentException(exportPackageError(String.format("%s must be non-negative but was %d.", fieldName, i)));
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ExportPackageAnnotation that = (ExportPackageAnnotation) o;
+ return major == that.major && minor == that.minor && micro == that.micro && Objects.equals(qualifier, that.qualifier);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(major, minor, micro, qualifier);
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ImportCollector.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ImportCollector.java
new file mode 100644
index 00000000000..3946fe297f9
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/ImportCollector.java
@@ -0,0 +1,35 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import org.objectweb.asm.Type;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author ollivir
+ */
+public interface ImportCollector {
+ Set<String> imports();
+
+ default void addImportWithTypeDesc(String typeDescriptor) {
+ addImport(Type.getType(typeDescriptor));
+ }
+
+ default void addImport(Type type) {
+ addImport(Analyze.getClassName(type));
+ }
+
+ default void addImportWithInternalName(String name) {
+ addImport(Analyze.internalNameToClassName(name));
+ }
+
+ default void addImports(Collection<String> imports) {
+ imports().addAll(imports);
+ }
+
+ default void addImport(Optional<String> anImport) {
+ anImport.ifPresent(pkg -> imports().add(pkg));
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java
new file mode 100644
index 00000000000..13bbc63192c
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/PackageTally.java
@@ -0,0 +1,79 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import com.google.common.collect.Sets;
+import com.yahoo.container.plugin.util.Maps;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class PackageTally {
+ private final Map<String, Optional<ExportPackageAnnotation>> definedPackagesMap;
+ private final Set<String> referencedPackagesUnfiltered;
+
+ public PackageTally(Map<String, Optional<ExportPackageAnnotation>> definedPackagesMap, Set<String> referencedPackagesUnfiltered) {
+ this.definedPackagesMap = definedPackagesMap;
+ this.referencedPackagesUnfiltered = referencedPackagesUnfiltered;
+ }
+
+ public Set<String> definedPackages() {
+ return definedPackagesMap.keySet();
+ }
+
+ public Set<String> referencedPackages() {
+ return Sets.difference(referencedPackagesUnfiltered, definedPackages());
+ }
+
+ public Map<String, ExportPackageAnnotation> exportedPackages() {
+ Map<String, ExportPackageAnnotation> ret = new HashMap<>();
+ definedPackagesMap.forEach((k, v) -> {
+ v.ifPresent(annotation -> ret.put(k, annotation));
+ });
+ return ret;
+ }
+
+ /**
+ * Represents the classes for two package tallies that are deployed as a single unit.
+ * <p>
+ * ExportPackageAnnotations from this has precedence over the other.
+ */
+ public PackageTally combine(PackageTally other) {
+ Map<String, Optional<ExportPackageAnnotation>> map = Maps.combine(this.definedPackagesMap, other.definedPackagesMap,
+ (l, r) -> l.isPresent() ? l : r);
+ Set<String> referencedPkgs = new HashSet<>(this.referencedPackagesUnfiltered);
+ referencedPkgs.addAll(other.referencedPackagesUnfiltered);
+
+ return new PackageTally(map, referencedPkgs);
+ }
+
+ public static PackageTally combine(Collection<PackageTally> packageTallies) {
+ Map<String, Optional<ExportPackageAnnotation>> map = new HashMap<>();
+ Set<String> referencedPkgs = new HashSet<>();
+
+ for (PackageTally pt : packageTallies) {
+ pt.definedPackagesMap.forEach((k, v) -> map.merge(k, v, (l, r) -> l.isPresent() ? l : r));
+ referencedPkgs.addAll(pt.referencedPackagesUnfiltered);
+ }
+ return new PackageTally(map, referencedPkgs);
+ }
+
+ public static PackageTally fromAnalyzedClassFiles(Collection<ClassFileMetaData> analyzedClassFiles) {
+ Map<String, Optional<ExportPackageAnnotation>> map = new HashMap<>();
+ Set<String> referencedPkgs = new HashSet<>();
+
+ for (ClassFileMetaData metaData : analyzedClassFiles) {
+ String packageName = Packages.packageName(metaData.getName());
+ map.merge(packageName, metaData.getExportPackage(), (l, r) -> l.isPresent() ? l : r);
+ metaData.getReferencedClasses().forEach(className -> referencedPkgs.add(Packages.packageName(className)));
+ }
+ return new PackageTally(map, referencedPkgs);
+ }
+}
diff --git a/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Packages.java b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Packages.java
new file mode 100644
index 00000000000..f9c6503c475
--- /dev/null
+++ b/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/Packages.java
@@ -0,0 +1,43 @@
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.plugin.classanalysis;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Utility methods related to packages.
+ *
+ * @author Tony Vaagenes
+ * @author ollivir
+ */
+public class Packages {
+ public static class PackageMetaData {
+ public final Set<String> definedPackages;
+ public final Set<String> referencedExternalPackages;
+
+ public PackageMetaData(Set<String> definedPackages, Set<String> referencedExternalPackages) {
+ this.definedPackages = definedPackages;
+ this.referencedExternalPackages = referencedExternalPackages;
+ }
+ }
+
+ public static String packageName(String fullClassName) {
+ int index = fullClassName.lastIndexOf('.');
+ if (index == -1) {
+ return "";
+ } else {
+ return fullClassName.substring(0, index);
+ }
+ }
+
+ public static PackageMetaData analyzePackages(Set<ClassFileMetaData> allClasses) {
+ Set<String> definedPackages = new HashSet<>();
+ Set<String> referencedPackages = new HashSet<>();
+ for (ClassFileMetaData metaData : allClasses) {
+ definedPackages.add(packageName(metaData.getName()));
+ metaData.getReferencedClasses().forEach(className -> referencedPackages.add(packageName(className)));
+ }
+ referencedPackages.removeAll(definedPackages);
+ return new PackageMetaData(definedPackages, referencedPackages);
+ }
+}