Stuart McCulloch | 26e7a5a | 2011-10-17 10:31:43 +0000 | [diff] [blame] | 1 | package aQute.bnd.make.metatype; |
| 2 | |
| 3 | import java.io.*; |
| 4 | import java.util.*; |
| 5 | import java.util.concurrent.*; |
| 6 | import java.util.regex.*; |
| 7 | |
| 8 | import aQute.bnd.annotation.metatype.*; |
| 9 | import aQute.lib.io.*; |
| 10 | import aQute.lib.osgi.*; |
| 11 | import aQute.lib.osgi.Clazz.MethodDef; |
| 12 | import aQute.lib.tag.*; |
| 13 | import aQute.libg.generics.*; |
| 14 | |
| 15 | public 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 | } |