Stuart McCulloch | 26e7a5a | 2011-10-17 10:31:43 +0000 | [diff] [blame] | 1 | package aQute.bnd.make.component; |
| 2 | |
| 3 | import static aQute.bnd.make.component.ServiceComponent.*; |
| 4 | |
| 5 | import java.lang.reflect.*; |
| 6 | import java.util.*; |
| 7 | import java.util.regex.*; |
| 8 | |
| 9 | import aQute.bnd.annotation.component.*; |
| 10 | import aQute.lib.osgi.*; |
| 11 | import aQute.libg.reporter.*; |
| 12 | |
| 13 | public 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 | } |