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