1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package com.lhkbob.entreri.impl;
28
29 import com.lhkbob.entreri.*;
30 import com.lhkbob.entreri.property.*;
31
32 import javax.annotation.processing.Filer;
33 import javax.lang.model.element.*;
34 import javax.lang.model.type.MirroredTypeException;
35 import javax.lang.model.type.TypeKind;
36 import javax.lang.model.type.TypeMirror;
37 import javax.lang.model.util.ElementFilter;
38 import javax.lang.model.util.Elements;
39 import javax.lang.model.util.Types;
40 import javax.tools.FileObject;
41 import javax.tools.StandardLocation;
42 import java.io.IOException;
43 import java.util.*;
44
45
46
47
48
49
50
51
52 class MirrorComponentSpecification implements ComponentSpecification {
53 private final String typeName;
54 private final String packageName;
55 private final List<MirrorPropertyDeclaration> properties;
56
57 public MirrorComponentSpecification(TypeElement type, Types tu, Elements eu,
58 Filer io) {
59 TypeMirror baseComponentType = eu
60 .getTypeElement(Component.class.getCanonicalName()).asType();
61 TypeMirror ownerType = eu.getTypeElement(Owner.class.getCanonicalName()).asType();
62 TypeMirror ownableType = eu.getTypeElement(Ownable.class.getCanonicalName())
63 .asType();
64 TypeMirror objectType = eu.getTypeElement(Object.class.getCanonicalName())
65 .asType();
66
67 if (!tu.isAssignable(type.asType(), baseComponentType)) {
68 throw fail(type.asType(), "Class must extend Component");
69 }
70 if (!type.getKind().equals(ElementKind.INTERFACE)) {
71 throw fail(type.asType(), "Component definition must be an interface");
72 }
73
74 List<MirrorPropertyDeclaration> properties = new ArrayList<>();
75
76
77
78
79 List<? extends ExecutableElement> methods = ElementFilter
80 .methodsIn(eu.getAllMembers(type));
81 Map<String, ExecutableElement> getters = new HashMap<>();
82 Map<String, ExecutableElement> setters = new HashMap<>();
83 Map<String, Integer> setterParameters = new HashMap<>();
84
85 for (ExecutableElement m : methods) {
86
87 String name = m.getSimpleName().toString();
88 TypeMirror declare = m.getEnclosingElement().asType();
89
90 if (tu.isSameType(declare, baseComponentType) ||
91 tu.isSameType(declare, ownableType) ||
92 tu.isSameType(declare, ownerType) ||
93 tu.isSameType(declare, objectType)) {
94 continue;
95 }
96
97 if (!tu.isAssignable(declare, baseComponentType)) {
98 throw fail(declare, name + ", method is not declared in Component");
99 }
100
101 if (name.startsWith("is")) {
102 processGetter(m, "is", getters);
103 } else if (name.startsWith("has")) {
104 processGetter(m, "has", getters);
105 } else if (name.startsWith("get")) {
106 processGetter(m, "get", getters);
107 } else if (name.startsWith("set")) {
108 processSetter(m, setters, setterParameters, tu);
109 } else {
110 throw fail(declare, name + " is an illegal property method");
111 }
112 }
113
114 for (String property : getters.keySet()) {
115 ExecutableElement getter = getters.get(property);
116 ExecutableElement setter = setters.remove(property);
117 Integer param = setterParameters.remove(property);
118
119 if (setter == null) {
120 throw fail(type.asType(), property + " has no matching setter");
121 } else if (!tu.isSameType(getter.getReturnType(),
122 setter.getParameters().get(param).asType())) {
123 throw fail(type.asType(), property + " has inconsistent type");
124 }
125
126 TypeMirror propertyType = getPropertyType(getter, tu, eu, io);
127 properties.add(new MirrorPropertyDeclaration(property, getter, setter, param,
128 propertyType));
129 }
130
131 if (!setters.isEmpty()) {
132 throw fail(type.asType(), setters.keySet() + " have no matching getters");
133 }
134
135
136 Collections.sort(properties);
137
138 String qualifiedName = type.getQualifiedName().toString();
139 String packageName = eu.getPackageOf(type).getQualifiedName().toString();
140 if (packageName.isEmpty()) {
141 typeName = qualifiedName;
142 this.packageName = "";
143 } else {
144 typeName = qualifiedName.substring(packageName.length() + 1);
145 this.packageName = packageName;
146 }
147
148 this.properties = Collections.unmodifiableList(properties);
149 }
150
151 @Override
152 public String getType() {
153 return typeName;
154 }
155
156 @Override
157 public String getPackage() {
158 return packageName;
159 }
160
161 @Override
162 public List<? extends PropertyDeclaration> getProperties() {
163 return properties;
164 }
165
166 private static IllegalComponentDefinitionException fail(TypeMirror type, String msg) {
167 return new IllegalComponentDefinitionException(type.toString(), msg);
168 }
169
170 private static class MirrorPropertyDeclaration implements PropertyDeclaration {
171 private final String name;
172
173 private final String setter;
174 private final int setterParameter;
175 private final boolean setterReturnsComponent;
176
177 private final String getter;
178 private final boolean isSharedInstance;
179
180 private final String type;
181 private final String propertyType;
182
183 public MirrorPropertyDeclaration(String name, ExecutableElement getter,
184 ExecutableElement setter, int parameter,
185 TypeMirror propertyType) {
186 this.name = name;
187 this.getter = getter.getSimpleName().toString();
188 this.setter = setter.getSimpleName().toString();
189 this.propertyType = propertyType.toString();
190 setterParameter = parameter;
191
192 type = getter.getReturnType().toString();
193 setterReturnsComponent = !setter.getReturnType().getKind()
194 .equals(TypeKind.VOID);
195
196 isSharedInstance = getter.getAnnotation(SharedInstance.class) != null;
197 }
198
199 @Override
200 public String getName() {
201 return name;
202 }
203
204 @Override
205 public String getType() {
206 return type;
207 }
208
209 @Override
210 public String getPropertyImplementation() {
211 return propertyType;
212 }
213
214 @Override
215 public String getSetterMethod() {
216 return setter;
217 }
218
219 @Override
220 public String getGetterMethod() {
221 return getter;
222 }
223
224 @Override
225 public int getSetterParameter() {
226 return setterParameter;
227 }
228
229 @Override
230 public boolean getSetterReturnsComponent() {
231 return setterReturnsComponent;
232 }
233
234 @Override
235 public boolean isShared() {
236 return isSharedInstance;
237 }
238
239 @Override
240 public PropertyFactory<?> getPropertyFactory() {
241 throw new UnsupportedOperationException(
242 "Cannot create PropertyFactory with mirror API");
243 }
244
245 @Override
246 public int compareTo(PropertyDeclaration o) {
247 return name.compareTo(o.getName());
248 }
249 }
250
251 private static void processSetter(ExecutableElement m,
252 Map<String, ExecutableElement> setters,
253 Map<String, Integer> parameters, Types tu) {
254 TypeMirror declaringClass = m.getEnclosingElement().asType();
255 if (!tu.isSameType(m.getReturnType(), m.getEnclosingElement().asType()) &&
256 !m.getReturnType().getKind().equals(TypeKind.VOID)) {
257 throw fail(declaringClass, m + " has invalid return type for setter");
258 }
259
260 List<? extends VariableElement> params = m.getParameters();
261 if (params.isEmpty()) {
262 throw fail(declaringClass, m + " must have at least one parameter");
263 }
264
265 if (params.size() == 1) {
266 String name = getNameFromParameter(params.get(0));
267 if (name != null) {
268
269 if (m.getAnnotation(Named.class) != null) {
270 throw fail(declaringClass,
271 m + ", @Named cannot be on both parameter and method");
272 }
273 } else {
274 name = getName(m, "set");
275 }
276
277 if (setters.containsKey(name)) {
278 throw fail(declaringClass, name + " already declared on a setter");
279 }
280 setters.put(name, m);
281 parameters.put(name, 0);
282 } else {
283
284 if (m.getAnnotation(Named.class) != null) {
285 throw fail(declaringClass, m +
286 ", @Named cannot be applied to setter method with multiple parameters");
287 }
288
289 int i = 0;
290 for (VariableElement p : params) {
291 String name = getNameFromParameter(p);
292 if (name == null) {
293 throw fail(declaringClass, m +
294 ", @Named must be applied to each parameter for multi-parameter setter methods");
295 }
296
297 if (setters.containsKey(name)) {
298 throw fail(declaringClass, name + " already declared on a setter");
299 }
300
301 setters.put(name, m);
302 parameters.put(name, i++);
303 }
304 }
305 }
306
307 private static void processGetter(ExecutableElement m, String prefix,
308 Map<String, ExecutableElement> getters) {
309 TypeMirror declaringClass = m.getEnclosingElement().asType();
310
311 String name = getName(m, prefix);
312 if (getters.containsKey(name)) {
313 throw fail(declaringClass, name + " already declared on a getter");
314 }
315 if (!m.getParameters().isEmpty()) {
316 throw fail(declaringClass, m + ", getter must not take arguments");
317 }
318 if (m.getReturnType().getKind().equals(TypeKind.VOID)) {
319 throw fail(declaringClass, m + ", getter must have non-void return type");
320 }
321
322 getters.put(name, m);
323 }
324
325 private static String getNameFromParameter(VariableElement parameter) {
326 Named n = parameter.getAnnotation(Named.class);
327 if (n != null) {
328 return n.value();
329 } else {
330 return null;
331 }
332 }
333
334 private static String getName(ExecutableElement m, String prefix) {
335 Named n = m.getAnnotation(Named.class);
336 if (n != null) {
337 return n.value();
338 } else {
339 String name = m.getSimpleName().toString();
340 return Character.toLowerCase(name.charAt(prefix.length())) +
341 name.substring(prefix.length() + 1);
342 }
343 }
344
345 private static TypeMirror getPropertyType(ExecutableElement getter, Types tu,
346 Elements eu, Filer io) {
347 TypeMirror baseType = getter.getReturnType();
348
349 TypeMirror factory = getFactory(getter);
350 if (factory != null) {
351
352 return validateFactory(getter, factory, null, tu, eu);
353 } else {
354
355 TypeElement mappedType;
356 switch (baseType.getKind()) {
357 case BOOLEAN:
358 mappedType = eu.getTypeElement(BooleanProperty.class.getCanonicalName());
359 break;
360 case BYTE:
361 mappedType = eu.getTypeElement(ByteProperty.class.getCanonicalName());
362 break;
363 case CHAR:
364 mappedType = eu.getTypeElement(CharProperty.class.getCanonicalName());
365 break;
366 case DOUBLE:
367 mappedType = eu.getTypeElement(DoubleProperty.class.getCanonicalName());
368 break;
369 case FLOAT:
370 mappedType = eu.getTypeElement(FloatProperty.class.getCanonicalName());
371 break;
372 case INT:
373 mappedType = eu.getTypeElement(IntProperty.class.getCanonicalName());
374 break;
375 case LONG:
376 mappedType = eu.getTypeElement(LongProperty.class.getCanonicalName());
377 break;
378 case SHORT:
379 mappedType = eu.getTypeElement(ShortProperty.class.getCanonicalName());
380 break;
381 default:
382 FileObject mapping;
383 try {
384 mapping = io.getResource(StandardLocation.CLASS_PATH, "",
385 TypePropertyMapping.MAPPING_DIR +
386 baseType.toString());
387 } catch (IOException e) {
388
389 mapping = null;
390 }
391
392 if (mapping != null) {
393 try {
394 String content = mapping.getCharContent(true).toString().trim();
395 mappedType = eu.getTypeElement(content);
396 } catch (IOException e) {
397
398
399 throw new RuntimeException(e);
400 }
401 } else {
402 mappedType = eu
403 .getTypeElement(ObjectProperty.class.getCanonicalName());
404 }
405 }
406
407 factory = getFactory(mappedType);
408 if (factory == null) {
409 throw fail(getter.getEnclosingElement().asType(),
410 mappedType + " has no @Factory annotation");
411 } else {
412 return validateFactory(getter, factory, mappedType, tu, eu);
413 }
414 }
415 }
416
417 private static TypeMirror getFactory(Element e) {
418 try {
419 com.lhkbob.entreri.property.Factory factory = e
420 .getAnnotation(com.lhkbob.entreri.property.Factory.class);
421 if (factory != null) {
422 factory.value();
423 }
424 return null;
425 } catch (MirroredTypeException te) {
426 return te.getTypeMirror();
427 }
428 }
429
430 private static final EnumSet<TypeKind> PRIMITIVES = EnumSet
431 .of(TypeKind.BOOLEAN, TypeKind.BYTE, TypeKind.CHAR, TypeKind.DOUBLE,
432 TypeKind.FLOAT, TypeKind.INT, TypeKind.LONG, TypeKind.SHORT);
433
434 private static TypeMirror validateFactory(ExecutableElement getter,
435 TypeMirror factory,
436 TypeElement propertyType, Types tu,
437 Elements eu) {
438 TypeMirror declaringClass = getter.getEnclosingElement().asType();
439 boolean isShared = getter.getAnnotation(SharedInstance.class) != null;
440 TypeMirror baseType = getter.getReturnType();
441
442 TypeMirror createdType = null;
443 List<? extends ExecutableElement> factoryMethods = ElementFilter
444 .methodsIn(eu.getAllMembers((TypeElement) tu.asElement(factory)));
445 for (ExecutableElement m : factoryMethods) {
446 if (m.getSimpleName().contentEquals("create")) {
447 createdType = m.getReturnType();
448 break;
449 }
450 }
451 if (createdType == null) {
452 throw fail(declaringClass, factory + " is missing create() method");
453 }
454
455 if (propertyType == null) {
456
457 propertyType = (TypeElement) tu.asElement(createdType);
458 } else {
459
460 if (!tu.isAssignable(createdType, propertyType.asType())) {
461 throw fail(declaringClass, "Factory creates " + createdType +
462 ", which is incompatible with expected type " +
463 propertyType);
464 }
465 }
466
467
468 TypeMirror asType = propertyType.asType();
469 if (tu.isSameType(asType,
470 eu.getTypeElement(ObjectProperty.class.getCanonicalName())
471 .asType())) {
472
473
474 if (isShared) {
475 throw fail(declaringClass,
476 propertyType + " can't be used with @SharedInstance");
477 } else if (PRIMITIVES.contains(asType.getKind())) {
478 throw fail(declaringClass,
479 "ObjectProperty cannot be used with primitive types");
480 }
481
482 } else {
483 TypeMirror intType = tu.getPrimitiveType(TypeKind.INT);
484 TypeMirror voidType = tu.getNoType(TypeKind.VOID);
485 List<? extends ExecutableElement> methods = ElementFilter
486 .methodsIn(eu.getAllMembers(propertyType));
487
488 if (!findMethod(methods, tu, "get", baseType, intType)) {
489 throw fail(declaringClass,
490 propertyType + " does not implement " + baseType + " get()");
491 }
492 if (!findMethod(methods, tu, "set", voidType, intType, baseType)) {
493 throw fail(declaringClass,
494 propertyType + " does not implement void set(int, " +
495 baseType + ")");
496 }
497
498 if (isShared) {
499
500
501 TypeMirror share = tu.erasure(
502 eu.getTypeElement(ShareableProperty.class.getCanonicalName())
503 .asType());
504 if (!tu.isAssignable(asType, share)) {
505 throw fail(declaringClass,
506 propertyType + " can't be used with @SharedInstance");
507 }
508
509
510 if (!findMethod(methods, tu, "get", voidType, intType, baseType)) {
511 throw fail(declaringClass,
512 propertyType + " does not implement void get(int, " +
513 baseType + ")");
514 }
515 if (!findMethod(methods, tu, "createShareableInstance", baseType)) {
516 throw fail(declaringClass,
517 propertyType + " does not implement " + baseType +
518 " createShareableInstance()");
519 }
520 }
521 }
522
523 return tu.erasure(asType);
524 }
525
526 private static boolean findMethod(List<? extends ExecutableElement> methods, Types tu,
527 String name, TypeMirror returnType,
528 TypeMirror... params) {
529 for (ExecutableElement m : methods) {
530 if (m.getSimpleName().contentEquals(name) &&
531 tu.isSameType(returnType, m.getReturnType())) {
532
533 List<? extends VariableElement> realParams = m.getParameters();
534 if (params.length == realParams.size()) {
535 boolean found = true;
536 for (int i = 0; i < params.length; i++) {
537 if (!tu.isSameType(params[i], realParams.get(i).asType())) {
538 found = false;
539 break;
540 }
541 }
542
543 if (found) {
544 return true;
545 }
546 }
547 }
548 }
549
550 return false;
551 }
552 }