blob: a3763fa6566cdf83f2419a17eac32c2a7e9c9db7 [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
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;
Stuart McCulloch4482c702012-06-15 13:27:53 +000050 MultiMap<String,String> methods = new MultiMap<String,String>();
Stuart McCullochf3173222012-06-07 21:57:32 +000051 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) {
Stuart McCulloch4482c702012-06-15 13:27:53 +000080 analyzer.error("Missing super class for DS annotations: " + extendsClass + " from "
81 + clazz.getClassName());
Stuart McCullochf3173222012-06-07 21:57:32 +000082 } else {
83 ec.parseClassFileWithCollector(this);
84 }
85 }
86 }
87 for (ReferenceDef rdef : component.references.values()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +000088 rdef.unbind = referredMethod(analyzer, rdef, rdef.unbind, "add(.*)", "remove$1", "(.*)", "un$1");
89 rdef.updated = referredMethod(analyzer, rdef, rdef.updated, "(add|set|bind)(.*)", "updated$2", "(.*)",
90 "updated$1");
Stuart McCullochf3173222012-06-07 21:57:32 +000091 }
92 return component;
93 }
94
95 /**
Stuart McCullochf3173222012-06-07 21:57:32 +000096 * @param analyzer
97 * @param rdef
98 */
Stuart McCulloch4482c702012-06-15 13:27:53 +000099 protected String referredMethod(Analyzer analyzer, ReferenceDef rdef, String value, String... matches) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000100 if (value == null) {
101 String bind = rdef.bind;
102 for (int i = 0; i < matches.length; i += 2) {
103 Matcher m = Pattern.compile(matches[i]).matcher(bind);
104 if (m.matches()) {
105 value = m.replaceFirst(matches[i + 1]);
106 break;
107 }
108 }
109 } else if (value.equals("-"))
110 return null;
111
112 if (methods.containsKey(value)) {
113 for (String descriptor : methods.get(value)) {
114 Matcher matcher = BINDDESCRIPTOR.matcher(descriptor);
115 if (matcher.matches()) {
116 String type = matcher.group(2);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000117 if (rdef.service.equals(Clazz.objectDescriptorToFQN(type)) || type.equals("Ljava/util/Map;")
Stuart McCullochf3173222012-06-07 21:57:32 +0000118 || type.equals("Lorg/osgi/framework/ServiceReference;")) {
119
120 return value;
121 }
122 }
123 }
124 analyzer.error(
125 "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)",
126 rdef.bind, value, component.implementation, value, rdef.service);
127 }
128 return null;
129 }
130
131 public void annotation(Annotation annotation) {
132 try {
133 java.lang.annotation.Annotation a = annotation.getAnnotation();
134 if (a instanceof Component)
135 doComponent((Component) a, annotation);
136 else if (a instanceof Activate)
137 doActivate();
138 else if (a instanceof Deactivate)
139 doDeactivate();
140 else if (a instanceof Modified)
141 doModified();
142 else if (a instanceof Reference)
143 doReference((Reference) a, annotation);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000144 }
145 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000146 e.printStackTrace();
147 analyzer.error("During generation of a component on class %s, exception %s", clazz, e);
148 }
149 }
150
151 /**
152 *
153 */
154 protected void doDeactivate() {
155 if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
156 analyzer.error(
157 "Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
158 clazz, method.getDescriptor());
159 else {
160 component.deactivate = method.getName();
161 }
162 }
163
164 /**
165 *
166 */
167 protected void doModified() {
168 if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
169 analyzer.error(
170 "Modified method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
171 clazz, method.getDescriptor());
172 else {
173 component.modified = method.getName();
174 }
175 }
176
177 /**
178 * @param annotation
179 * @throws Exception
180 */
181 protected void doReference(Reference reference, Annotation raw) throws Exception {
182 ReferenceDef def = new ReferenceDef();
183 def.name = reference.name();
184
185 if (def.name == null) {
186 Matcher m = BINDNAME.matcher(method.getName());
Stuart McCulloch4482c702012-06-15 13:27:53 +0000187 if (m.matches())
Stuart McCullochf3173222012-06-07 21:57:32 +0000188 def.name = m.group(2);
189 else
190 analyzer.error("Invalid name for bind method %s", method.getName());
191 }
192
193 def.unbind = reference.unbind();
194 def.updated = reference.updated();
195 def.bind = method.getName();
196
197 def.service = raw.get("service");
198 if (def.service != null) {
199 def.service = Clazz.objectDescriptorToFQN(def.service);
200 } else {
201 // We have to find the type of the current method to
202 // link it to the referenced service.
203 Matcher m = BINDDESCRIPTOR.matcher(method.getDescriptor().toString());
204 if (m.matches()) {
205 def.service = Descriptors.binaryToFQN(m.group(3));
206 } else
207 throw new IllegalArgumentException(
208 "Cannot detect the type of a Component Reference from the descriptor: "
209 + method.getDescriptor());
210 }
211
212 // Check if we have a target, this must be a filter
213 def.target = reference.target();
214
215 if (component.references.containsKey(def.name))
216 analyzer.error(
217 "In component %s, multiple references with the same name: %s. Previous def: %s, this def: %s",
218 component.implementation, component.references.get(def.name), def.service, "");
219 else
220 component.references.put(def.name, def);
221
222 def.cardinality = reference.cardinality();
223 def.policy = reference.policy();
224 def.policyOption = reference.policyOption();
225 }
226
227 /**
228 *
229 */
230 protected void doActivate() {
231 if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
232 analyzer.error(
233 "Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
234 clazz, method.getDescriptor());
235 else {
236 component.activate = method.getName();
237 }
238 }
239
240 /**
241 * @param annotation
242 * @throws Exception
243 */
244 protected void doComponent(Component comp, Annotation annotation) throws Exception {
245
246 // Check if we are doing a super class
247 if (component.implementation != null)
248 return;
249
250 component.version = V1_1;
251 component.implementation = clazz.getClassName();
252 component.name = comp.name();
253 component.factory = comp.factory();
254 component.configurationPolicy = comp.configurationPolicy();
255 if (annotation.get("enabled") != null)
256 component.enabled = comp.enabled();
257 if (annotation.get("factory") != null)
258 component.factory = comp.factory();
259 if (annotation.get("immediate") != null)
260 component.immediate = comp.immediate();
261 if (annotation.get("servicefactory") != null)
262 component.servicefactory = comp.servicefactory();
263
264 if (annotation.get("configurationPid") != null)
265 component.configurationPid = comp.configurationPid();
266
267 if (annotation.get("xmlns") != null)
268 component.xmlns = comp.xmlns();
269
270 String properties[] = comp.properties();
271 if (properties != null)
272 for (String entry : properties)
273 component.properties.add(entry);
274
275 doProperties(comp.property());
276 Object[] x = annotation.get("service");
277
278 if (x == null) {
279 // Use the found interfaces, but convert from internal to
280 // fqn.
281 if (interfaces != null) {
282 List<TypeRef> result = new ArrayList<TypeRef>();
283 for (int i = 0; i < interfaces.length; i++) {
284 if (!interfaces[i].equals(analyzer.getTypeRef("scala/ScalaObject")))
285 result.add(interfaces[i]);
286 }
287 component.service = result.toArray(EMPTY);
288 }
289 } else {
290 // We have explicit interfaces set
291 component.service = new TypeRef[x.length];
292 for (int i = 0; i < x.length; i++) {
293 String s = (String) x[i];
294 TypeRef ref = analyzer.getTypeRefFromFQN(s);
295 component.service[i] = ref;
296 }
297 }
298
299 }
300
301 /**
302 * Parse the properties
303 */
304
305 private void doProperties(String[] properties) {
306 if (properties != null) {
307 for (String p : properties) {
308 Matcher m = PROPERTY_PATTERN.matcher(p);
309
310 if (m.matches()) {
311 String key = m.group(1);
Stuart McCullochcd1ddd72012-07-19 13:11:20 +0000312 String type = m.group(2);
313 if ( type != null)
314 key += ":" + type;
315
316 String value = m.group(3);
Stuart McCullochf3173222012-06-07 21:57:32 +0000317 component.property.add(key, value);
318 } else
Stuart McCulloch4482c702012-06-15 13:27:53 +0000319 throw new IllegalArgumentException("Malformed property '" + p + "' on component: " + className);
Stuart McCullochf3173222012-06-07 21:57:32 +0000320 }
321 }
322 }
323
324 /**
325 * Are called during class parsing
326 */
327
Stuart McCulloch4482c702012-06-15 13:27:53 +0000328 @Override
329 public void classBegin(int access, TypeRef name) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000330 className = name;
331 }
332
Stuart McCulloch4482c702012-06-15 13:27:53 +0000333 @Override
334 public void implementsInterfaces(TypeRef[] interfaces) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000335 this.interfaces = interfaces;
336 }
337
Stuart McCulloch4482c702012-06-15 13:27:53 +0000338 @Override
339 public void method(Clazz.MethodDef method) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000340 int access = method.getAccess();
341
342 if (Modifier.isAbstract(access) || Modifier.isStatic(access))
343 return;
344
345 if (!baseclass && Modifier.isPrivate(access))
346 return;
347
348 this.method = method;
349 methods.add(method.getName(), method.getDescriptor().toString());
350 }
351
Stuart McCulloch4482c702012-06-15 13:27:53 +0000352 @Override
353 public void extendsClass(TypeRef name) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000354 this.extendsClass = name;
355 }
356
357}