blob: 94a65aea3c16c0f025baa1d2c48f50e0f0083c79 [file] [log] [blame]
Stuart McCulloch26e7a5a2011-10-17 10:31:43 +00001package aQute.bnd.make.metatype;
2
3import java.io.*;
4import java.util.*;
5import java.util.concurrent.*;
6import java.util.regex.*;
7
8import aQute.bnd.annotation.metatype.*;
9import aQute.lib.io.*;
10import aQute.lib.osgi.*;
11import aQute.lib.osgi.Clazz.MethodDef;
12import aQute.lib.tag.*;
13import aQute.libg.generics.*;
14
15public class MetaTypeReader extends ClassDataCollector implements Resource {
16 final Analyzer reporter;
17 Clazz clazz;
18 String interfaces[];
19 Tag metadata = new Tag("metatype:MetaData", new String[] {
20 "xmlns:metatype", "http://www.osgi.org/xmlns/metatype/v1.1.0" });
21 Tag ocd = new Tag(metadata, "OCD");
22 Tag designate = new Tag(metadata, "Designate");
23 Tag object = new Tag(designate, "Object");
24
25 // Resource
26 String extra;
27
28 // One time init
29 boolean finished;
30
31 // Designate
32 boolean override;
33 String designatePid;
34 boolean factory;
35
36 // AD
37 Map<MethodDef, Meta.AD> methods = new LinkedHashMap<MethodDef, Meta.AD>();
38
39 // OCD
40 Annotation ocdAnnotation;
41
42 MethodDef method;
43
44 public MetaTypeReader(Clazz clazz, Analyzer reporter) {
45 this.clazz = clazz;
46 this.reporter = reporter;
47 }
48
49 public void annotation(Annotation annotation) {
50 try {
51 Meta.OCD ocd = annotation.getAnnotation(Meta.OCD.class);
52 Meta.AD ad = annotation.getAnnotation(Meta.AD.class);
53 if (ocd != null) {
54 this.ocdAnnotation = annotation;
55 }
56 if (ad != null) {
57 assert method != null;
58 methods.put(method, ad);
59 }
60 } catch (Exception e) {
61 reporter.error("Error during annotation parsing %s : %s", clazz, e);
62 e.printStackTrace();
63 }
64 }
65
66 /**
67 * @param id
68 * @param name
69 * @param cardinality
70 * @param required
71 * @param deflt
72 * @param type
73 * @param max
74 * @param min
75 * @param optionLabels
76 * @param optionValues
77 */
78
79 static Pattern COLLECTION = Pattern
80 .compile("(.*(Collection|Set|List|Queue|Stack|Deque))<(L.+;)>");
81
82 private void addMethod(MethodDef method, Meta.AD ad) throws Exception {
83
84 // Set all the defaults.
85 String rtype = method.getReturnType();
86 String id = Configurable.mangleMethodName(method.name);
87 String name = Clazz.unCamel(id);
88
89 int cardinality = 0;
90
91 if (rtype.endsWith("[]")) {
92 cardinality = Integer.MAX_VALUE;
93 rtype = rtype.substring(0, rtype.length() - 2);
94 }
95 if (rtype.indexOf('<') > 0) {
96 if (cardinality != 0)
97 reporter.error(
98 "AD for %s.%s uses an array of collections in return type (%s), Metatype allows either Vector or array",
99 clazz.getFQN(), method.name, method.getReturnType());
100 Matcher m = COLLECTION.matcher(rtype);
101 if (m.matches()) {
102 rtype = Clazz.objectDescriptorToFQN(m.group(3));
103 cardinality = Integer.MIN_VALUE;
104 }
105 }
106
107 Meta.Type type = getType(rtype);
108
109 boolean required = ad ==null || ad.required();
110 String deflt = null;
111 String max = null;
112 String min = null;
113 String[] optionLabels = null;
114 String[] optionValues = null;
115 String description = null;
116
117 Clazz c = reporter.findClass(Clazz.fqnToPath(rtype));
118 if (c != null && c.isEnum()) {
119 optionValues = parseOptionValues(c);
120 }
121
122 // Now parse the annotation for any overrides
123
124 if (ad != null) {
125 if (ad.id() != null)
126 id = ad.id();
127 if (ad.name() != null)
128 name = ad.name();
129 if (ad.cardinality() != 0)
130 cardinality = ad.cardinality();
131 if (ad.type() != null)
132 type = ad.type();
133 if (ad.required() || ad.deflt() == null)
134 required = true;
135
136 if (ad.description() != null)
137 description = ad.description();
138
139 if (ad.optionLabels() != null)
140 optionLabels = ad.optionLabels();
141 if (ad.optionValues() != null )
142 optionValues = ad.optionValues();
143
144 if (ad.min() != null)
145 min = ad.min();
146 if (ad.max() != null)
147 max = ad.max();
148
149 if (ad.deflt() != null)
150 deflt = ad.deflt();
151 }
152
153 if (optionValues != null) {
154 if (optionLabels == null || optionLabels.length == 0) {
155 optionLabels = new String[optionValues.length];
156 for (int i = 0; i < optionValues.length; i++)
157 optionLabels[i] = Clazz.unCamel(optionValues[i]);
158 }
159
160 if (optionLabels.length != optionValues.length) {
161 reporter.error("Option labels and option values not the same length for %s", id);
162 optionLabels = optionValues;
163 }
164 }
165
166 Tag adt = new Tag(this.ocd, "AD");
167 adt.addAttribute("name", name);
168 adt.addAttribute("id", id);
169 adt.addAttribute("cardinality", cardinality);
170 adt.addAttribute("required", required);
171 adt.addAttribute("default", deflt);
172 adt.addAttribute("type", type);
173 adt.addAttribute("max", max);
174 adt.addAttribute("min", min);
175 adt.addAttribute("description", description);
176
177 if (optionLabels != null) {
178 for (int i = 0; i < optionLabels.length; i++) {
179 Tag option = new Tag(adt, "Option");
180 option.addAttribute("label", optionLabels[i]);
181 option.addAttribute("value", optionValues[i]);
182 }
183 }
184 }
185
186 private String[] parseOptionValues(Clazz c) throws Exception {
187 final List<String> values = Create.list();
188
189 c.parseClassFileWithCollector(new ClassDataCollector() {
190 public void field(Clazz.FieldDef def) {
191 if (def.isEnum()) {
192 values.add(def.name);
193 }
194 }
195 });
196 return values.toArray(new String[values.size()]);
197 }
198
199 Meta.Type getType(String rtype) {
200 if (rtype.endsWith("[]")) {
201 rtype = rtype.substring(0, rtype.length() - 2);
202 if (rtype.endsWith("[]"))
203 throw new IllegalArgumentException("Can only handle array of depth one");
204 }
205
206 if ("boolean".equals(rtype) || Boolean.class.getName().equals(rtype))
207 return Meta.Type.Boolean;
208 else if ("byte".equals(rtype) || Byte.class.getName().equals(rtype))
209 return Meta.Type.Byte;
210 else if ("char".equals(rtype) || Character.class.getName().equals(rtype))
211 return Meta.Type.Character;
212 else if ("short".equals(rtype) || Short.class.getName().equals(rtype))
213 return Meta.Type.Short;
214 else if ("int".equals(rtype) || Integer.class.getName().equals(rtype))
215 return Meta.Type.Integer;
216 else if ("long".equals(rtype) || Long.class.getName().equals(rtype))
217 return Meta.Type.Long;
218 else if ("float".equals(rtype) || Float.class.getName().equals(rtype))
219 return Meta.Type.Float;
220 else if ("double".equals(rtype) || Double.class.getName().equals(rtype))
221 return Meta.Type.Double;
222 else
223 return Meta.Type.String;
224 }
225
226 @Override public void method(MethodDef mdef) {
227 method = mdef;
228 methods.put(mdef, null);
229 }
230
231 public String getExtra() {
232 return extra;
233 }
234
235 public long lastModified() {
236 return 0;
237 }
238
239 public InputStream openInputStream() throws IOException {
240 final PipedInputStream pin = new PipedInputStream();
241 final PipedOutputStream pout = new PipedOutputStream(pin);
242 getExecutor().execute(new Runnable() {
243 public void run() {
244 try {
245 write(pout);
246 } catch (IOException e) {
247 // Cause an exception in the other end
248 IO.close(pin);
249 }
250 IO.close(pout);
251 }
252 });
253 return pin;
254 }
255
256 private Executor getExecutor() {
257 return reporter.getPlugin(Executor.class);
258 }
259
260 public void setExtra(String extra) {
261 this.extra = extra;
262 }
263
264 public void write(OutputStream out) throws IOException {
265 try {
266 finish();
267 } catch (Exception e) {
268 throw new RuntimeException(e);
269 }
270 PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
271 pw.println("<?xml version='1.0'?>");
272 metadata.print(0, pw);
273 pw.flush();
274 }
275
276 void finish() throws Exception {
277 if (!finished) {
278 finished = true;
279 clazz.parseClassFileWithCollector(this);
280 Meta.OCD ocd = null;
281 if (this.ocdAnnotation != null)
282 ocd = this.ocdAnnotation.getAnnotation(Meta.OCD.class);
283 else
284 ocd = Configurable.createConfigurable(Meta.OCD.class,
285 new HashMap<String, Object>());
286
287 // defaults
288 String id = clazz.getFQN();
289 String name = Clazz.unCamel(Clazz.getShortName(clazz.getFQN()));
290 String description = null;
291 String localization = id;
292 boolean factory = this.factory;
293
294 if (ocd.id() != null)
295 id = ocd.id();
296
297
298 if (ocd.name() != null)
299 name = ocd.name();
300
301 if (ocd.localization() != null)
302 localization = ocd.localization();
303
304 if (ocd.description() != null)
305 description = ocd.description();
306
307 String pid = id;
308 if (override) {
309 pid = this.designatePid;
310 factory = this.factory;
311 id = this.designatePid; // for the felix problems
312 } else {
313 if (ocdAnnotation.get("factory") != null) {
314 factory = true;
315 }
316 }
317
318 this.ocd.addAttribute("name", name);
319 this.ocd.addAttribute("id", id);
320 this.ocd.addAttribute("description", description);
321 this.ocd.addAttribute("localization", localization);
322
323 // do ADs
324 for (Map.Entry<MethodDef, Meta.AD> entry : methods.entrySet())
325 addMethod(entry.getKey(), entry.getValue());
326
327 this.designate.addAttribute("pid", pid);
328 if (factory)
329 this.designate.addAttribute("factoryPid", pid);
330
331 this.object.addAttribute("ocdref", id);
332
333 }
334 }
335
336 public void setDesignate(String pid, boolean factory) {
337 this.override = true;
338 this.factory = factory;
339 this.designatePid = pid;
340 }
341}