blob: a73e714ea8dd3233f87099c28b3a127d61d2f741 [file] [log] [blame]
Stuart McCulloch26e7a5a2011-10-17 10:31:43 +00001package aQute.bnd.make.component;
2
3import static aQute.bnd.make.component.ServiceComponent.*;
4
5import java.lang.reflect.*;
6import java.util.*;
7import java.util.regex.*;
8
9import aQute.bnd.annotation.component.*;
10import aQute.lib.osgi.*;
11import aQute.libg.reporter.*;
12
13public class ComponentAnnotationReader extends ClassDataCollector {
14 String EMPTY[] = new String[0];
15 private static final String V1_1 = "1.1.0"; // "1.1.0"
16 static Pattern BINDDESCRIPTOR = Pattern
17 .compile("\\(L([^;]*);(Ljava/util/Map;|Lorg/osgi/framework/ServiceReference;)*\\)V");
18 static Pattern BINDMETHOD = Pattern.compile("(set|bind|add)(.)(.*)");
19
20 static Pattern ACTIVATEDESCRIPTOR = Pattern
21 .compile("\\(((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;))*\\)V");
22 static Pattern OLDACTIVATEDESCRIPTOR = Pattern
23 .compile("\\(Lorg/osgi/service/component/ComponentContext;\\)V");
24 static Pattern OLDBINDDESCRIPTOR = Pattern.compile("\\(L([^;]*);\\)V");
25 static Pattern REFERENCEBINDDESCRIPTOR = Pattern
26 .compile("\\(Lorg/osgi/framework/ServiceReference;\\)V");
27
28 Reporter reporter = new Processor();
29 String method;
30 String methodDescriptor;
31 int methodAccess;
32 String className;
33 Clazz clazz;
34 String interfaces[];
35 Set<String> multiple = new HashSet<String>();
36 Set<String> optional = new HashSet<String>();
37 Set<String> dynamic = new HashSet<String>();
38
39 Map<String, String> map = new TreeMap<String, String>();
40 Set<String> descriptors = new HashSet<String>();
41 List<String> properties = new ArrayList<String>();
42 String version = null;
43
44 // TODO make patterns for descriptors
45
46 ComponentAnnotationReader(Clazz clazz) {
47 this.clazz = clazz;
48 }
49
50 public void setReporter(Reporter reporter) {
51 this.reporter = reporter;
52 }
53
54 public Reporter getReporter() {
55 return this.reporter;
56 }
57
58 public static Map<String, String> getDefinition(Clazz c) throws Exception {
59 return getDefinition(c, new Processor());
60 }
61
62 public static Map<String, String> getDefinition(Clazz c, Reporter reporter) throws Exception {
63 ComponentAnnotationReader r = new ComponentAnnotationReader(c);
64 r.setReporter(reporter);
65 c.parseClassFileWithCollector(r);
66 r.finish();
67 return r.map;
68 }
69
70 public void annotation(Annotation annotation) {
71
72 if (annotation.getName().equals(Component.RNAME)) {
73 set(COMPONENT_NAME, annotation.get(Component.NAME), "<>");
74 set(COMPONENT_FACTORY, annotation.get(Component.FACTORY), false);
75 setBoolean(COMPONENT_ENABLED, annotation.get(Component.ENABLED), true);
76 setBoolean(COMPONENT_IMMEDIATE, annotation.get(Component.IMMEDIATE), false);
77 setBoolean(COMPONENT_SERVICEFACTORY, annotation.get(Component.SERVICEFACTORY), false);
78
79 if (annotation.get(Component.DESIGNATE) != null) {
80 String configs = annotation.get(Component.DESIGNATE);
81 if (configs != null) {
82 set(COMPONENT_DESIGNATE, Clazz.objectDescriptorToFQN(configs), "");
83 }
84 }
85
86 if (annotation.get(Component.DESIGNATE_FACTORY) != null) {
87 String configs = annotation.get(Component.DESIGNATE_FACTORY);
88 if (configs != null) {
89 set(COMPONENT_DESIGNATEFACTORY, Clazz.objectDescriptorToFQN(configs), "");
90 }
91 }
92
93 setVersion((String) annotation.get(Component.VERSION));
94
95 String configurationPolicy = annotation.get(Component.CONFIGURATION_POLICY);
96 if (configurationPolicy != null)
97 set(COMPONENT_CONFIGURATION_POLICY, configurationPolicy.toLowerCase(), "");
98
99 doProperties(annotation);
100
101 Object[] provides = (Object[]) annotation.get(Component.PROVIDE);
102 String[] p;
103 if (provides == null) {
104 // Use the found interfaces, but convert from internal to
105 // fqn.
106 if (interfaces != null) {
107 List<String> result = new ArrayList<String>();
108 for (int i = 0; i < interfaces.length; i++) {
109 if (!interfaces[i].equals("scala/ScalaObject") )
110 result.add(Clazz.internalToFqn(interfaces[i]));
111 }
112 p = result.toArray(EMPTY);
113 } else
114 p = EMPTY;
115 } else {
116 // We have explicit interfaces set
117 p = new String[provides.length];
118 for (int i = 0; i < provides.length; i++) {
119 p[i] = descriptorToFQN(provides[i].toString());
120 }
121 }
122 if (p.length > 0) {
123 set(COMPONENT_PROVIDE, Processor.join(Arrays.asList(p)), "<>");
124 }
125
126 } else if (annotation.getName().equals(Activate.RNAME)) {
127 if (!checkMethod())
128 setVersion(V1_1);
129
130 if (!ACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches())
131 reporter.error(
132 "Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
133 className, methodDescriptor);
134
135 if (method.equals("activate")
136 && OLDACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches()) {
137 // this is the default!
138 } else {
139 setVersion(V1_1);
140 set(COMPONENT_ACTIVATE, method, "<>");
141 }
142
143 } else if (annotation.getName().equals(Deactivate.RNAME)) {
144 if (!checkMethod())
145 setVersion(V1_1);
146
147 if (!ACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches())
148 reporter.error(
149 "Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s",
150 className, methodDescriptor);
151 if (method.equals("deactivate")
152 && OLDACTIVATEDESCRIPTOR.matcher(methodDescriptor).matches()) {
153 // This is the default!
154 } else {
155 setVersion(V1_1);
156 set(COMPONENT_DEACTIVATE, method, "<>");
157 }
158 } else if (annotation.getName().equals(Modified.RNAME)) {
159 set(COMPONENT_MODIFIED, method, "<>");
160 setVersion(V1_1);
161 } else if (annotation.getName().equals(Reference.RNAME)) {
162
163 String name = (String) annotation.get(Reference.NAME);
164 String bind = method;
165 String unbind = null;
166
167 if (name == null) {
168 Matcher m = BINDMETHOD.matcher(method);
169 if (m.matches()) {
170 name = m.group(2).toLowerCase() + m.group(3);
171 } else {
172 name = method.toLowerCase();
173 }
174 }
175 String simpleName = name;
176
177 unbind = annotation.get(Reference.UNBIND);
178
179 if (bind != null) {
180 name = name + "/" + bind;
181 if (unbind != null)
182 name = name + "/" + unbind;
183 }
184 String service = annotation.get(Reference.SERVICE);
185
186 if (service != null) {
187 service = Clazz.objectDescriptorToFQN(service);
188 } else {
189 // We have to find the type of the current method to
190 // link it to the referenced service.
191 Matcher m = BINDDESCRIPTOR.matcher(methodDescriptor);
192 if (m.matches()) {
193 service = Clazz.internalToFqn(m.group(1));
194 } else
195 throw new IllegalArgumentException(
196 "Cannot detect the type of a Component Reference from the descriptor: "
197 + methodDescriptor);
198 }
199
200 // Check if we have a target, this must be a filter
201 String target = annotation.get(Reference.TARGET);
202 if (target != null) {
203 Verifier.verifyFilter(target, 0);
204 service = service + target;
205 }
206
207 Integer c = annotation.get(Reference.TYPE);
208 if (c != null && !c.equals(0) && !c.equals((int) '1')) {
209 service = service + (char) c.intValue();
210 }
211
212 if (map.containsKey(name))
213 reporter.error(
214 "In component %s, Multiple references with the same name: %s. Previous def: %s, this def: %s",
215 name, map.get(name), service, "");
216 map.put(name, service);
217
218 if (isTrue(annotation.get(Reference.MULTIPLE)))
219 multiple.add(simpleName);
220 if (isTrue(annotation.get(Reference.OPTIONAL)))
221 optional.add(simpleName);
222 if (isTrue(annotation.get(Reference.DYNAMIC)))
223 dynamic.add(simpleName);
224
225 if (!checkMethod())
226 setVersion(V1_1);
227 else if (REFERENCEBINDDESCRIPTOR.matcher(methodDescriptor).matches()
228 || !OLDBINDDESCRIPTOR.matcher(methodDescriptor).matches())
229 setVersion(V1_1);
230 }
231 }
232
233 private void setVersion(String v) {
234 if (v == null)
235 return;
236
237 if (version == null)
238 version = v;
239 else if (v.compareTo(version) > 0) // we're safe to 9.9.9
240 version = v;
241 }
242
243 private boolean checkMethod() {
244 return Modifier.isPublic(methodAccess) || Modifier.isProtected(methodAccess);
245 }
246
247 static Pattern PROPERTY_PATTERN = Pattern.compile("[^=]+=.+");
248
249 private void doProperties(Annotation annotation) {
250 Object[] properties = annotation.get(Component.PROPERTIES);
251
252 if (properties != null) {
253 for (Object o : properties) {
254 String p = (String) o;
255 if (PROPERTY_PATTERN.matcher(p).matches())
256 this.properties.add(p);
257 else
258 throw new IllegalArgumentException("Malformed property '" + p + "' on: "
259 + annotation.get(Component.NAME));
260 }
261 }
262 }
263
264 private boolean isTrue(Object object) {
265 if (object == null)
266 return false;
267 return (Boolean) object;
268 }
269
270 private void setBoolean(String string, Object object, boolean b) {
271 if (object == null)
272 object = b;
273
274 Boolean bb = (Boolean) object;
275 if (bb == b)
276 return;
277
278 map.put(string, bb.toString());
279 }
280
281 private void set(String string, Object object, Object deflt) {
282 if (object == null || object.equals(deflt))
283 return;
284
285 map.put(string, object.toString());
286 }
287
288 /**
289 * Skip L and ; and replace / for . in an object descriptor.
290 *
291 * A string like Lcom/acme/Foo; becomes com.acme.Foo
292 *
293 * @param string
294 * @return
295 */
296
297 private String descriptorToFQN(String string) {
298 StringBuilder sb = new StringBuilder();
299 for (int i = 1; i < string.length() - 1; i++) {
300 char c = string.charAt(i);
301 if (c == '/')
302 c = '.';
303 sb.append(c);
304 }
305 return sb.toString();
306 }
307
308 @Override public void classBegin(int access, String name) {
309 className = name;
310 }
311
312 @Override public void implementsInterfaces(String[] interfaces) {
313 this.interfaces = interfaces;
314 }
315
316 @Override public void method(int access, String name, String descriptor) {
317 this.method = name;
318 this.methodDescriptor = descriptor;
319 this.methodAccess = access;
320 descriptors.add(method);
321 }
322
323 void set(String name, Collection<String> l) {
324 if (l.size() == 0)
325 return;
326
327 set(name, Processor.join(l), "<>");
328 }
329
330 public void finish() {
331 set(COMPONENT_MULTIPLE, multiple);
332 set(COMPONENT_DYNAMIC, dynamic);
333 set(COMPONENT_OPTIONAL, optional);
334 set(COMPONENT_IMPLEMENTATION, clazz.getFQN(), "<>");
335 set(COMPONENT_PROPERTIES, properties);
336 if (version != null) {
337 set(COMPONENT_VERSION, version, "<>");
338 reporter.trace("Component %s is v1.1", map);
339 }
340 set(COMPONENT_DESCRIPTORS, descriptors);
341 }
342
343}