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/component/AnnotationReader.java b/bundleplugin/src/main/java/aQute/bnd/component/AnnotationReader.java
new file mode 100644
index 0000000..35cec3f
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/AnnotationReader.java
@@ -0,0 +1,353 @@
+package aQute.bnd.component;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+
+import org.osgi.service.component.annotations.*;
+
+import aQute.lib.collections.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Clazz.MethodDef;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.libg.version.*;
+
+/**
+ * fixup any unbind methods To declare no unbind method, the value "-" must be
+ * used. If not specified, the name of the unbind method is derived from the
+ * name of the annotated bind method. If the annotated method name begins with
+ * set, that is replaced with unset to derive the unbind method name. If the
+ * annotated method name begins with add, that is replaced with remove to derive
+ * the unbind method name. Otherwise, un is prefixed to the annotated method
+ * name to derive the unbind method name.
+ * 
+ * @return
+ * @throws Exception
+ */
+public class AnnotationReader extends ClassDataCollector {
+	final static TypeRef[]		EMPTY					= new TypeRef[0];
+	final static Pattern		PROPERTY_PATTERN		= Pattern
+																.compile("([^=]+(:(Boolean|Byte|Char|Short|Integer|Long|Float|Double|String))?)\\s*=(.*)");
+
+	public static final Version	V1_1					= new Version("1.1.0");																												// "1.1.0"
+	public static final Version	V1_2					= new Version("1.2.0");																												// "1.1.0"
+	static Pattern				BINDNAME				= Pattern.compile("(set|add|bind)?(.*)");
+	static Pattern				BINDDESCRIPTOR			= Pattern
+																.compile("\\(((L([^;]+);)(Ljava/util/Map;)?|Lorg/osgi/framework/ServiceReference;)\\)V");
+
+	static Pattern				LIFECYCLEDESCRIPTOR		= Pattern
+																.compile("\\(((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;))*\\)V");
+	static Pattern				REFERENCEBINDDESCRIPTOR	= Pattern
+																.compile("\\(Lorg/osgi/framework/ServiceReference;\\)V");
+
+	ComponentDef				component				= new ComponentDef();
+
+	Clazz						clazz;
+	TypeRef						interfaces[];
+	MethodDef					method;
+	TypeRef						className;
+	Analyzer					analyzer;
+	MultiMap<String, String>	methods					= new MultiMap<String, String>();
+	TypeRef						extendsClass;
+	boolean						inherit;
+	boolean						baseclass				= true;
+
+	AnnotationReader(Analyzer analyzer, Clazz clazz, boolean inherit) {
+		this.analyzer = analyzer;
+		this.clazz = clazz;
+		this.inherit = inherit;
+	}
+
+	public static ComponentDef getDefinition(Clazz c, Analyzer analyzer) throws Exception {
+		boolean inherit = Processor.isTrue(analyzer.getProperty("-dsannotations-inherit"));
+		AnnotationReader r = new AnnotationReader(analyzer, c, inherit);
+		return r.getDef();
+	}
+
+	private ComponentDef getDef() throws Exception {
+		clazz.parseClassFileWithCollector(this);
+		if (component.implementation == null)
+			return null;
+
+		if (inherit) {
+			baseclass = false;
+			while (extendsClass != null) {
+				if (extendsClass.isJava())
+					break;
+
+				Clazz ec = analyzer.findClass(extendsClass);
+				if (ec == null) {
+					analyzer.error("Missing super class for DS annotations: " + extendsClass
+							+ " from " + clazz.getClassName());
+				} else {
+					ec.parseClassFileWithCollector(this);
+				}
+			}
+		}
+		for (ReferenceDef rdef : component.references.values()) {
+			rdef.unbind = referredMethod(analyzer, rdef, rdef.unbind, "add(.*)", "remove$1",
+					"(.*)", "un$1");
+			rdef.updated = referredMethod(analyzer, rdef, rdef.updated, "(add|set|bind)(.*)",
+					"updated$2", "(.*)", "updated$1");
+		}
+		return component;
+	}
+
+	/**
+	 * 
+	 * @param analyzer
+	 * @param rdef
+	 */
+	protected String referredMethod(Analyzer analyzer, ReferenceDef rdef, String value,
+			String... matches) {
+		if (value == null) {
+			String bind = rdef.bind;
+			for (int i = 0; i < matches.length; i += 2) {
+				Matcher m = Pattern.compile(matches[i]).matcher(bind);
+				if (m.matches()) {
+					value = m.replaceFirst(matches[i + 1]);
+					break;
+				}
+			}
+		} else if (value.equals("-"))
+			return null;
+
+		if (methods.containsKey(value)) {
+			for (String descriptor : methods.get(value)) {
+				Matcher matcher = BINDDESCRIPTOR.matcher(descriptor);
+				if (matcher.matches()) {
+					String type = matcher.group(2);
+					if (rdef.service.equals(Clazz.objectDescriptorToFQN(type))
+							|| type.equals("Ljava/util/Map;")
+							|| type.equals("Lorg/osgi/framework/ServiceReference;")) {
+
+						return value;
+					}
+				}
+			}
+			analyzer.error(
+					"A related method to %s from the reference %s has no proper prototype for class %s. Expected void %s(%s s [,Map m] | ServiceReference r)",
+					rdef.bind, value, component.implementation, value, rdef.service);
+		}
+		return null;
+	}
+
+	public void annotation(Annotation annotation) {
+		try {
+			java.lang.annotation.Annotation a = annotation.getAnnotation();
+			if (a instanceof Component)
+				doComponent((Component) a, annotation);
+			else if (a instanceof Activate)
+				doActivate();
+			else if (a instanceof Deactivate)
+				doDeactivate();
+			else if (a instanceof Modified)
+				doModified();
+			else if (a instanceof Reference)
+				doReference((Reference) a, annotation);
+		} catch (Exception e) {
+			e.printStackTrace();
+			analyzer.error("During generation of a component on class %s, exception %s", clazz, e);
+		}
+	}
+
+	/**
+	 * 
+	 */
+	protected void doDeactivate() {
+		if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
+			analyzer.error(
+					"Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+					clazz, method.getDescriptor());
+		else {
+			component.deactivate = method.getName();
+		}
+	}
+
+	/**
+	 * 
+	 */
+	protected void doModified() {
+		if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
+			analyzer.error(
+					"Modified method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+					clazz, method.getDescriptor());
+		else {
+			component.modified = method.getName();
+		}
+	}
+
+	/**
+	 * @param annotation
+	 * @throws Exception
+	 */
+	protected void doReference(Reference reference, Annotation raw) throws Exception {
+		ReferenceDef def = new ReferenceDef();
+		def.name = reference.name();
+
+		if (def.name == null) {
+			Matcher m = BINDNAME.matcher(method.getName());
+			if ( m.matches() )
+				def.name = m.group(2);
+			else
+				analyzer.error("Invalid name for bind method %s", method.getName());
+		}
+
+		def.unbind = reference.unbind();
+		def.updated = reference.updated();
+		def.bind = method.getName();
+
+		def.service = raw.get("service");
+		if (def.service != null) {
+			def.service = Clazz.objectDescriptorToFQN(def.service);
+		} else {
+			// We have to find the type of the current method to
+			// link it to the referenced service.
+			Matcher m = BINDDESCRIPTOR.matcher(method.getDescriptor().toString());
+			if (m.matches()) {
+				def.service = Descriptors.binaryToFQN(m.group(3));
+			} else
+				throw new IllegalArgumentException(
+						"Cannot detect the type of a Component Reference from the descriptor: "
+								+ method.getDescriptor());
+		}
+
+		// Check if we have a target, this must be a filter
+		def.target = reference.target();
+
+		if (component.references.containsKey(def.name))
+			analyzer.error(
+					"In component %s, multiple references with the same name: %s. Previous def: %s, this def: %s",
+					component.implementation, component.references.get(def.name), def.service, "");
+		else
+			component.references.put(def.name, def);
+
+		def.cardinality = reference.cardinality();
+		def.policy = reference.policy();
+		def.policyOption = reference.policyOption();
+	}
+
+	/**
+	 * 
+	 */
+	protected void doActivate() {
+		if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
+			analyzer.error(
+					"Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
+					clazz, method.getDescriptor());
+		else {
+			component.activate = method.getName();
+		}
+	}
+
+	/**
+	 * @param annotation
+	 * @throws Exception
+	 */
+	protected void doComponent(Component comp, Annotation annotation) throws Exception {
+
+		// Check if we are doing a super class
+		if (component.implementation != null)
+			return;
+
+		component.version = V1_1;
+		component.implementation = clazz.getClassName();
+		component.name = comp.name();
+		component.factory = comp.factory();
+		component.configurationPolicy = comp.configurationPolicy();
+		if (annotation.get("enabled") != null)
+			component.enabled = comp.enabled();
+		if (annotation.get("factory") != null)
+			component.factory = comp.factory();
+		if (annotation.get("immediate") != null)
+			component.immediate = comp.immediate();
+		if (annotation.get("servicefactory") != null)
+			component.servicefactory = comp.servicefactory();
+
+		if (annotation.get("configurationPid") != null)
+			component.configurationPid = comp.configurationPid();
+
+		if (annotation.get("xmlns") != null)
+			component.xmlns = comp.xmlns();
+
+		String properties[] = comp.properties();
+		if (properties != null)
+			for (String entry : properties)
+				component.properties.add(entry);
+
+		doProperties(comp.property());
+		Object[] x = annotation.get("service");
+
+		if (x == null) {
+			// Use the found interfaces, but convert from internal to
+			// fqn.
+			if (interfaces != null) {
+				List<TypeRef> result = new ArrayList<TypeRef>();
+				for (int i = 0; i < interfaces.length; i++) {
+					if (!interfaces[i].equals(analyzer.getTypeRef("scala/ScalaObject")))
+						result.add(interfaces[i]);
+				}
+				component.service = result.toArray(EMPTY);
+			}
+		} else {
+			// We have explicit interfaces set
+			component.service = new TypeRef[x.length];
+			for (int i = 0; i < x.length; i++) {
+				String s = (String) x[i];
+				TypeRef ref = analyzer.getTypeRefFromFQN(s);
+				component.service[i] = ref;
+			}
+		}
+
+	}
+
+	/**
+	 * Parse the properties
+	 */
+
+	private void doProperties(String[] properties) {
+		if (properties != null) {
+			for (String p : properties) {
+				Matcher m = PROPERTY_PATTERN.matcher(p);
+
+				if (m.matches()) {
+					String key = m.group(1);
+					String value = m.group(4);
+					component.property.add(key, value);
+				} else
+					throw new IllegalArgumentException("Malformed property '" + p
+							+ "' on component: " + className);
+			}
+		}
+	}
+
+	/**
+	 * Are called during class parsing
+	 */
+
+	@Override public void classBegin(int access, TypeRef name) {
+		className = name;
+	}
+
+	@Override public void implementsInterfaces(TypeRef[] interfaces) {
+		this.interfaces = interfaces;
+	}
+
+	@Override public void method(Clazz.MethodDef method) {
+		int access = method.getAccess();
+
+		if (Modifier.isAbstract(access) || Modifier.isStatic(access))
+			return;
+
+		if (!baseclass && Modifier.isPrivate(access))
+			return;
+
+		this.method = method;
+		methods.add(method.getName(), method.getDescriptor().toString());
+	}
+
+	@Override public void extendsClass(TypeRef name) {
+		this.extendsClass = name;
+	}
+
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java b/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java
new file mode 100644
index 0000000..75b7b73
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/ComponentDef.java
@@ -0,0 +1,208 @@
+package aQute.bnd.component;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.osgi.service.component.annotations.*;
+
+import aQute.lib.collections.*;
+import aQute.lib.osgi.*;
+import aQute.lib.osgi.Descriptors.TypeRef;
+import aQute.lib.tag.*;
+import aQute.libg.version.*;
+
+/**
+ * This class just holds the information for the component, implementation, and
+ * service/provide elements. The {@link #prepare(Analyzer)} method will check if
+ * things are ok and the {@link #getTag()} method returns a tag if the prepare
+ * method returns without any errors. The class uses {@link ReferenceDef} to
+ * hold the references.
+ */
+class ComponentDef {
+	final static String				NAMESPACE_STEM	= "http://www.osgi.org/xmlns/scr";
+	final List<String>				properties		= new ArrayList<String>();
+	final MultiMap<String, String>	property		= new MultiMap<String, String>();
+	final Map<String, ReferenceDef>	references		= new TreeMap<String, ReferenceDef>();
+
+	Version							version			= AnnotationReader.V1_1;
+	String							name;
+	String							factory;
+	Boolean							immediate;
+	Boolean							servicefactory;
+	ConfigurationPolicy				configurationPolicy;
+	TypeRef							implementation;
+	TypeRef							service[];
+	String							activate;
+	String							deactivate;
+	String							modified;
+	Boolean							enabled;
+	String							xmlns;
+	String							configurationPid;
+	List<Tag>						propertyTags	= new ArrayList<Tag>();
+
+	/**
+	 * Called to prepare. If will look for any errors or inconsistencies in the
+	 * setup.
+	 * 
+	 * @param analyzer
+	 *            the analyzer to report errors and create references
+	 * @throws Exception
+	 */
+	void prepare(Analyzer analyzer) throws Exception {
+
+		for (ReferenceDef ref : references.values()) {
+			ref.prepare(analyzer);
+			if (ref.version.compareTo(version) > 0)
+				version = ref.version;
+		}
+
+		if (implementation == null) {
+			analyzer.error("No Implementation defined for component " + name);
+			return;
+		}
+
+		analyzer.referTo(implementation);
+
+		if (name == null)
+			name = implementation.getFQN();
+
+		if (service != null && service.length > 0) {
+			for (TypeRef interfaceName : service)
+				analyzer.referTo(interfaceName);
+		} else if (servicefactory != null && servicefactory)
+			analyzer.warning("The servicefactory:=true directive is set but no service is provided, ignoring it");
+
+		if (configurationPid != null)
+			version = ReferenceDef.max(version, AnnotationReader.V1_2);
+
+		for (Map.Entry<String, List<String>> kvs : property.entrySet()) {
+			Tag property = new Tag("property");
+			String name = kvs.getKey();
+			String type = null;
+			int n = name.indexOf(':');
+			if (n > 0) {
+				type = name.substring(n + 1);
+				name = name.substring(0, n);
+			}
+
+			property.addAttribute("name", name);
+			if (type != null) {
+				property.addAttribute("type", type);
+			}
+			if (kvs.getValue().size() == 1) {
+				String value = kvs.getValue().get(0);
+				value = check(type, value, analyzer);
+				property.addAttribute("value", value);
+			} else {
+				StringBuilder sb = new StringBuilder();
+
+				String del = "";
+				for (String v : kvs.getValue()) {
+					sb.append(del);
+					v = check(type, v, analyzer);
+					sb.append(v);
+					del = "\n";
+				}
+				property.addContent(sb.toString());
+			}
+			propertyTags.add(property);
+		}
+	}
+
+	/**
+	 * Returns a tag describing the component element.
+	 * 
+	 * @return a component element
+	 */
+	Tag getTag() {
+		Tag component = new Tag("scr:component");
+		if (xmlns != null)
+			component.addAttribute("xmlns:scr", xmlns);
+		else
+			component.addAttribute("xmlns:scr", NAMESPACE_STEM + "/v" + version);
+
+		component.addAttribute("name", name);
+
+		if (servicefactory != null)
+			component.addAttribute("servicefactory", servicefactory);
+
+		if (configurationPolicy != null)
+			component.addAttribute("configuration-policy", configurationPolicy.toString()
+					.toLowerCase());
+
+		if (enabled != null)
+			component.addAttribute("enabled", enabled);
+
+		if (immediate != null)
+			component.addAttribute("immediate", immediate);
+
+		if (factory != null)
+			component.addAttribute("factory", factory);
+
+		if (activate != null)
+			component.addAttribute("activate", activate);
+
+		if (deactivate != null)
+			component.addAttribute("deactivate", deactivate);
+
+		if (modified != null)
+			component.addAttribute("modified", modified);
+
+		if (configurationPid != null)
+			component.addAttribute("configuration-pid", configurationPid);
+
+		Tag impl = new Tag(component, "implementation");
+		impl.addAttribute("class", implementation.getFQN());
+
+		if (service != null && service.length != 0) {
+			Tag s = new Tag(component, "service");
+			if (servicefactory != null && servicefactory)
+				s.addAttribute("servicefactory", true);
+
+			for (TypeRef ss : service) {
+				Tag provide = new Tag(s, "provide");
+				provide.addAttribute("interface", ss.getFQN());
+			}
+		}
+
+		for (ReferenceDef ref : references.values()) {
+			Tag refTag = ref.getTag();
+			component.addContent(refTag);
+		}
+
+		for (Tag tag : propertyTags)
+			component.addContent(tag);
+
+		for (String entry : properties) {
+			Tag properties = new Tag(component, "properties");
+			properties.addAttribute("entry", entry);
+		}
+		return component;
+	}
+
+	private String check(String type, String v, Analyzer analyzer) {
+		if (type == null)
+			return v;
+
+		try {
+			Class<?> c = Class.forName("java.lang." + type);
+			if (c == String.class)
+				return v;
+
+			v = v.trim();
+			if (c == Character.class)
+				c = Integer.class;
+			Method m = c.getMethod("valueOf", String.class);
+			m.invoke(null, v);
+		} catch (ClassNotFoundException e) {
+			analyzer.error("Invalid data type %s", type);
+		} catch (NoSuchMethodException e) {
+			analyzer.error("Cannot convert data %s to type %s", v, type);
+		} catch (NumberFormatException e) {
+			analyzer.error("Not a valid number %s for %s, %s", v, type, e.getMessage());
+		} catch (Exception e) {
+			analyzer.error("Cannot convert data %s to type %s", v, type);
+		}
+		return v;
+	}
+}
\ No newline at end of file
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java b/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java
new file mode 100644
index 0000000..e451eb0
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/DSAnnotations.java
@@ -0,0 +1,52 @@
+package aQute.bnd.component;
+
+import java.util.*;
+
+import aQute.bnd.service.*;
+import aQute.lib.osgi.*;
+import aQute.libg.header.*;
+
+/**
+ * Analyze the class space for any classes that have an OSGi annotation for DS.
+ * 
+ */
+public class DSAnnotations implements AnalyzerPlugin {
+
+	public boolean analyzeJar(Analyzer analyzer) throws Exception {
+		Parameters header = OSGiHeader.parseHeader(analyzer
+				.getProperty("-dsannotations"));
+		if ( header.size()==0)
+			return false;
+		
+		Instructions instructions = new Instructions(header);
+		Set<Clazz> list = new HashSet<Clazz>(analyzer.getClassspace().values());
+		String sc = analyzer.getProperty(Constants.SERVICE_COMPONENT);
+		List<String> names = new ArrayList<String>();
+		if ( sc != null && sc.trim().length() > 0)
+			names.add(sc);
+		
+		for (Iterator<Clazz> i = list.iterator(); i.hasNext();) {
+			for (Instruction instruction : instructions.keySet()) {
+				Clazz c = i.next();
+
+				if (instruction.matches(c.getFQN())) {
+					if (instruction.isNegated())
+						i.remove();
+					else {
+						ComponentDef definition = AnnotationReader.getDefinition(c, analyzer);
+						if (definition != null) {
+							definition.prepare(analyzer);
+							String name = "OSGI-INF/" + definition.name + ".xml";
+							names.add(name);
+							analyzer.getJar().putResource(name,
+									new TagResource(definition.getTag()));
+						}
+					}
+				}
+			}
+		}
+		sc = Processor.append(names.toArray(new String[names.size()]));
+		analyzer.setProperty(Constants.SERVICE_COMPONENT, sc);
+		return false;
+	}
+}
diff --git a/bundleplugin/src/main/java/aQute/bnd/component/ReferenceDef.java b/bundleplugin/src/main/java/aQute/bnd/component/ReferenceDef.java
new file mode 100644
index 0000000..162bdb4
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/bnd/component/ReferenceDef.java
@@ -0,0 +1,95 @@
+package aQute.bnd.component;
+
+import org.osgi.service.component.annotations.*;
+
+import aQute.lib.osgi.*;
+import aQute.lib.tag.*;
+import aQute.libg.version.*;
+
+/**
+ * Holds the information in the reference element.
+ */
+
+class ReferenceDef {
+	Version					version = AnnotationReader.V1_1;
+	String					name;
+	String					service;
+	ReferenceCardinality	cardinality;
+	ReferencePolicy			policy;
+	ReferencePolicyOption	policyOption;
+	String					target;
+	String					bind;
+	String					unbind;
+	String					updated;
+
+	/**
+	 * Prepare the reference, will check for any errors.
+	 * 
+	 * @param analyzer the analyzer to report errors to.
+	 * @throws Exception 
+	 */
+	public void prepare(Analyzer analyzer) throws Exception {
+		if (name == null)
+			analyzer.error("No name for a reference");
+		
+		if ((updated != null && !updated.equals("-")) || policyOption!= null)
+			version = max(version, AnnotationReader.V1_2);
+
+		if (target != null) {
+			String error = Verifier.validateFilter(target);
+			if ( error != null)
+				analyzer.error("Invalid target filter %s for %s", target, name);
+		}
+
+		if ( service == null)
+			analyzer.error("No interface specified on %s", name);
+		
+	}
+
+	/**
+	 * Calculate the tag.
+	 * 
+	 * @return a tag for the reference element.
+	 */
+	public Tag getTag() {
+		Tag ref = new Tag("reference");
+		ref.addAttribute("name", name);
+		if (cardinality != null)
+			ref.addAttribute("cardinality", cardinality.toString());
+
+		if (policy != null)
+			ref.addAttribute("policy", policy.toString());
+
+		ref.addAttribute("interface", service);
+
+		if (target != null)
+			ref.addAttribute("target", target);
+
+		if (bind != null && !"-".equals(bind))
+			ref.addAttribute("bind", bind);
+
+		if (unbind != null && !"-".equals(unbind))
+			ref.addAttribute("unbind", unbind);
+
+		if (updated != null && !"-".equals(updated)) 
+			ref.addAttribute("updated", updated);
+
+		if ( policyOption != null)
+			ref.addAttribute("policy-option", policyOption.toString());
+		
+		return ref;
+	}
+
+	static <T extends Comparable<T>> T max(T a, T b) {
+		int n = a.compareTo(b);
+		if (n >= 0)
+			return a;
+		else
+			return b;
+	}
+
+	public String toString() {
+		return name;
+	}
+
+}