blob: 1ca3af7b69940d735f2cb400e5a8dedb22f04947 [file] [log] [blame]
Stuart McCullochf3173222012-06-07 21:57:32 +00001package aQute.bnd.component;
2
3import java.lang.reflect.*;
4import java.util.*;
5import java.util.regex.*;
6
7import org.osgi.service.component.annotations.*;
8
Stuart McCulloch42151ee2012-07-16 13:43:38 +00009import aQute.bnd.osgi.*;
10import aQute.bnd.osgi.Clazz.MethodDef;
11import aQute.bnd.osgi.Descriptors.TypeRef;
Stuart McCullochcd1ddd72012-07-19 13:11:20 +000012import aQute.bnd.version.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000013import aQute.lib.collections.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000014
15/**
16 * fixup any unbind methods To declare no unbind method, the value "-" must be
17 * used. If not specified, the name of the unbind method is derived from the
18 * name of the annotated bind method. If the annotated method name begins with
19 * set, that is replaced with unset to derive the unbind method name. If the
20 * annotated method name begins with add, that is replaced with remove to derive
21 * the unbind method name. Otherwise, un is prefixed to the annotated method
22 * name to derive the unbind method name.
23 *
24 * @return
25 * @throws Exception
26 */
27public class AnnotationReader extends ClassDataCollector {
28 final static TypeRef[] EMPTY = new TypeRef[0];
29 final static Pattern PROPERTY_PATTERN = Pattern
Stuart McCulloch528a65d2012-07-20 16:50:39 +000030 .compile("\\s*([^=\\s:]+)\\s*(?::\\s*(Boolean|Byte|Character|Short|Integer|Long|Float|Double|String)\\s*)?=(.*)");
Stuart McCullochf3173222012-06-07 21:57:32 +000031
Stuart McCulloch5515a832012-07-22 00:19:13 +000032 public static final Version V1_0 = new Version("1.0.0"); // "1.1.0"
Stuart McCullochf3173222012-06-07 21:57:32 +000033 public static final Version V1_1 = new Version("1.1.0"); // "1.1.0"
34 public static final Version V1_2 = new Version("1.2.0"); // "1.1.0"
35 static Pattern BINDNAME = Pattern.compile("(set|add|bind)?(.*)");
36 static Pattern BINDDESCRIPTOR = Pattern
37 .compile("\\(((L([^;]+);)(Ljava/util/Map;)?|Lorg/osgi/framework/ServiceReference;)\\)V");
38
39 static Pattern LIFECYCLEDESCRIPTOR = Pattern
40 .compile("\\(((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;))*\\)V");
41 static Pattern REFERENCEBINDDESCRIPTOR = Pattern
42 .compile("\\(Lorg/osgi/framework/ServiceReference;\\)V");
43
44 ComponentDef component = new ComponentDef();
45
46 Clazz clazz;
47 TypeRef interfaces[];
48 MethodDef method;
49 TypeRef className;
50 Analyzer analyzer;
Stuart McCulloch4482c702012-06-15 13:27:53 +000051 MultiMap<String,String> methods = new MultiMap<String,String>();
Stuart McCullochf3173222012-06-07 21:57:32 +000052 TypeRef extendsClass;
53 boolean inherit;
54 boolean baseclass = true;
55
56 AnnotationReader(Analyzer analyzer, Clazz clazz, boolean inherit) {
57 this.analyzer = analyzer;
58 this.clazz = clazz;
59 this.inherit = inherit;
60 }
61
62 public static ComponentDef getDefinition(Clazz c, Analyzer analyzer) throws Exception {
63 boolean inherit = Processor.isTrue(analyzer.getProperty("-dsannotations-inherit"));
64 AnnotationReader r = new AnnotationReader(analyzer, c, inherit);
65 return r.getDef();
66 }
67
68 private ComponentDef getDef() throws Exception {
69 clazz.parseClassFileWithCollector(this);
70 if (component.implementation == null)
71 return null;
72
73 if (inherit) {
74 baseclass = false;
75 while (extendsClass != null) {
76 if (extendsClass.isJava())
77 break;
78
79 Clazz ec = analyzer.findClass(extendsClass);
80 if (ec == null) {
Stuart McCulloch4482c702012-06-15 13:27:53 +000081 analyzer.error("Missing super class for DS annotations: " + extendsClass + " from "
82 + clazz.getClassName());
Stuart McCullochf3173222012-06-07 21:57:32 +000083 } else {
84 ec.parseClassFileWithCollector(this);
85 }
86 }
87 }
88 for (ReferenceDef rdef : component.references.values()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +000089 rdef.unbind = referredMethod(analyzer, rdef, rdef.unbind, "add(.*)", "remove$1", "(.*)", "un$1");
90 rdef.updated = referredMethod(analyzer, rdef, rdef.updated, "(add|set|bind)(.*)", "updated$2", "(.*)",
91 "updated$1");
Stuart McCullochf3173222012-06-07 21:57:32 +000092 }
93 return component;
94 }
95
96 /**
Stuart McCullochf3173222012-06-07 21:57:32 +000097 * @param analyzer
98 * @param rdef
99 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000100 protected String referredMethod(Analyzer analyzer, ReferenceDef rdef, String value, String... matches) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000101 if (value == null) {
102 String bind = rdef.bind;
103 for (int i = 0; i < matches.length; i += 2) {
104 Matcher m = Pattern.compile(matches[i]).matcher(bind);
105 if (m.matches()) {
106 value = m.replaceFirst(matches[i + 1]);
107 break;
108 }
109 }
110 } else if (value.equals("-"))
111 return null;
112
113 if (methods.containsKey(value)) {
114 for (String descriptor : methods.get(value)) {
115 Matcher matcher = BINDDESCRIPTOR.matcher(descriptor);
116 if (matcher.matches()) {
117 String type = matcher.group(2);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000118 if (rdef.service.equals(Clazz.objectDescriptorToFQN(type)) || type.equals("Ljava/util/Map;")
Stuart McCullochf3173222012-06-07 21:57:32 +0000119 || type.equals("Lorg/osgi/framework/ServiceReference;")) {
120
121 return value;
122 }
123 }
124 }
125 analyzer.error(
126 "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)",
127 rdef.bind, value, component.implementation, value, rdef.service);
128 }
129 return null;
130 }
131
132 public void annotation(Annotation annotation) {
133 try {
134 java.lang.annotation.Annotation a = annotation.getAnnotation();
135 if (a instanceof Component)
136 doComponent((Component) a, annotation);
137 else if (a instanceof Activate)
138 doActivate();
139 else if (a instanceof Deactivate)
140 doDeactivate();
141 else if (a instanceof Modified)
142 doModified();
143 else if (a instanceof Reference)
144 doReference((Reference) a, annotation);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000145 }
146 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000147 e.printStackTrace();
148 analyzer.error("During generation of a component on class %s, exception %s", clazz, e);
149 }
150 }
151
152 /**
153 *
154 */
155 protected void doDeactivate() {
156 if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
157 analyzer.error(
158 "Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
159 clazz, method.getDescriptor());
160 else {
161 component.deactivate = method.getName();
162 }
163 }
164
165 /**
166 *
167 */
168 protected void doModified() {
169 if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
170 analyzer.error(
171 "Modified method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
172 clazz, method.getDescriptor());
173 else {
174 component.modified = method.getName();
175 }
176 }
177
178 /**
179 * @param annotation
180 * @throws Exception
181 */
182 protected void doReference(Reference reference, Annotation raw) throws Exception {
183 ReferenceDef def = new ReferenceDef();
184 def.name = reference.name();
185
186 if (def.name == null) {
187 Matcher m = BINDNAME.matcher(method.getName());
Stuart McCulloch4482c702012-06-15 13:27:53 +0000188 if (m.matches())
Stuart McCullochf3173222012-06-07 21:57:32 +0000189 def.name = m.group(2);
190 else
191 analyzer.error("Invalid name for bind method %s", method.getName());
192 }
193
194 def.unbind = reference.unbind();
195 def.updated = reference.updated();
196 def.bind = method.getName();
197
198 def.service = raw.get("service");
199 if (def.service != null) {
200 def.service = Clazz.objectDescriptorToFQN(def.service);
201 } else {
202 // We have to find the type of the current method to
203 // link it to the referenced service.
204 Matcher m = BINDDESCRIPTOR.matcher(method.getDescriptor().toString());
205 if (m.matches()) {
206 def.service = Descriptors.binaryToFQN(m.group(3));
207 } else
208 throw new IllegalArgumentException(
209 "Cannot detect the type of a Component Reference from the descriptor: "
210 + method.getDescriptor());
211 }
212
213 // Check if we have a target, this must be a filter
214 def.target = reference.target();
215
216 if (component.references.containsKey(def.name))
217 analyzer.error(
218 "In component %s, multiple references with the same name: %s. Previous def: %s, this def: %s",
219 component.implementation, component.references.get(def.name), def.service, "");
220 else
221 component.references.put(def.name, def);
222
223 def.cardinality = reference.cardinality();
224 def.policy = reference.policy();
225 def.policyOption = reference.policyOption();
226 }
227
228 /**
229 *
230 */
231 protected void doActivate() {
232 if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
233 analyzer.error(
234 "Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
235 clazz, method.getDescriptor());
236 else {
237 component.activate = method.getName();
238 }
239 }
240
241 /**
242 * @param annotation
243 * @throws Exception
244 */
245 protected void doComponent(Component comp, Annotation annotation) throws Exception {
246
247 // Check if we are doing a super class
248 if (component.implementation != null)
249 return;
250
Stuart McCulloch5515a832012-07-22 00:19:13 +0000251 component.version = V1_0;
Stuart McCullochf3173222012-06-07 21:57:32 +0000252 component.implementation = clazz.getClassName();
253 component.name = comp.name();
254 component.factory = comp.factory();
255 component.configurationPolicy = comp.configurationPolicy();
256 if (annotation.get("enabled") != null)
257 component.enabled = comp.enabled();
258 if (annotation.get("factory") != null)
259 component.factory = comp.factory();
260 if (annotation.get("immediate") != null)
261 component.immediate = comp.immediate();
262 if (annotation.get("servicefactory") != null)
263 component.servicefactory = comp.servicefactory();
264
265 if (annotation.get("configurationPid") != null)
266 component.configurationPid = comp.configurationPid();
267
268 if (annotation.get("xmlns") != null)
269 component.xmlns = comp.xmlns();
270
271 String properties[] = comp.properties();
272 if (properties != null)
273 for (String entry : properties)
274 component.properties.add(entry);
275
276 doProperties(comp.property());
277 Object[] x = annotation.get("service");
278
279 if (x == null) {
280 // Use the found interfaces, but convert from internal to
281 // fqn.
282 if (interfaces != null) {
283 List<TypeRef> result = new ArrayList<TypeRef>();
284 for (int i = 0; i < interfaces.length; i++) {
285 if (!interfaces[i].equals(analyzer.getTypeRef("scala/ScalaObject")))
286 result.add(interfaces[i]);
287 }
288 component.service = result.toArray(EMPTY);
289 }
290 } else {
291 // We have explicit interfaces set
292 component.service = new TypeRef[x.length];
293 for (int i = 0; i < x.length; i++) {
294 String s = (String) x[i];
295 TypeRef ref = analyzer.getTypeRefFromFQN(s);
296 component.service[i] = ref;
297 }
298 }
299
300 }
301
302 /**
303 * Parse the properties
304 */
305
306 private void doProperties(String[] properties) {
307 if (properties != null) {
308 for (String p : properties) {
309 Matcher m = PROPERTY_PATTERN.matcher(p);
310
311 if (m.matches()) {
312 String key = m.group(1);
Stuart McCullochcd1ddd72012-07-19 13:11:20 +0000313 String type = m.group(2);
314 if ( type != null)
315 key += ":" + type;
316
317 String value = m.group(3);
Stuart McCullochf3173222012-06-07 21:57:32 +0000318 component.property.add(key, value);
319 } else
Stuart McCulloch4482c702012-06-15 13:27:53 +0000320 throw new IllegalArgumentException("Malformed property '" + p + "' on component: " + className);
Stuart McCullochf3173222012-06-07 21:57:32 +0000321 }
322 }
323 }
324
325 /**
326 * Are called during class parsing
327 */
328
Stuart McCulloch4482c702012-06-15 13:27:53 +0000329 @Override
330 public void classBegin(int access, TypeRef name) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000331 className = name;
332 }
333
Stuart McCulloch4482c702012-06-15 13:27:53 +0000334 @Override
335 public void implementsInterfaces(TypeRef[] interfaces) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000336 this.interfaces = interfaces;
337 }
338
Stuart McCulloch4482c702012-06-15 13:27:53 +0000339 @Override
340 public void method(Clazz.MethodDef method) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000341 int access = method.getAccess();
342
343 if (Modifier.isAbstract(access) || Modifier.isStatic(access))
344 return;
345
346 if (!baseclass && Modifier.isPrivate(access))
347 return;
348
349 this.method = method;
350 methods.add(method.getName(), method.getDescriptor().toString());
351 }
352
Stuart McCulloch4482c702012-06-15 13:27:53 +0000353 @Override
354 public void extendsClass(TypeRef name) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000355 this.extendsClass = name;
356 }
357
358}