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