blob: a452c9fb3f3a5d5a618f887e99eb5a984bc27df4 [file] [log] [blame]
package aQute.bnd.component;
import java.lang.reflect.*;
import java.util.*;
import java.util.regex.*;
import org.osgi.service.component.annotations.*;
import aQute.bnd.osgi.*;
import aQute.bnd.osgi.Clazz.MethodDef;
import aQute.bnd.osgi.Descriptors.TypeRef;
import aQute.bnd.version.*;
import aQute.lib.collections.*;
/**
* 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("\\s*([^=\\s:]+)\\s*(?::\\s*(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 type = m.group(2);
if ( type != null)
key += ":" + type;
String value = m.group(3);
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;
}
}