All Downloads are FREE. Search and download functionalities are using the official Maven repository.

aQute.bnd.differ.JavaElement Maven / Gradle / Ivy

The newest version!
package aQute.bnd.differ;

import static aQute.bnd.service.diff.Delta.CHANGED;
import static aQute.bnd.service.diff.Delta.IGNORED;
import static aQute.bnd.service.diff.Delta.MAJOR;
import static aQute.bnd.service.diff.Delta.MICRO;
import static aQute.bnd.service.diff.Delta.MINOR;
import static aQute.bnd.service.diff.Type.ACCESS;
import static aQute.bnd.service.diff.Type.ANNOTATED;
import static aQute.bnd.service.diff.Type.ANNOTATION;
import static aQute.bnd.service.diff.Type.API;
import static aQute.bnd.service.diff.Type.CLASS;
import static aQute.bnd.service.diff.Type.CLASS_VERSION;
import static aQute.bnd.service.diff.Type.CONSTANT;
import static aQute.bnd.service.diff.Type.ENUM;
import static aQute.bnd.service.diff.Type.EXTENDS;
import static aQute.bnd.service.diff.Type.FIELD;
import static aQute.bnd.service.diff.Type.IMPLEMENTS;
import static aQute.bnd.service.diff.Type.INTERFACE;
import static aQute.bnd.service.diff.Type.METHOD;
import static aQute.bnd.service.diff.Type.PACKAGE;
import static aQute.bnd.service.diff.Type.PROPERTY;
import static aQute.bnd.service.diff.Type.RETURN;
import static aQute.bnd.service.diff.Type.VERSION;

import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.Manifest;

import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.osgi.Analyzer;
import aQute.bnd.osgi.Annotation;
import aQute.bnd.osgi.ClassDataCollector;
import aQute.bnd.osgi.Clazz;
import aQute.bnd.osgi.Clazz.JAVA;
import aQute.bnd.osgi.Clazz.MethodDef;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Descriptors.PackageRef;
import aQute.bnd.osgi.Descriptors.TypeRef;
import aQute.bnd.osgi.Instructions;
import aQute.bnd.osgi.Packages;
import aQute.bnd.service.diff.Delta;
import aQute.bnd.service.diff.Type;
import aQute.bnd.version.Version;
import aQute.lib.collections.MultiMap;
import aQute.libg.generics.Create;

/**
 * An element that compares the access field in a binary compatible way. This
 * element is used for classes, methods, constructors, and fields. For that
 * reason we also included the only method that uses this class as a static
 * method.
 * 

* Packages *

    *
  • MAJOR - Remove a public type *
  • MINOR - Add a public class *
  • MINOR - Add an interface *
  • MINOR - Add a method to a class *
  • MINOR - Add a method to a provider interface *
  • MAJOR - Add a method to a consumer interface *
  • MINOR - Add a field *
  • MICRO - Add an annotation to a member *
  • MINOR - Change the value of a constant *
  • MICRO - -abstract *
  • MICRO - -final *
  • MICRO - -protected *
  • MAJOR - +abstract *
  • MAJOR - +final *
  • MAJOR - +protected *
*/ class JavaElement { final static EnumSet INHERITED = EnumSet.of(FIELD, METHOD, EXTENDS, IMPLEMENTS); private static final Element PROTECTED = new Element(ACCESS, "protected", null, MAJOR, MINOR, null); private static final Element PROTECTED_PROVIDER = new Element(ACCESS, "protected", null, MINOR, MINOR, null); private static final Element STATIC = new Element(ACCESS, "static", null, MAJOR, MAJOR, null); private static final Element ABSTRACT = new Element(ACCESS, "abstract", null, MAJOR, MINOR, null); private static final Element FINAL = new Element(ACCESS, "final", null, MAJOR, MINOR, null); // Common return type elements static final Element VOID_R = new Element(RETURN, "void"); static final Element BOOLEAN_R = new Element(RETURN, "boolean"); static final Element BYTE_R = new Element(RETURN, "byte"); static final Element SHORT_R = new Element(RETURN, "short"); static final Element CHAR_R = new Element(RETURN, "char"); static final Element INT_R = new Element(RETURN, "int"); static final Element LONG_R = new Element(RETURN, "long"); static final Element FLOAT_R = new Element(RETURN, "float"); static final Element DOUBLE_R = new Element(RETURN, "double"); static final Element OBJECT_R = new Element(RETURN, "java.lang.Object"); final Analyzer analyzer; final Map providerMatcher = Create.map(); final Set notAccessible = Create.set(); final Map cache = Create.map(); final MultiMap packages; final Set javas = Create.set(); final Packages exports; /** * Create an element for the API. We take the exported packages and traverse * those for their classes. If there is no manifest or it does not describe * a bundle we assume the whole contents is exported. * * @param infos */ JavaElement(Analyzer analyzer) throws Exception { this.analyzer = analyzer; Manifest manifest = analyzer.getJar() .getManifest(); if (manifest != null && manifest.getMainAttributes() .getValue(Constants.BUNDLE_MANIFESTVERSION) != null) { exports = new Packages(); for (Map.Entry entry : OSGiHeader.parseHeader(manifest.getMainAttributes() .getValue(Constants.EXPORT_PACKAGE)) .entrySet()) exports.put(analyzer.getPackageRef(entry.getKey()), entry.getValue()); } else exports = analyzer.getContained(); // // We have to gather the -providers and parse them into instructions // so we can efficiently match them during class parsing to find // out who the providers and consumers are // for (Entry entry : exports.entrySet()) { String value = entry.getValue() .get(Constants.PROVIDER_TYPE_DIRECTIVE); if (value != null) { providerMatcher.put(entry.getKey(), new Instructions(value)); } } // we now need to gather all the packages but without // creating the packages yet because we do not yet know // which classes are accessible packages = new MultiMap<>(); for (Clazz c : analyzer.getClassspace() .values()) { if (c.isSynthetic()) continue; if (c.isPublic() || c.isProtected()) { PackageRef packageName = c.getClassName() .getPackageRef(); if (exports.containsKey(packageName)) { Element cdef = classElement(c); packages.add(packageName, cdef); } } } } static Element getAPI(Analyzer analyzer) throws Exception { analyzer.analyze(); JavaElement te = new JavaElement(analyzer); return te.getLocalAPI(); } private Element getLocalAPI() throws Exception { Set result = new HashSet<>(); for (Map.Entry> entry : packages.entrySet()) { List set = entry.getValue(); set.removeIf(element -> notAccessible.contains(analyzer.getTypeRefFromFQN(element.getName()))); String version = exports.get(entry.getKey()) .get(Constants.VERSION_ATTRIBUTE); if (version != null) { Version v = new Version(version); set.add(new Element(VERSION, v.toStringWithoutQualifier(), null, IGNORED, IGNORED, null)); } Element pd = new Element(PACKAGE, entry.getKey() .getFQN(), set, MINOR, MAJOR, null); result.add(pd); } for (JAVA java : javas) { result.add(new Element(CLASS_VERSION, java.toString(), null, CHANGED, CHANGED, null)); } return new Element(API, "", result, CHANGED, CHANGED, null); } /** * Calculate the class element. This requires parsing the class file and * finding all the methods that were added etc. The parsing will take super * interfaces and super classes into account. For this reason it maintains a * queue of classes/interfaces to parse. * * @param analyzer * @param clazz * @param infos * @throws Exception */ Element classElement(final Clazz clazz) throws Exception { Element e = cache.get(clazz); if (e != null) return e; final Set members = Create.set(); final Set methods = Create.set(); final Set fields = Create.set(); final MultiMap annotations = new MultiMap<>(); final TypeRef name = clazz.getClassName(); final String fqn = name.getFQN(); final String shortName = name.getShortName(); // Check if this clazz is actually a provider or not // providers must be listed in the exported package in the // PROVIDER_TYPE directive. Instructions matchers = providerMatcher.get(name.getPackageRef()); boolean p = matchers != null && matchers.matches(shortName); final AtomicBoolean provider = new AtomicBoolean(p); // // Check if we already had this clazz in the cache // Element before = cache.get(clazz); // for super classes if (before != null) return before; clazz.parseClassFileWithCollector(new ClassDataCollector() { boolean memberEnd; Clazz.FieldDef last; @Override public void version(int minor, int major) { javas.add(Clazz.JAVA.getJava(major, minor)); } @Override public void method(MethodDef defined) { if ((defined.isProtected() || defined.isPublic())) { last = defined; methods.add(defined); } else { last = null; } } @Override public void deprecated() { if (memberEnd) clazz.setDeprecated(true); else if (last != null) last.setDeprecated(true); } @Override public void field(Clazz.FieldDef defined) { if (defined.isProtected() || defined.isPublic()) { last = defined; fields.add(defined); } else last = null; } @Override public void constant(Object o) { if (last != null) { // Must be accessible now last.setConstant(o); } } @Override public void extendsClass(TypeRef name) throws Exception { String comment = null; if (!clazz.isInterface()) comment = inherit(members, name); Clazz c = analyzer.findClass(name); if ((c == null || c.isPublic()) && !name.isObject()) members.add(new Element(EXTENDS, name.getFQN(), null, MICRO, MAJOR, comment)); } @Override public void implementsInterfaces(TypeRef names[]) throws Exception { Arrays.sort(names); // ignore type reordering for (TypeRef name : names) { String comment = null; if (clazz.isInterface() || clazz.isAbstract()) comment = inherit(members, name); members.add(new Element(IMPLEMENTS, name.getFQN(), null, MINOR, MAJOR, comment)); } } /** */ Set OBJECT = Create.set(); public String inherit(final Set members, TypeRef name) throws Exception { if (name.isObject()) { if (OBJECT.isEmpty()) { Clazz c = analyzer.findClass(name); if (c == null) { // Bnd fails on Java 9 class files #1598 // Caused by Java 9 not making class rsources // available return null; } Element s = classElement(c); for (Element child : s.children) { if (INHERITED.contains(child.type)) { String n = child.getName(); if (child.type == METHOD) { if (n.startsWith("") || "getClass()".equals(child.getName()) || n.startsWith("wait(") || n.startsWith("notify(") || n.startsWith("notifyAll(")) continue; } if (isStatic(child)) continue; OBJECT.add(child); } } } members.addAll(OBJECT); } else { Clazz c = analyzer.findClass(name); if (c == null) { return inherit(members, analyzer.getTypeRef("java/lang/Object")); } Element s = classElement(c); for (Element child : s.children) { if (isStatic(child)) continue; if (INHERITED.contains(child.type) && !child.name.startsWith("<")) { members.add(child); } } } return null; } private boolean isStatic(Element child) { boolean isStatic = child.get("static") != null; return isStatic; } /** * Deprecated annotations and Provider/Consumer Type (both bnd and * OSGi) are treated special. Other annotations are turned into a * tree. Starting with ANNOTATED, and then properties. A property is * a PROPERTY property or an ANNOTATED property if it is an * annotation. If it is an array, the key is suffixed with the * index. * *
			 *  public @interface Outer { Inner[] value(); }
			 * public @interface Inner { String[] value(); } @Outer(
			 * { @Inner("1","2"}) } class Xyz {} ANNOTATED Outer
			 * (CHANGED/CHANGED) ANNOTATED Inner (CHANGED/CHANGED) PROPERTY
			 * value.0=1 (CHANGED/CHANGED) PROPERTY value.1=2 (CHANGED/CHANGED)
			 * 
*/ @Override public void annotation(Annotation annotation) { if (Deprecated.class.getName() .equals(annotation.getName() .getFQN())) { if (memberEnd) clazz.setDeprecated(true); else if (last != null) last.setDeprecated(true); return; } Element e = annotatedToElement(annotation); if (memberEnd) { members.add(e); // // Check for the provider/consumer. We use strings because // these are not officially // released yet // String name = annotation.getName() .getFQN(); if ("aQute.bnd.annotation.ProviderType".equals(name) || "org.osgi.annotation.versioning.ProviderType".equals(name)) { provider.set(true); } else if ("aQute.bnd.annotation.ConsumerType".equals(name) || "org.osgi.annotation.versioning.ConsumerType".equals(name)) { provider.set(false); } } else if (last != null) annotations.add(last, e); } /* * Return an ANNOTATED element for this annotation. An ANNOTATED * element contains either PROPERTY children or ANNOTATED children. */ private Element annotatedToElement(Annotation annotation) { Collection properties = Create.set(); for (String key : annotation.keySet()) { addAnnotationMember(properties, key, annotation.get(key)); } return new Element(ANNOTATED, annotation.getName() .getFQN(), properties, CHANGED, CHANGED, null); } /* * This method detects 3 cases: An Annotation, which means it * creates a new child ANNOTATED element, an array, which means it * will repeat recursively but suffixes the key with the index, or a * simple value which is turned into a string. */ private void addAnnotationMember(Collection properties, String key, Object member) { if (member instanceof Annotation) { properties.add(annotatedToElement((Annotation) member)); } else if (member.getClass() .isArray()) { int l = Array.getLength(member); for (int i = 0; i < l; i++) { addAnnotationMember(properties, key + "." + i, Array.get(member, i)); } } else { StringBuilder sb = new StringBuilder(); sb.append(key); sb.append('='); if (member instanceof String) { sb.append("'"); sb.append(member); sb.append("'"); } else sb.append(member); properties.add(new Element(PROPERTY, sb.toString(), null, CHANGED, CHANGED, null)); } } @Override public void innerClass(TypeRef innerClass, TypeRef outerClass, String innerName, int innerClassAccessFlags) throws Exception { Clazz clazz = analyzer.findClass(innerClass); if (clazz != null) clazz.setInnerAccess(innerClassAccessFlags); if (Modifier.isProtected(innerClassAccessFlags) || Modifier.isPublic(innerClassAccessFlags)) return; notAccessible.add(innerClass); } @Override public void memberEnd() { memberEnd = true; } }); // This is the heart of the semantic versioning. If we // add or remove a method from an interface then Delta add; Delta remove; Type type; // Calculate the type of the clazz. A class // can be an interface, class, enum, or annotation if (clazz.isInterface()) if (clazz.isAnnotation()) type = ANNOTATION; else type = INTERFACE; else if (clazz.isEnum()) type = ENUM; else type = CLASS; if (type == INTERFACE) { if (provider.get()) { // Adding a method for a provider is not an issue // because it must be aware of the changes add = MINOR; // Removing a method influences consumers since they // tend to call this guy. remove = MAJOR; } else { // Adding a method is a major change // because the consumer has to implement it // or the provider will call a non existent // method on the consumer add = MAJOR; // Removing a method is not an issue for // providers, however, consumers could potentially // call through this interface :-( remove = MAJOR; } } else { // Adding a method to a class can never do any harm // except when the class is extended and the new // method clashes with the new method. That is // why API classes in general should be final, at // least not extended by consumers. add = MINOR; // Removing it will likely hurt consumers remove = MAJOR; } for (MethodDef m : methods) { if (m.isSynthetic()) { // Ignore synthetic methods continue; } Collection children = annotations.get(m); if (children == null) children = new HashSet<>(); access(children, m.getAccess(), m.isDeprecated(), provider.get()); // A final class cannot be extended, ergo, // all methods defined in it are by definition // final. However, marking them final (either // on the method or inheriting it from the class) // will create superfluous changes if we // override a method from a super class that was not // final. So we actually remove the final for methods // in a final class. if (clazz.isFinal()) children.remove(FINAL); children.add(getReturn(m.getType())); // // Java default methods are concrete implementations of methods // on an interface. // if (clazz.isInterface() && !m.isAbstract()) { // // We have a Java 8 default method! // Such a method is always a minor update // add = MINOR; } String signature = m.getName() + toString(m.getPrototype()); Element member = new Element(METHOD, signature, children, add, provider.get() && !m.isPublic() ? MINOR : remove, null); if (!members.add(member)) { members.remove(member); members.add(member); } } for (Clazz.FieldDef f : fields) { if (f.isSynthetic()) { // Ignore synthetic fields continue; } Collection children = annotations.get(f); if (children == null) children = new HashSet<>(); // Fields can have a constant value, this is a new element if (f.getConstant() != null) { children.add(new Element(CONSTANT, f.getConstant() .toString(), null, CHANGED, CHANGED, null)); } access(children, f.getAccess(), f.isDeprecated(), provider.get()); children.add(getReturn(f.getType())); Element member = new Element(FIELD, f.getName(), children, MINOR, provider.get() && !f.isPublic() ? MINOR : MAJOR, null); if (!members.add(member)) { members.remove(member); members.add(member); } } access(members, clazz.getAccess(), clazz.isDeprecated(), provider.get()); // And make the result Element s = new Element(type, fqn, members, MINOR, MAJOR, null); cache.put(clazz, s); return s; } private String toString(TypeRef[] prototype) { StringBuilder sb = new StringBuilder(); sb.append("("); String del = ""; for (TypeRef ref : prototype) { sb.append(del); sb.append(ref.getFQN()); del = ","; } sb.append(")"); return sb.toString(); } private Element getReturn(TypeRef type) { if (!type.isPrimitive()) { return type.isObject() ? OBJECT_R : new Element(RETURN, type.getFQN()); } switch (type.getBinary() .charAt(0)) { case 'V' : return VOID_R; case 'Z' : return BOOLEAN_R; case 'S' : return SHORT_R; case 'I' : return INT_R; case 'B' : return BYTE_R; case 'C' : return CHAR_R; case 'J' : return LONG_R; case 'F' : return FLOAT_R; case 'D' : return DOUBLE_R; default : throw new IllegalArgumentException("Unknown primitive " + type); } } private static void access(Collection children, int access, @SuppressWarnings("unused") boolean deprecated, boolean provider) { if (!Modifier.isPublic(access)) children.add(provider ? PROTECTED_PROVIDER : PROTECTED); if (Modifier.isAbstract(access)) children.add(ABSTRACT); if (Modifier.isFinal(access)) children.add(FINAL); if (Modifier.isStatic(access)) children.add(STATIC); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy