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