blob: 09c5f255d74d6fa6f085fba08244310b3e9d2350 [file] [log] [blame]
Stuart McCullochbb014372012-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 McCulloch39cc9ac2012-07-16 13:43:38 +00009import aQute.bnd.osgi.*;
10import aQute.bnd.osgi.Clazz.MethodDef;
11import aQute.bnd.osgi.Descriptors.TypeRef;
Stuart McCulloch6a046662012-07-19 13:11:20 +000012import aQute.bnd.version.*;
Stuart McCullochbb014372012-06-07 21:57:32 +000013import aQute.lib.collections.*;
Stuart McCullochbb014372012-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 McCullochd602fe12012-07-20 16:50:39 +000030 .compile("\\s*([^=\\s:]+)\\s*(?::\\s*(Boolean|Byte|Character|Short|Integer|Long|Float|Double|String)\\s*)?=(.*)");
Stuart McCullochbb014372012-06-07 21:57:32 +000031
Stuart McCulloch4b9de8e2012-07-22 00:19:13 +000032 public static final Version V1_0 = new Version("1.0.0"); // "1.1.0"
Stuart McCullochbb014372012-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 McCulloch2286f232012-06-15 13:27:53 +000051 MultiMap<String,String> methods = new MultiMap<String,String>();
Stuart McCullochbb014372012-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 McCulloch2286f232012-06-15 13:27:53 +000081 analyzer.error("Missing super class for DS annotations: " + extendsClass + " from "
82 + clazz.getClassName());
Stuart McCullochbb014372012-06-07 21:57:32 +000083 } else {
84 ec.parseClassFileWithCollector(this);
85 }
86 }
87 }
88 for (ReferenceDef rdef : component.references.values()) {
Stuart McCulloch2286f232012-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 McCullochbb014372012-06-07 21:57:32 +000092 }
93 return component;
94 }
95
96 /**
Stuart McCullochbb014372012-06-07 21:57:32 +000097 * @param analyzer
98 * @param rdef
99 */
Stuart McCulloch2286f232012-06-15 13:27:53 +0000100 protected String referredMethod(Analyzer analyzer, ReferenceDef rdef, String value, String... matches) {
Stuart McCullochbb014372012-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 McCulloch2286f232012-06-15 13:27:53 +0000118 if (rdef.service.equals(Clazz.objectDescriptorToFQN(type)) || type.equals("Ljava/util/Map;")
Stuart McCullochbb014372012-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
Stuart McCulloch55d4dfe2012-08-07 10:57:21 +0000132 @Override
Stuart McCullochbb014372012-06-07 21:57:32 +0000133 public void annotation(Annotation annotation) {
134 try {
135 java.lang.annotation.Annotation a = annotation.getAnnotation();
136 if (a instanceof Component)
137 doComponent((Component) a, annotation);
138 else if (a instanceof Activate)
139 doActivate();
140 else if (a instanceof Deactivate)
141 doDeactivate();
142 else if (a instanceof Modified)
143 doModified();
144 else if (a instanceof Reference)
145 doReference((Reference) a, annotation);
Stuart McCulloch2286f232012-06-15 13:27:53 +0000146 }
147 catch (Exception e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000148 e.printStackTrace();
149 analyzer.error("During generation of a component on class %s, exception %s", clazz, e);
150 }
151 }
152
153 /**
154 *
155 */
156 protected void doDeactivate() {
157 if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
158 analyzer.error(
159 "Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
160 clazz, method.getDescriptor());
161 else {
162 component.deactivate = method.getName();
163 }
164 }
165
166 /**
167 *
168 */
169 protected void doModified() {
170 if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
171 analyzer.error(
172 "Modified method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
173 clazz, method.getDescriptor());
174 else {
175 component.modified = method.getName();
176 }
177 }
178
179 /**
180 * @param annotation
181 * @throws Exception
182 */
183 protected void doReference(Reference reference, Annotation raw) throws Exception {
184 ReferenceDef def = new ReferenceDef();
185 def.name = reference.name();
186
187 if (def.name == null) {
188 Matcher m = BINDNAME.matcher(method.getName());
Stuart McCulloch2286f232012-06-15 13:27:53 +0000189 if (m.matches())
Stuart McCullochbb014372012-06-07 21:57:32 +0000190 def.name = m.group(2);
191 else
192 analyzer.error("Invalid name for bind method %s", method.getName());
193 }
194
195 def.unbind = reference.unbind();
196 def.updated = reference.updated();
197 def.bind = method.getName();
198
199 def.service = raw.get("service");
200 if (def.service != null) {
201 def.service = Clazz.objectDescriptorToFQN(def.service);
202 } else {
203 // We have to find the type of the current method to
204 // link it to the referenced service.
205 Matcher m = BINDDESCRIPTOR.matcher(method.getDescriptor().toString());
206 if (m.matches()) {
207 def.service = Descriptors.binaryToFQN(m.group(3));
208 } else
209 throw new IllegalArgumentException(
210 "Cannot detect the type of a Component Reference from the descriptor: "
211 + method.getDescriptor());
212 }
213
214 // Check if we have a target, this must be a filter
215 def.target = reference.target();
216
217 if (component.references.containsKey(def.name))
218 analyzer.error(
219 "In component %s, multiple references with the same name: %s. Previous def: %s, this def: %s",
220 component.implementation, component.references.get(def.name), def.service, "");
221 else
222 component.references.put(def.name, def);
223
224 def.cardinality = reference.cardinality();
225 def.policy = reference.policy();
226 def.policyOption = reference.policyOption();
227 }
228
229 /**
230 *
231 */
232 protected void doActivate() {
233 if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches())
234 analyzer.error(
235 "Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
236 clazz, method.getDescriptor());
237 else {
238 component.activate = method.getName();
239 }
240 }
241
242 /**
243 * @param annotation
244 * @throws Exception
245 */
246 protected void doComponent(Component comp, Annotation annotation) throws Exception {
247
248 // Check if we are doing a super class
249 if (component.implementation != null)
250 return;
251
Stuart McCulloch4b9de8e2012-07-22 00:19:13 +0000252 component.version = V1_0;
Stuart McCullochbb014372012-06-07 21:57:32 +0000253 component.implementation = clazz.getClassName();
254 component.name = comp.name();
255 component.factory = comp.factory();
256 component.configurationPolicy = comp.configurationPolicy();
257 if (annotation.get("enabled") != null)
258 component.enabled = comp.enabled();
259 if (annotation.get("factory") != null)
260 component.factory = comp.factory();
261 if (annotation.get("immediate") != null)
262 component.immediate = comp.immediate();
263 if (annotation.get("servicefactory") != null)
264 component.servicefactory = comp.servicefactory();
265
266 if (annotation.get("configurationPid") != null)
267 component.configurationPid = comp.configurationPid();
268
269 if (annotation.get("xmlns") != null)
270 component.xmlns = comp.xmlns();
271
272 String properties[] = comp.properties();
273 if (properties != null)
274 for (String entry : properties)
275 component.properties.add(entry);
276
277 doProperties(comp.property());
278 Object[] x = annotation.get("service");
279
280 if (x == null) {
281 // Use the found interfaces, but convert from internal to
282 // fqn.
283 if (interfaces != null) {
284 List<TypeRef> result = new ArrayList<TypeRef>();
285 for (int i = 0; i < interfaces.length; i++) {
286 if (!interfaces[i].equals(analyzer.getTypeRef("scala/ScalaObject")))
287 result.add(interfaces[i]);
288 }
289 component.service = result.toArray(EMPTY);
290 }
291 } else {
292 // We have explicit interfaces set
293 component.service = new TypeRef[x.length];
294 for (int i = 0; i < x.length; i++) {
295 String s = (String) x[i];
296 TypeRef ref = analyzer.getTypeRefFromFQN(s);
297 component.service[i] = ref;
298 }
299 }
300
301 }
302
303 /**
304 * Parse the properties
305 */
306
307 private void doProperties(String[] properties) {
308 if (properties != null) {
309 for (String p : properties) {
310 Matcher m = PROPERTY_PATTERN.matcher(p);
311
312 if (m.matches()) {
313 String key = m.group(1);
Stuart McCulloch6a046662012-07-19 13:11:20 +0000314 String type = m.group(2);
315 if ( type != null)
316 key += ":" + type;
317
318 String value = m.group(3);
Stuart McCullochbb014372012-06-07 21:57:32 +0000319 component.property.add(key, value);
320 } else
Stuart McCulloch2286f232012-06-15 13:27:53 +0000321 throw new IllegalArgumentException("Malformed property '" + p + "' on component: " + className);
Stuart McCullochbb014372012-06-07 21:57:32 +0000322 }
323 }
324 }
325
326 /**
327 * Are called during class parsing
328 */
329
Stuart McCulloch2286f232012-06-15 13:27:53 +0000330 @Override
331 public void classBegin(int access, TypeRef name) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000332 className = name;
333 }
334
Stuart McCulloch2286f232012-06-15 13:27:53 +0000335 @Override
336 public void implementsInterfaces(TypeRef[] interfaces) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000337 this.interfaces = interfaces;
338 }
339
Stuart McCulloch2286f232012-06-15 13:27:53 +0000340 @Override
341 public void method(Clazz.MethodDef method) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000342 int access = method.getAccess();
343
344 if (Modifier.isAbstract(access) || Modifier.isStatic(access))
345 return;
346
347 if (!baseclass && Modifier.isPrivate(access))
348 return;
349
350 this.method = method;
351 methods.add(method.getName(), method.getDescriptor().toString());
352 }
353
Stuart McCulloch2286f232012-06-15 13:27:53 +0000354 @Override
355 public void extendsClass(TypeRef name) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000356 this.extendsClass = name;
357 }
358
359}