Use local copy of latest bndlib code for pre-release testing purposes

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1347815 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/Baseline.java b/bundleplugin/src/main/java/aQute/bnd/differ/Baseline.java
new file mode 100644
index 0000000..eee3e27
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/Baseline.java
@@ -0,0 +1,174 @@
+package aQute.bnd.differ;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.service.diff.*;
+import aQute.bnd.service.diff.Diff.Ignore;
+import aQute.lib.osgi.*;
+import aQute.libg.generics.*;
+import aQute.libg.header.*;
+import aQute.libg.reporter.*;
+import aQute.libg.version.*;
+
+/**
+ * This class maintains
+ * 
+ */
+public class Baseline {
+
+	public static class Info {
+		public String				packageName;
+		public Diff					packageDiff;
+		public Collection<String>	providers;
+		public Map<String, String>	attributes;
+		public Version				newerVersion;
+		public Version				olderVersion;
+		public Version				suggestedVersion;
+		public Version				suggestedIfProviders;
+		public boolean				mismatch;
+		public String				warning="";
+
+	}
+
+	final Differ	differ;
+	final Reporter	bnd;
+
+	public Baseline(Reporter bnd, Differ differ) throws IOException {
+		this.differ = differ;
+		this.bnd = bnd;
+	}
+
+	/**
+	 * This method compares a jar to a baseline jar and returns version
+	 * suggestions if the baseline does not agree with the newer jar. The
+	 * returned set contains all the exported packages.
+	 * 
+	 * @param newer
+	 * @param older
+	 * @return null if ok, otherwise a set of suggested versions for all
+	 *         packages (also the ones that were ok).
+	 * @throws Exception
+	 */
+	public Set<Info> baseline(Jar newer, Jar older, Instructions packageFilters)
+			throws Exception {
+		Tree n = differ.tree(newer);
+		Parameters nExports = getExports(newer);
+		Tree o = differ.tree(older);
+		Parameters oExports = getExports(older);
+		if ( packageFilters == null)
+			packageFilters = new Instructions();
+		
+		return baseline(n, nExports, o, oExports, packageFilters);
+	}
+
+	public Set<Info> baseline(Tree n, Parameters nExports, Tree o,
+			Parameters oExports, Instructions packageFilters)
+			throws Exception {
+		Diff diff = n.diff(o).get("<api>");
+		Set<Info> infos = Create.set();
+
+		for (Diff pdiff : diff.getChildren()) {
+			if (pdiff.getType() != Type.PACKAGE) // Just packages
+				continue;
+
+			if (pdiff.getName().startsWith("java."))
+				continue;
+
+			if (!packageFilters.matches(pdiff.getName()))
+				continue;
+
+			final Info info = new Info();
+			infos.add(info);
+
+			info.packageDiff = pdiff;
+			info.packageName = pdiff.getName();
+			info.attributes = nExports.get(info.packageName);
+			bnd.trace("attrs for %s %s", info.packageName,info.attributes);
+
+			info.newerVersion = getVersion(info.attributes);
+			info.olderVersion = getVersion(oExports.get(info.packageName));
+			if (pdiff.getDelta() == Delta.UNCHANGED) {
+				info.suggestedVersion = info.olderVersion;
+				if( !info.newerVersion.equals(info.olderVersion)) {
+					info.warning += "No difference but versions are equal";
+				}
+			} else if (pdiff.getDelta() == Delta.REMOVED) {
+				info.suggestedVersion = null;
+			} else if (pdiff.getDelta() == Delta.ADDED) {
+				info.suggestedVersion = Version.ONE;
+			} else {
+				// We have an API change
+				info.suggestedVersion = bump(pdiff.getDelta(), info.olderVersion, 1, 0);
+
+				if (info.newerVersion.compareTo(info.suggestedVersion) < 0) {
+					info.mismatch = true; // our suggested version is smaller
+											// than
+											// the
+											// old version!
+
+					// We can fix some major problems by assuming
+					// that an interface is a provider interface
+					if (pdiff.getDelta() == Delta.MAJOR) {
+
+						info.providers = Create.set();
+						if (info.attributes != null)
+							info.providers.addAll(Processor.split(info.attributes
+									.get(Constants.PROVIDER_TYPE_DIRECTIVE)));
+
+						// Calculate the new delta assuming we fix all the major
+						// interfaces
+						// by making them providers
+						Delta tryDelta = pdiff.getDelta(new Ignore() {
+							public boolean contains(Diff diff) {
+								if (diff.getType() == Type.INTERFACE
+										&& diff.getDelta() == Delta.MAJOR) {
+									info.providers.add(Descriptors.getShortName(diff.getName()));
+									return true;
+								}
+								return false;
+							}
+						});
+
+						if (tryDelta != Delta.MAJOR) {
+							info.suggestedIfProviders = bump(tryDelta, info.olderVersion, 1, 0);
+						}
+					}
+				}
+			}
+		}
+		return infos;
+	}
+
+	private Version bump(Delta delta, Version last, int offset, int base) {
+		switch (delta) {
+		case UNCHANGED:
+			return last;
+		case MINOR:
+			return new Version(last.getMajor(), last.getMinor() + offset, base);
+		case MAJOR:
+			return new Version(last.getMajor() + 1, base, base);
+		case ADDED:
+			return last;
+		default:
+			return new Version(last.getMajor(), last.getMinor(), last.getMicro() + offset);
+		}
+	}
+
+	private Version getVersion(Map<String, String> map) {
+		if (map == null)
+			return Version.LOWEST;
+
+		return Version.parseVersion(map.get(Constants.VERSION_ATTRIBUTE));
+	}
+
+	private Parameters getExports(Jar jar) throws Exception {
+		Manifest m = jar.getManifest();
+		if (m == null)
+			return new Parameters();
+
+		return OSGiHeader.parseHeader(m.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/DiffImpl.java b/bundleplugin/src/main/java/aQute/bnd/differ/DiffImpl.java
new file mode 100644
index 0000000..7b675c2
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/DiffImpl.java
@@ -0,0 +1,210 @@
+package aQute.bnd.differ;
+
+import static aQute.bnd.service.diff.Delta.*;
+
+import java.util.*;
+
+import aQute.bnd.service.diff.*;
+
+/**
+ * A DiffImpl class compares a newer Element to an older Element. The Element
+ * classes hide all the low level details. A Element class is either either
+ * Structured (has children) or it is a Leaf, it only has a value. The
+ * constructor will first build its children (if any) and then calculate the
+ * delta. Each comparable element is translated to an Element. If necessary the
+ * Element can be sub classed to provide special behavior.
+ */
+
+public class DiffImpl implements Diff, Comparable<DiffImpl> {
+
+	final Element				older;
+	final Element				newer;
+	final Collection<DiffImpl>	children;
+	final Delta					delta;
+
+	/**
+	 * The transitions table defines how the state is escalated depending on the
+	 * children. horizontally is the current delta and this is indexed with the
+	 * child delta for each child. This escalates deltas from below up.
+	 */
+	final static Delta[][]		TRANSITIONS	= {
+			{ IGNORED, UNCHANGED, CHANGED, MICRO, MINOR, MAJOR }, // IGNORED
+			{ IGNORED, UNCHANGED, CHANGED, MICRO, MINOR, MAJOR }, // UNCHANGED
+			{ IGNORED, CHANGED, CHANGED, MICRO, MINOR, MAJOR }, // CHANGED
+			{ IGNORED, MICRO, MICRO, MICRO, MINOR, MAJOR }, // MICRO
+			{ IGNORED, MINOR, MINOR, MINOR, MINOR, MAJOR }, // MINOR
+			{ IGNORED, MAJOR, MAJOR, MAJOR, MAJOR, MAJOR }, // MAJOR
+			{ IGNORED, MAJOR, MAJOR, MAJOR, MAJOR, MAJOR }, // REMOVED
+			{ IGNORED, MINOR, MINOR, MINOR, MINOR, MAJOR }, // ADDED
+											};
+
+	/**
+	 * Compares the newer against the older, traversing the children if
+	 * necessary.
+	 * 
+	 * @param newer
+	 *            The newer Element
+	 * @param older
+	 *            The older Element
+	 * @param types
+	 */
+	DiffImpl(Element newer, Element older) {
+		assert newer != null || older != null;
+		this.older = older;
+		this.newer = newer;
+
+		// Either newer or older can be null, indicating remove or add
+		// so we have to be very careful.
+		Element[] newerChildren = newer == null ? Element.EMPTY : newer.children;
+		Element[] olderChildren = older == null ? Element.EMPTY : older.children;
+
+		int o = 0;
+		int n = 0;
+		List<DiffImpl> children = new ArrayList<DiffImpl>();
+		while (true) {
+			Element nw = n < newerChildren.length ? newerChildren[n] : null;
+			Element ol = o < olderChildren.length ? olderChildren[o] : null;
+			DiffImpl diff;
+
+			if (nw == null && ol == null)
+				break;
+
+			if (nw != null && ol != null) {
+				// we have both sides
+				int result = nw.compareTo(ol);
+				if (result == 0) {
+					// we have two equal named elements
+					// use normal diff
+					diff = new DiffImpl(nw, ol);
+					n++;
+					o++;
+				} else if (result > 0) {
+					// we newer > older, so there is no newer == removed
+					diff = new DiffImpl(null, ol);
+					o++;
+				} else {
+					// we newer < older, so there is no older == added
+					diff = new DiffImpl(nw, null);
+					n++;
+				}
+			} else {
+				// we reached the end of one of the list
+				diff = new DiffImpl(nw, ol);
+				n++;
+				o++;
+			}
+			children.add(diff);
+		}
+
+		// make sure they're read only
+		this.children = Collections.unmodifiableCollection(children);
+		delta = getDelta(null);
+	}
+
+	/**
+	 * Return the absolute delta. Also see
+	 * {@link #getDelta(aQute.bnd.service.diff.Diff.Ignore)} that allows you to
+	 * ignore Diff objects on the fly (and calculate their parents accordingly).
+	 */
+	public Delta getDelta() {
+		return delta;
+	}
+
+	/**
+	 * This getDelta calculates the delta but allows the caller to ignore
+	 * certain Diff objects by calling back the ignore call back parameter. This
+	 * can be useful to ignore warnings/errors.
+	 */
+
+	public Delta getDelta(Ignore ignore) {
+
+		// If ignored, we just return ignore.
+		if (ignore != null && ignore.contains(this))
+			return IGNORED;
+
+		if (newer == null) {
+			return REMOVED;
+		} else if (older == null) {
+			return ADDED;
+		} else {
+			// now we're sure newer and older are both not null
+			assert newer != null && older != null;
+			assert newer.getClass() == older.getClass();
+
+			Delta local = Delta.UNCHANGED;
+
+			for (DiffImpl child : children) {
+				Delta sub = child.getDelta(ignore);
+				if (sub == REMOVED)
+					sub = child.older.remove;
+				else if (sub == ADDED)
+					sub = child.newer.add;
+
+				// The escalate method is used to calculate the default
+				// transition in the
+				// delta based on the children. In general the delta can
+				// only escalate, i.e.
+				// move up in the chain.
+
+				local = TRANSITIONS[sub.ordinal()][local.ordinal()];
+			}
+			return local;
+		}
+	}
+
+	public Type getType() {
+		return (newer == null ? older : newer).getType();
+	}
+
+	public String getName() {
+		return (newer == null ? older : newer).getName();
+	}
+
+	public Collection<? extends Diff> getChildren() {
+		return children;
+	}
+
+	public String toString() {
+		return String.format("%-10s %-10s %s", getDelta(), getType(), getName());
+	}
+
+	public boolean equals(Object other) {
+		if (other instanceof DiffImpl) {
+			DiffImpl o = (DiffImpl) other;
+			return getDelta() == o.getDelta() && getType() == o.getType()
+					&& getName().equals(o.getName());
+		}
+		return false;
+	}
+
+	public int hashCode() {
+		return getDelta().hashCode() ^ getType().hashCode() ^ getName().hashCode();
+	}
+
+	public int compareTo(DiffImpl other) {
+		if (getDelta() == other.getDelta()) {
+			if (getType() == other.getType()) {
+				return getName().compareTo(other.getName());
+			} else
+				return getType().compareTo(other.getType());
+		} else
+			return getDelta().compareTo(other.getDelta());
+	}
+
+	public Diff get(String name) {
+		for (DiffImpl child : children) {
+			if (child.getName().equals(name))
+				return child;
+		}
+		return null;
+	}
+
+	public Tree getOlder() {
+		return older;
+	}
+
+	public Tree getNewer() {
+		return newer;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java b/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java
new file mode 100644
index 0000000..e3a1308
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/DiffPluginImpl.java
@@ -0,0 +1,181 @@
+package aQute.bnd.differ;
+
+import static aQute.bnd.service.diff.Delta.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+import aQute.bnd.service.diff.*;
+import aQute.bnd.service.diff.Tree.Data;
+import aQute.lib.hex.*;
+import aQute.lib.io.*;
+import aQute.lib.osgi.*;
+import aQute.libg.cryptography.*;
+import aQute.libg.header.*;
+
+
+/**
+ * This Diff Plugin Implementation will compare JARs for their API (based on the
+ * Bundle Class Path and exported packages), the Manifest, and the resources.
+ * The Differences are represented in a {@link Diff} tree.
+ */
+public class DiffPluginImpl implements Differ {
+	
+	/**
+	 * Headers that are considered major enough to parse according to spec and
+	 * compare their constituents
+	 */
+	final static Set<String>				MAJOR_HEADERS	= new TreeSet<String>(
+																	String.CASE_INSENSITIVE_ORDER);
+
+	/**
+	 * Headers that are considered not major enough to be considered
+	 */
+	final static Set<String>				IGNORE_HEADERS	= new TreeSet<String>(
+																	String.CASE_INSENSITIVE_ORDER);
+
+	static {
+		MAJOR_HEADERS.add(Constants.EXPORT_PACKAGE);
+		MAJOR_HEADERS.add(Constants.IMPORT_PACKAGE);
+		MAJOR_HEADERS.add(Constants.REQUIRE_BUNDLE);
+		MAJOR_HEADERS.add(Constants.FRAGMENT_HOST);
+		MAJOR_HEADERS.add(Constants.BUNDLE_SYMBOLICNAME);
+		MAJOR_HEADERS.add(Constants.BUNDLE_LICENSE);
+		MAJOR_HEADERS.add(Constants.BUNDLE_NATIVECODE);
+		MAJOR_HEADERS.add(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT);
+		MAJOR_HEADERS.add(Constants.DYNAMICIMPORT_PACKAGE);
+
+		IGNORE_HEADERS.add(Constants.TOOL);
+		IGNORE_HEADERS.add(Constants.BND_LASTMODIFIED);
+		IGNORE_HEADERS.add(Constants.CREATED_BY);
+	}
+
+	/**
+	 * 
+	 * @see aQute.bnd.service.diff.Differ#diff(aQute.lib.resource.Jar,
+	 *      aQute.lib.resource.Jar)
+	 */
+	public Tree tree(File newer) throws Exception {
+		Jar jnewer = new Jar(newer);
+		try {
+			return tree(jnewer);
+		} finally {
+			jnewer.close();
+		}
+	}
+
+	/**
+	 * 
+	 * @see aQute.bnd.service.diff.Differ#diff(aQute.lib.resource.Jar,
+	 *      aQute.lib.resource.Jar)
+	 */
+	public Tree tree(Jar newer) throws Exception {
+		Analyzer anewer = new Analyzer();
+		try {
+				anewer.setJar(newer);
+				return tree(anewer);
+		} finally {
+			anewer.setJar((Jar) null);
+			anewer.close();
+		}
+	}
+
+	public Tree tree(Analyzer newer) throws Exception {
+		return bundleElement(newer);
+	}
+
+	/**
+	 * Create an element representing a bundle from the Jar.
+	 * 
+	 * @param infos 
+	 * @param jar
+	 *            The Jar to be analyzed
+	 * @return the elements that should be compared
+	 * @throws Exception
+	 */
+	private Element bundleElement(Analyzer analyzer) throws Exception {
+		List<Element> result = new ArrayList<Element>();
+
+		Manifest manifest = analyzer.getJar().getManifest();
+
+		if (manifest != null) {
+			result.add(JavaElement.getAPI(analyzer));
+			result.add(manifestElement(manifest));
+		}
+		result.add(resourcesElement(analyzer.getJar()));
+		return new Element(Type.BUNDLE, analyzer.getJar().getName(), result, CHANGED, CHANGED,
+				null);
+	}
+
+	/**
+	 * Create an element representing all resources in the JAR
+	 * 
+	 * @param jar
+	 * @return
+	 * @throws Exception
+	 */
+	private Element resourcesElement(Jar jar) throws Exception {
+		List<Element> resources = new ArrayList<Element>();
+		for (Map.Entry<String, Resource> entry : jar.getResources().entrySet()) {
+
+			InputStream in = entry.getValue().openInputStream();
+			try {
+				Digester<SHA1> digester = SHA1.getDigester();
+				IO.copy(in, digester);
+				String value = Hex.toHexString(digester.digest().digest());
+				resources
+						.add(new Element(Type.RESOURCE, entry.getKey()+"="+value, null, CHANGED, CHANGED, null));
+			} finally {
+				in.close();
+			}
+		}
+		return new Element(Type.RESOURCES, "<resources>", resources, CHANGED, CHANGED, null);
+	}
+
+
+
+
+	/**
+	 * Create an element for each manifest header. There are
+	 * {@link #IGNORE_HEADERS} and {@link #MAJOR_HEADERS} that will be treated
+	 * differently.
+	 * 
+	 * @param manifest
+	 * @return
+	 */
+
+	private Element manifestElement(Manifest manifest) {
+		List<Element> result = new ArrayList<Element>();
+
+		for (Object key : manifest.getMainAttributes().keySet()) {
+			String header = key.toString();
+			String value = manifest.getMainAttributes().getValue(header);
+			if (IGNORE_HEADERS.contains(header))
+				continue;
+
+			if (MAJOR_HEADERS.contains(header)) {
+				Parameters clauses = OSGiHeader.parseHeader(value);
+				Collection<Element> clausesDef = new ArrayList<Element>();
+				for (Map.Entry<String, Attrs> clause : clauses.entrySet()) {
+					Collection<Element> parameterDef = new ArrayList<Element>();
+					for (Map.Entry<String, String> parameter : clause.getValue().entrySet()) {
+						parameterDef.add(new Element(Type.PARAMETER, parameter.getKey() + ":" + parameter
+								.getValue(), null, CHANGED, CHANGED, null));
+					}
+					clausesDef.add(new Element(Type.CLAUSE, clause.getKey(), parameterDef,
+							CHANGED, CHANGED, null));
+				}
+				result.add(new Element(Type.HEADER, header, clausesDef, CHANGED, CHANGED, null));
+			} else {
+				result.add(new Element(Type.HEADER, header +":"+ value, null,CHANGED, CHANGED, null));
+			}
+		}
+		return new Element(Type.MANIFEST, "<manifest>", result, CHANGED, CHANGED, null);
+	}
+
+	public Tree deserialize(Data data) throws Exception {
+		return new Element(data);
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/Element.java b/bundleplugin/src/main/java/aQute/bnd/differ/Element.java
new file mode 100644
index 0000000..602f95c
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/Element.java
@@ -0,0 +1,144 @@
+package aQute.bnd.differ;
+
+import java.util.*;
+
+import aQute.bnd.service.diff.*;
+
+/**
+ * An element can be compared to another element of the same type. Elements with
+ * the same name and same place in the hierarchy should have the same type. The
+ * idea is that for a certain resource type you create an element (Structured or
+ * Leaf). This process is done for the newer and older resource.
+ * <p>
+ * A Leaf type has a value, comparison is rather simple in this case.
+ * <p>
+ * A Structured type has named children. The comparison between the newer and
+ * older child elements is then done on their name. Two elements with the same
+ * name are then matched.
+ * <p>
+ * The classes are prepared for extension but so far it turned out to be
+ * unnecessary.
+ */
+
+class Element implements Comparable<Element>, Tree {
+	final static Element[]	EMPTY	= new Element[0];
+	final Type				type;
+	final String			name;
+	final Delta				add;
+	final Delta				remove;
+	final String			comment;
+	final Element[]			children;
+
+	Element(Type type, String name) {
+		this(type, name, null, Delta.MINOR, Delta.MAJOR, null);
+	}
+
+	Element(Type type, String name, Element... children) {
+		this(type, name, Arrays.asList(children), Delta.MINOR, Delta.MAJOR, null);
+	}
+
+	Element(Type type, String name, Collection<? extends Element> children, Delta add,
+			Delta remove, String comment) {
+		this.type = type;
+		this.name = name;
+		this.add = add;
+		this.remove = remove;
+		this.comment = comment;
+		if (children != null && children.size() > 0) {
+			this.children = children.toArray(new Element[children.size()]);
+			Arrays.sort(this.children);
+		} else
+			this.children = EMPTY;
+	}
+
+	public Element(Data data) {
+		this.name = data.name;
+		this.type = data.type;
+		this.comment = data.comment;
+		this.add = data.add;
+		this.remove = data.rem;
+		if (data.children == null)
+			children = EMPTY;
+		else {
+			this.children = new Element[data.children.length];
+			for (int i = 0; i < children.length; i++)
+				children[i] = new Element(data.children[i]);
+			Arrays.sort(this.children);
+		}
+	}
+	public Data serialize() {
+		Data data = new Data();
+		data.type = this.type;
+		data.name = this.name;
+		data.add = this.add;
+		data.rem = this.remove;
+		data.comment = this.comment;
+		if (children.length != 0) {
+			data.children = new Data[children.length];
+			for (int i = 0; i < children.length; i++) {
+				data.children[i] = children[i].serialize();
+			}
+		}
+		return data;
+	}
+
+	public Type getType() {
+		return type;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	String getComment() {
+		return comment;
+	}
+
+	public int compareTo(Element other) {
+		if (type == other.type)
+			return name.compareTo(other.name);
+		else
+			return type.compareTo(other.type);
+	}
+
+	public boolean equals(Object other) {
+		if (other == null || getClass() != other.getClass())
+			return false;
+
+		return compareTo((Element) other) == 0;
+	}
+
+	public int hashCode() {
+		return type.hashCode() ^ name.hashCode();
+	}
+
+	public Tree[] getChildren() {
+		return children;
+	}
+
+	public Delta ifAdded() {
+		return add;
+	}
+
+	public Delta ifRemoved() {
+		return remove;
+	}
+
+	public Diff diff(Tree older) {
+		return new DiffImpl(this, (Element) older);
+	}
+
+	public Element get(String name) {
+		for (Element e : children) {
+			if (e.name.equals(name))
+				return e;
+		}
+		return null;
+	}
+	
+	public String toString() {
+		return type + " " + name + " (" + add + "/" + remove + ")";
+	}
+
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/differ/JavaElement.java b/bundleplugin/src/main/java/aQute/bnd/differ/JavaElement.java
new file mode 100644
index 0000000..e9a77a5
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/differ/JavaElement.java
@@ -0,0 +1,655 @@
+package aQute.bnd.differ;
+
+import static aQute.bnd.service.diff.Delta.*;
+import static aQute.bnd.service.diff.Type.*;
+import static java.lang.reflect.Modifier.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.atomic.*;
+import java.util.jar.*;
+
+import aQute.bnd.annotation.*;
+import aQute.bnd.service.diff.*;
+import aQute.bnd.service.diff.Type;
+import aQute.lib.collections.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.JAVA;
+import aQute.lib.osgi.Clazz.MethodDef;
+import aQute.lib.osgi.Descriptors.PackageRef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.libg.generics.*;
+import aQute.libg.header.*;
+import aQute.libg.version.Version;
+
+/**
+ * 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.
+ * <p>
+ * Packages
+ * <ul>
+ * <li>MAJOR - Remove a public type
+ * <li>MINOR - Add a public class
+ * <li>MINOR - Add an interface
+ * <li>MINOR - Add a method to a class
+ * <li>MINOR - Add a method to a provider interface
+ * <li>MAJOR - Add a method to a consumer interface
+ * <li>MINOR - Add a field
+ * <li>MICRO - Add an annotation to a member
+ * <li>MINOR - Change the value of a constant
+ * <li>MICRO - -abstract
+ * <li>MICRO - -final
+ * <li>MICRO - -protected
+ * <li>MAJOR - +abstract
+ * <li>MAJOR - +final
+ * <li>MAJOR - +protected
+ * </ul>
+ * 
+ */
+
+class JavaElement {
+	final static EnumSet<Type>			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<PackageRef, Instructions>	providerMatcher	= Create.map();
+	final Set<TypeRef>					notAccessible	= Create.set();
+	final Map<Object, Element>			cache			= Create.map();
+	MultiMap<PackageRef, //
+	Element>							packages;
+	final MultiMap<TypeRef, //
+	Element>							covariant		= new MultiMap<TypeRef, Element>();
+	final Set<JAVA>						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<String, Attrs> 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<PackageRef, Attrs> 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<PackageRef, Element>();
+
+		for (Clazz c : analyzer.getClassspace().values()) {
+			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 {
+		List<Element> result = new ArrayList<Element>();
+
+		for (Map.Entry<PackageRef, List<Element>> entry : packages.entrySet()) {
+			List<Element> set = entry.getValue();
+			for (Iterator<Element> 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, "<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
+	 * @return
+	 * @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<Element> members = new HashSet<Element>();
+		final Set<MethodDef> methods = Create.set();
+		final Set<Clazz.FieldDef> fields = Create.set();
+		final MultiMap<Clazz.Def, Element> annotations = new MultiMap<Clazz.Def, Element>();
+
+		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
+					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));
+				}
+			}
+
+			/**
+			 * @param members
+			 * @param name
+			 * @param comment
+			 * @return
+			 */
+			Set<Element>	OBJECT	= Create.set();
+
+			public String inherit(final Set<Element> 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("<init>")
+											|| "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;
+					} else {
+						Element s = classElement(c);
+						for (Element child : s.children) {
+							if (INHERITED.contains(child.type) && !child.name.startsWith("<")) {
+								members.add(child);
+							}
+						}
+					}
+				}
+				return null;
+			}
+
+			@Override public void annotation(Annotation annotation) {
+				Collection<Element> properties = Create.set();
+				if (Deprecated.class.getName().equals(annotation.getName().getFQN())) {
+					if (memberEnd)
+						clazz.setDeprecated(true);
+					else
+						last.setDeprecated(true);
+					return;
+				}
+
+				for (String key : annotation.keySet()) {
+					StringBuilder sb = new StringBuilder();
+					sb.append(key);
+					sb.append('=');
+					toString(sb, annotation.get(key));
+
+					properties.add(new Element(Type.PROPERTY, sb.toString(), null, CHANGED,
+							CHANGED, null));
+				}
+
+				if (memberEnd) {
+					members.add(new Element(Type.ANNOTATED, annotation.getName().getFQN(),
+							properties, CHANGED, CHANGED, null));
+					if (ProviderType.class.getName().equals(annotation.getName().getFQN())) {
+						provider.set(true);
+					} else if (ConsumerType.class.getName().equals(annotation.getName().getFQN())) {
+						provider.set(false);
+					}
+				} else if (last != null)
+					annotations.add(last, new Element(Type.ANNOTATED,
+							annotation.getName().getFQN(), properties, CHANGED, CHANGED, null));
+			}
+
+			private void toString(StringBuilder sb, Object object) {
+
+				if (object.getClass().isArray()) {
+					sb.append('[');
+					int l = Array.getLength(object);
+					for (int i = 0; i < l; i++)
+						toString(sb, Array.get(object, i));
+					sb.append(']');
+				} else
+					sb.append(object);
+			}
+
+			@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.INTERFACE;
+			else
+				type = Type.ANNOTATION;
+		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 because the
+				// provider, which calls this contract must be
+				// aware of the removal
+
+				remove = MINOR;
+			}
+		} else {
+			// Adding a method to a class can never do any harm
+			add = MINOR;
+
+			// Removing it will likely hurt consumers
+			remove = MAJOR;
+		}
+
+		// Remove all synthetic methods, we need
+		// to treat them special for the covariant returns
+
+		Set<MethodDef> synthetic = Create.set();
+		for (Iterator<MethodDef> i = methods.iterator(); i.hasNext();) {
+			MethodDef m = i.next();
+			if (m.isSynthetic()) {
+				synthetic.add(m);
+				i.remove();
+			}
+		}
+
+		for (MethodDef m : methods) {
+			List<Element> children = annotations.get(m);
+			if (children == null)
+				children = new ArrayList<Element>();
+
+			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());
+
+			for (Iterator<MethodDef> 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());
+				}
+			}
+
+			Element member = new Element(Type.METHOD, m.getName() + toString(m.getPrototype()),
+					children, add, remove, null);
+
+			if (!members.add(member)) {
+				members.remove(member);
+				members.add(member);
+			}
+		}
+
+		/**
+		 * Repeat for the remaining synthetic methods
+		 */
+		for (MethodDef m : synthetic) {
+			List<Element> children = annotations.get(m);
+			if (children == null)
+				children = new ArrayList<Element>();
+			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());
+
+			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) {
+			List<Element> children = annotations.get(f);
+			if (children == null)
+				children = new ArrayList<Element>();
+
+			// 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<Element> elements, TypeRef type) 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;
+		}
+
+		List<Element> 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<Element> children, int access, 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);
+
+	}
+
+}
\ No newline at end of file