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 | |
| 32 | public static final Version V1_1 = new Version("1.1.0"); // "1.1.0" |
| 33 | public static final Version V1_2 = new Version("1.2.0"); // "1.1.0" |
| 34 | static Pattern BINDNAME = Pattern.compile("(set|add|bind)?(.*)"); |
| 35 | static Pattern BINDDESCRIPTOR = Pattern |
| 36 | .compile("\\(((L([^;]+);)(Ljava/util/Map;)?|Lorg/osgi/framework/ServiceReference;)\\)V"); |
| 37 | |
| 38 | static Pattern LIFECYCLEDESCRIPTOR = Pattern |
| 39 | .compile("\\(((Lorg/osgi/service/component/ComponentContext;)|(Lorg/osgi/framework/BundleContext;)|(Ljava/util/Map;))*\\)V"); |
| 40 | static Pattern REFERENCEBINDDESCRIPTOR = Pattern |
| 41 | .compile("\\(Lorg/osgi/framework/ServiceReference;\\)V"); |
| 42 | |
| 43 | ComponentDef component = new ComponentDef(); |
| 44 | |
| 45 | Clazz clazz; |
| 46 | TypeRef interfaces[]; |
| 47 | MethodDef method; |
| 48 | TypeRef className; |
| 49 | Analyzer analyzer; |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 50 | MultiMap<String,String> methods = new MultiMap<String,String>(); |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 51 | TypeRef extendsClass; |
| 52 | boolean inherit; |
| 53 | boolean baseclass = true; |
| 54 | |
| 55 | AnnotationReader(Analyzer analyzer, Clazz clazz, boolean inherit) { |
| 56 | this.analyzer = analyzer; |
| 57 | this.clazz = clazz; |
| 58 | this.inherit = inherit; |
| 59 | } |
| 60 | |
| 61 | public static ComponentDef getDefinition(Clazz c, Analyzer analyzer) throws Exception { |
| 62 | boolean inherit = Processor.isTrue(analyzer.getProperty("-dsannotations-inherit")); |
| 63 | AnnotationReader r = new AnnotationReader(analyzer, c, inherit); |
| 64 | return r.getDef(); |
| 65 | } |
| 66 | |
| 67 | private ComponentDef getDef() throws Exception { |
| 68 | clazz.parseClassFileWithCollector(this); |
| 69 | if (component.implementation == null) |
| 70 | return null; |
| 71 | |
| 72 | if (inherit) { |
| 73 | baseclass = false; |
| 74 | while (extendsClass != null) { |
| 75 | if (extendsClass.isJava()) |
| 76 | break; |
| 77 | |
| 78 | Clazz ec = analyzer.findClass(extendsClass); |
| 79 | if (ec == null) { |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 80 | analyzer.error("Missing super class for DS annotations: " + extendsClass + " from " |
| 81 | + clazz.getClassName()); |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 82 | } else { |
| 83 | ec.parseClassFileWithCollector(this); |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | for (ReferenceDef rdef : component.references.values()) { |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 88 | rdef.unbind = referredMethod(analyzer, rdef, rdef.unbind, "add(.*)", "remove$1", "(.*)", "un$1"); |
| 89 | rdef.updated = referredMethod(analyzer, rdef, rdef.updated, "(add|set|bind)(.*)", "updated$2", "(.*)", |
| 90 | "updated$1"); |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 91 | } |
| 92 | return component; |
| 93 | } |
| 94 | |
| 95 | /** |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 96 | * @param analyzer |
| 97 | * @param rdef |
| 98 | */ |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 99 | protected String referredMethod(Analyzer analyzer, ReferenceDef rdef, String value, String... matches) { |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 100 | if (value == null) { |
| 101 | String bind = rdef.bind; |
| 102 | for (int i = 0; i < matches.length; i += 2) { |
| 103 | Matcher m = Pattern.compile(matches[i]).matcher(bind); |
| 104 | if (m.matches()) { |
| 105 | value = m.replaceFirst(matches[i + 1]); |
| 106 | break; |
| 107 | } |
| 108 | } |
| 109 | } else if (value.equals("-")) |
| 110 | return null; |
| 111 | |
| 112 | if (methods.containsKey(value)) { |
| 113 | for (String descriptor : methods.get(value)) { |
| 114 | Matcher matcher = BINDDESCRIPTOR.matcher(descriptor); |
| 115 | if (matcher.matches()) { |
| 116 | String type = matcher.group(2); |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 117 | if (rdef.service.equals(Clazz.objectDescriptorToFQN(type)) || type.equals("Ljava/util/Map;") |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 118 | || type.equals("Lorg/osgi/framework/ServiceReference;")) { |
| 119 | |
| 120 | return value; |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | analyzer.error( |
| 125 | "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)", |
| 126 | rdef.bind, value, component.implementation, value, rdef.service); |
| 127 | } |
| 128 | return null; |
| 129 | } |
| 130 | |
| 131 | public void annotation(Annotation annotation) { |
| 132 | try { |
| 133 | java.lang.annotation.Annotation a = annotation.getAnnotation(); |
| 134 | if (a instanceof Component) |
| 135 | doComponent((Component) a, annotation); |
| 136 | else if (a instanceof Activate) |
| 137 | doActivate(); |
| 138 | else if (a instanceof Deactivate) |
| 139 | doDeactivate(); |
| 140 | else if (a instanceof Modified) |
| 141 | doModified(); |
| 142 | else if (a instanceof Reference) |
| 143 | doReference((Reference) a, annotation); |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 144 | } |
| 145 | catch (Exception e) { |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 146 | e.printStackTrace(); |
| 147 | analyzer.error("During generation of a component on class %s, exception %s", clazz, e); |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * |
| 153 | */ |
| 154 | protected void doDeactivate() { |
| 155 | if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches()) |
| 156 | analyzer.error( |
| 157 | "Deactivate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s", |
| 158 | clazz, method.getDescriptor()); |
| 159 | else { |
| 160 | component.deactivate = method.getName(); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | /** |
| 165 | * |
| 166 | */ |
| 167 | protected void doModified() { |
| 168 | if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches()) |
| 169 | analyzer.error( |
| 170 | "Modified method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s", |
| 171 | clazz, method.getDescriptor()); |
| 172 | else { |
| 173 | component.modified = method.getName(); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * @param annotation |
| 179 | * @throws Exception |
| 180 | */ |
| 181 | protected void doReference(Reference reference, Annotation raw) throws Exception { |
| 182 | ReferenceDef def = new ReferenceDef(); |
| 183 | def.name = reference.name(); |
| 184 | |
| 185 | if (def.name == null) { |
| 186 | Matcher m = BINDNAME.matcher(method.getName()); |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 187 | if (m.matches()) |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 188 | def.name = m.group(2); |
| 189 | else |
| 190 | analyzer.error("Invalid name for bind method %s", method.getName()); |
| 191 | } |
| 192 | |
| 193 | def.unbind = reference.unbind(); |
| 194 | def.updated = reference.updated(); |
| 195 | def.bind = method.getName(); |
| 196 | |
| 197 | def.service = raw.get("service"); |
| 198 | if (def.service != null) { |
| 199 | def.service = Clazz.objectDescriptorToFQN(def.service); |
| 200 | } else { |
| 201 | // We have to find the type of the current method to |
| 202 | // link it to the referenced service. |
| 203 | Matcher m = BINDDESCRIPTOR.matcher(method.getDescriptor().toString()); |
| 204 | if (m.matches()) { |
| 205 | def.service = Descriptors.binaryToFQN(m.group(3)); |
| 206 | } else |
| 207 | throw new IllegalArgumentException( |
| 208 | "Cannot detect the type of a Component Reference from the descriptor: " |
| 209 | + method.getDescriptor()); |
| 210 | } |
| 211 | |
| 212 | // Check if we have a target, this must be a filter |
| 213 | def.target = reference.target(); |
| 214 | |
| 215 | if (component.references.containsKey(def.name)) |
| 216 | analyzer.error( |
| 217 | "In component %s, multiple references with the same name: %s. Previous def: %s, this def: %s", |
| 218 | component.implementation, component.references.get(def.name), def.service, ""); |
| 219 | else |
| 220 | component.references.put(def.name, def); |
| 221 | |
| 222 | def.cardinality = reference.cardinality(); |
| 223 | def.policy = reference.policy(); |
| 224 | def.policyOption = reference.policyOption(); |
| 225 | } |
| 226 | |
| 227 | /** |
| 228 | * |
| 229 | */ |
| 230 | protected void doActivate() { |
| 231 | if (!LIFECYCLEDESCRIPTOR.matcher(method.getDescriptor().toString()).matches()) |
| 232 | analyzer.error( |
| 233 | "Activate method for %s does not have an acceptable prototype, only Map, ComponentContext, or BundleContext is allowed. Found: %s", |
| 234 | clazz, method.getDescriptor()); |
| 235 | else { |
| 236 | component.activate = method.getName(); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | /** |
| 241 | * @param annotation |
| 242 | * @throws Exception |
| 243 | */ |
| 244 | protected void doComponent(Component comp, Annotation annotation) throws Exception { |
| 245 | |
| 246 | // Check if we are doing a super class |
| 247 | if (component.implementation != null) |
| 248 | return; |
| 249 | |
| 250 | component.version = V1_1; |
| 251 | component.implementation = clazz.getClassName(); |
| 252 | component.name = comp.name(); |
| 253 | component.factory = comp.factory(); |
| 254 | component.configurationPolicy = comp.configurationPolicy(); |
| 255 | if (annotation.get("enabled") != null) |
| 256 | component.enabled = comp.enabled(); |
| 257 | if (annotation.get("factory") != null) |
| 258 | component.factory = comp.factory(); |
| 259 | if (annotation.get("immediate") != null) |
| 260 | component.immediate = comp.immediate(); |
| 261 | if (annotation.get("servicefactory") != null) |
| 262 | component.servicefactory = comp.servicefactory(); |
| 263 | |
| 264 | if (annotation.get("configurationPid") != null) |
| 265 | component.configurationPid = comp.configurationPid(); |
| 266 | |
| 267 | if (annotation.get("xmlns") != null) |
| 268 | component.xmlns = comp.xmlns(); |
| 269 | |
| 270 | String properties[] = comp.properties(); |
| 271 | if (properties != null) |
| 272 | for (String entry : properties) |
| 273 | component.properties.add(entry); |
| 274 | |
| 275 | doProperties(comp.property()); |
| 276 | Object[] x = annotation.get("service"); |
| 277 | |
| 278 | if (x == null) { |
| 279 | // Use the found interfaces, but convert from internal to |
| 280 | // fqn. |
| 281 | if (interfaces != null) { |
| 282 | List<TypeRef> result = new ArrayList<TypeRef>(); |
| 283 | for (int i = 0; i < interfaces.length; i++) { |
| 284 | if (!interfaces[i].equals(analyzer.getTypeRef("scala/ScalaObject"))) |
| 285 | result.add(interfaces[i]); |
| 286 | } |
| 287 | component.service = result.toArray(EMPTY); |
| 288 | } |
| 289 | } else { |
| 290 | // We have explicit interfaces set |
| 291 | component.service = new TypeRef[x.length]; |
| 292 | for (int i = 0; i < x.length; i++) { |
| 293 | String s = (String) x[i]; |
| 294 | TypeRef ref = analyzer.getTypeRefFromFQN(s); |
| 295 | component.service[i] = ref; |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | } |
| 300 | |
| 301 | /** |
| 302 | * Parse the properties |
| 303 | */ |
| 304 | |
| 305 | private void doProperties(String[] properties) { |
| 306 | if (properties != null) { |
| 307 | for (String p : properties) { |
| 308 | Matcher m = PROPERTY_PATTERN.matcher(p); |
| 309 | |
| 310 | if (m.matches()) { |
| 311 | String key = m.group(1); |
Stuart McCulloch | cd1ddd7 | 2012-07-19 13:11:20 +0000 | [diff] [blame] | 312 | String type = m.group(2); |
| 313 | if ( type != null) |
| 314 | key += ":" + type; |
| 315 | |
| 316 | String value = m.group(3); |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 317 | component.property.add(key, value); |
| 318 | } else |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 319 | throw new IllegalArgumentException("Malformed property '" + p + "' on component: " + className); |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 320 | } |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | /** |
| 325 | * Are called during class parsing |
| 326 | */ |
| 327 | |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 328 | @Override |
| 329 | public void classBegin(int access, TypeRef name) { |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 330 | className = name; |
| 331 | } |
| 332 | |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 333 | @Override |
| 334 | public void implementsInterfaces(TypeRef[] interfaces) { |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 335 | this.interfaces = interfaces; |
| 336 | } |
| 337 | |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 338 | @Override |
| 339 | public void method(Clazz.MethodDef method) { |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 340 | int access = method.getAccess(); |
| 341 | |
| 342 | if (Modifier.isAbstract(access) || Modifier.isStatic(access)) |
| 343 | return; |
| 344 | |
| 345 | if (!baseclass && Modifier.isPrivate(access)) |
| 346 | return; |
| 347 | |
| 348 | this.method = method; |
| 349 | methods.add(method.getName(), method.getDescriptor().toString()); |
| 350 | } |
| 351 | |
Stuart McCulloch | 4482c70 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 352 | @Override |
| 353 | public void extendsClass(TypeRef name) { |
Stuart McCulloch | f317322 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 354 | this.extendsClass = name; |
| 355 | } |
| 356 | |
| 357 | } |