Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
aQute.bnd.differ.JavaElement Maven / Gradle / Ivy
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.CLASS_VERSION;
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.METHOD;
import static aQute.bnd.service.diff.Type.RETURN;
import static java.lang.reflect.Modifier.isAbstract;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
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 java.util.regex.Matcher;
import java.util.regex.Pattern;
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 {
static Pattern PARAMETERS_P = Pattern.compile(".*(\\(.*\\)).*");
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 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);
// private static final Element DEPRECATED = new Element(ACCESS,
// "deprecated", null,
// CHANGED, CHANGED, null);
final Analyzer analyzer;
final Map providerMatcher = Create.map();
final Set notAccessible = Create.set();
final Map cache = Create.map();
MultiMap packages;
final MultiMap covariant = new MultiMap();
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();
for (Iterator i = set.iterator(); i.hasNext();) {
if (notAccessible.contains(analyzer.getTypeRefFromFQN(i.next().getName())))
i.remove();
}
String version = exports.get(entry.getKey()).get(Constants.VERSION_ATTRIBUTE);
if (version != null) {
Version v = new Version(version);
set.add(new Element(Type.VERSION, v.getWithoutQualifier().toString(), null, IGNORED, IGNORED, null));
}
Element pd = new Element(Type.PACKAGE, entry.getKey().getFQN(), set, MINOR, MAJOR, null);
result.add(pd);
}
for (JAVA java : javas) {
result.add(new Element(CLASS_VERSION, java.toString(), null, Delta.CHANGED, Delta.CHANGED, null));
}
return new Element(Type.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 StringBuilder comment = new StringBuilder();
final Set members = new LinkedHashSet();
final Set methods = new LinkedHashSet();
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(Type.EXTENDS, name.getFQN(), null, MICRO, MAJOR, comment));
}
@Override
public void implementsInterfaces(TypeRef names[]) throws Exception {
// TODO is interface reordering important for binary
// compatibility??
for (TypeRef name : names) {
String comment = null;
if (clazz.isInterface() || clazz.isAbstract())
comment = inherit(members, name);
members.add(new Element(Type.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);
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;
}
OBJECT.add(child);
}
}
}
members.addAll(OBJECT);
} else {
Clazz c = analyzer.findClass(name);
if (c == null) {
return "Cannot load " + name;
}
Element s = classElement(c);
for (Element child : s.children) {
if (INHERITED.contains(child.type) && !child.name.startsWith("<")) {
members.add(child);
}
}
}
return null;
}
/**
* 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(Type.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(Type.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 = Type.ANNOTATION;
else
type = Type.INTERFACE;
else if (clazz.isEnum())
type = Type.ENUM;
else
type = Type.CLASS;
if (type == 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;
}
// Remove all synthetic methods, we need
// to treat them special for the covariant returns
// We must do this in the correct order so please
// keep the linked hashset in place!
Set synthetic = new LinkedHashSet();
for (Iterator i = methods.iterator(); i.hasNext();) {
MethodDef m = i.next();
if (m.isSynthetic()) {
synthetic.add(m);
i.remove();
}
}
// System.out.println("methods/synthetic " + clazz+ " " + methods + " "
// + synthetic);
for (MethodDef m : methods) {
Collection children = annotations.get(m);
if (children == null)
children = new HashSet();
access(children, m.getAccess(), m.isDeprecated());
// 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);
// for covariant types we need to add the return types
// and all the implemented and extended types. This is already
// do for us when we get the element of the return type.
getCovariantReturns(children, m.getType(), m.getGenericReturnType());
/**
* No longer includes synthetic methods in the tree
*/
// for (Iterator i = synthetic.iterator(); i.hasNext();)
// {
// MethodDef s = i.next();
// if (s.getName().equals(m.getName()) &&
// Arrays.equals(s.getPrototype(), m.getPrototype())) {
// i.remove();
// getCovariantReturns(children, s.getType());
// }
// }
String signature = m.getSignature();
Matcher matcher;
if (signature != null && (matcher = PARAMETERS_P.matcher(signature)).matches()) {
signature = matcher.group(1);
} else
signature = toString(m.getPrototype());
//
// 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;
}
Element member = new Element(Type.METHOD, m.getName() + signature, children, add, remove, null);
if (!members.add(member)) {
members.remove(member);
members.add(member);
}
}
/**
* Repeat for the remaining synthetic methods After long discussions
* with BJ we decided to skip the synthetic methods since they do not
* seem to contribute to the binary compatibility. They also seem to
* cause problems between compilers. ECJ onlyu adds a synthetic methods
* for the super class while Javac traverse the whole super chain. I've
* actually not been able to figure out how ECJ gets away with this but
* the behavior seems ok.
*/
// for (MethodDef m : synthetic) {
// Collection children = annotations.get(m);
// if (children == null)
// children = new HashSet();
// access(children, m.getAccess(), m.isDeprecated());
//
// // 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);
//
// // for covariant types we need to add the return types
// // and all the implemented and extended types. This is already
// // done for us when we get the element of the return type.
//
// getCovariantReturns(children, m.getType());
//
// Element member = new Element(Type.METHOD, m.getName() +
// toString(m.getPrototype()), children, add, remove,
// "synthetic");
//
// if (!members.add(member)) {
// members.remove(member);
// members.add(member);
// }
// }
for (Clazz.FieldDef f : fields) {
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(Type.CONSTANT, f.getConstant().toString(), null, CHANGED, CHANGED, null));
}
access(children, f.getAccess(), f.isDeprecated());
Element member = new Element(Type.FIELD, f.getType().getFQN() + " " + f.getName(), children, MINOR, MAJOR,
null);
if (!members.add(member)) {
members.remove(member);
members.add(member);
}
}
access(members, clazz.getAccess(), clazz.isDeprecated());
// And make the result
Element s = new Element(type, fqn, members, MINOR, MAJOR, comment.length() == 0 ? null : comment.toString());
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();
}
static Element BOOLEAN_R = new Element(RETURN, "boolean");
static Element BYTE_R = new Element(RETURN, "byte");
static Element SHORT_R = new Element(RETURN, "short");
static Element CHAR_R = new Element(RETURN, "char");
static Element INT_R = new Element(RETURN, "int");
static Element LONG_R = new Element(RETURN, "long");
static Element FLOAT_R = new Element(RETURN, "float");
static Element DOUBLE_R = new Element(RETURN, "double");
private void getCovariantReturns(Collection elements, TypeRef type, String generics) throws Exception {
if (type == null || type.isObject())
return;
if (type.isPrimitive()) {
if (type.getFQN().equals("void"))
return;
String name = type.getBinary();
Element e;
switch (name.charAt(0)) {
case 'Z' :
e = BOOLEAN_R;
break;
case 'S' :
e = SHORT_R;
break;
case 'I' :
e = INT_R;
break;
case 'B' :
e = BYTE_R;
break;
case 'C' :
e = CHAR_R;
break;
case 'J' :
e = LONG_R;
break;
case 'F' :
e = FLOAT_R;
break;
case 'D' :
e = DOUBLE_R;
break;
default :
throw new IllegalArgumentException("Unknown primitive " + type);
}
elements.add(e);
return;
}
Element current = new Element(RETURN, generics);
elements.add(current);
// List set = covariant.get(type);
// if (set != null) {
// elements.addAll(set);
// return;
// }
//
// Element current = new Element(RETURN, type.getFQN());
// Clazz clazz = analyzer.findClass(type);
// if (clazz == null) {
// elements.add(current);
// return;
// }
//
// set = Create.list();
// set.add(current);
// getCovariantReturns(set, clazz.getSuper());
//
// TypeRef[] interfaces = clazz.getInterfaces();
// if (interfaces != null)
// for (TypeRef intf : interfaces) {
// getCovariantReturns(set, intf);
// }
//
// covariant.put(type, set);
// elements.addAll(set);
}
private static void access(Collection children, int access,
@SuppressWarnings("unused") boolean deprecated) {
if (!isPublic(access))
children.add(PROTECTED);
if (isAbstract(access))
children.add(ABSTRACT);
if (isFinal(access))
children.add(FINAL);
if (isStatic(access))
children.add(STATIC);
// Ignore for now
// if (deprecated)
// children.add(DEPRECATED);
}
}