-
Notifications
You must be signed in to change notification settings - Fork 114
/
FactoryProcessor.java
367 lines (314 loc) · 16.5 KB
/
FactoryProcessor.java
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
package toothpick.compiler.factory;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.inject.Inject;
import javax.inject.Scope;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import toothpick.Factory;
import toothpick.ScopeInstances;
import toothpick.compiler.common.ToothpickProcessor;
import toothpick.compiler.factory.generators.FactoryGenerator;
import toothpick.compiler.factory.targets.ConstructorInjectionTarget;
import toothpick.compiler.registry.generators.RegistryGenerator;
import toothpick.compiler.registry.targets.RegistryInjectionTarget;
import toothpick.registries.factory.AbstractFactoryRegistry;
import static java.lang.String.format;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
/**
* This processor's role is to create {@link Factory}.
* We create factories in different situations :
* <ul>
* <li> When a class {@code Foo} has an {@link javax.inject.Inject} annotated constructor : <br/>
* --> we create a Factory to create {@code Foo} instances.
* </ul>
* The processor will also try to relax the constraints to generate factories in a few cases. These factories
* are helpful as they require less work from developers :
* <ul>
* <li> When a class {@code Foo} is annotated with {@link javax.inject.Singleton} : <br/>
* --> it will use the annotated constructor or the default constructor if possible. Otherwise an error is raised.
* <li> When a class {@code Foo} is annotated with {@link ScopeInstances} : <br/>
* --> it will use the annotated constructor or the default constructor if possible. Otherwise an error is raised.
* <li> When a class {@code Foo} has an {@link javax.inject.Inject} annotated field {@code @Inject B b} : <br/>
* --> it will use the annotated constructor or the default constructor if possible. Otherwise an error is raised.
* <li> When a class {@code Foo} has an {@link javax.inject.Inject} method {@code @Inject m()} : <br/>
* --> it will use the annotated constructor or the default constructor if possible. Otherwise an error is raised.
* </ul>
* Note that if a class is abstract, the relax mechanism doesn't generate a factory and raises no error.
*/
//http://stackoverflow.com/a/2067863/693752
@SupportedAnnotationTypes({
ToothpickProcessor.INJECT_ANNOTATION_CLASS_NAME, //
ToothpickProcessor.SINGLETON_ANNOTATION_CLASS_NAME, //
ToothpickProcessor.PRODUCES_SINGLETON_ANNOTATION_CLASS_NAME
})
@SupportedOptions({
ToothpickProcessor.PARAMETER_REGISTRY_PACKAGE_NAME, //
ToothpickProcessor.PARAMETER_REGISTRY_CHILDREN_PACKAGE_NAMES, //
ToothpickProcessor.PARAMETER_EXCLUDES
}) //
public class FactoryProcessor extends ToothpickProcessor {
private Map<TypeElement, ConstructorInjectionTarget> mapTypeElementToConstructorInjectionTarget = new LinkedHashMap<>();
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
readProcessorOptions();
findAndParseTargets(roundEnv);
if (!roundEnv.processingOver()) {
return false;
}
// Generate Factories
List<TypeElement> elementsWithFactoryCreated = new ArrayList<>();
for (Map.Entry<TypeElement, ConstructorInjectionTarget> entry : mapTypeElementToConstructorInjectionTarget.entrySet()) {
ConstructorInjectionTarget constructorInjectionTarget = entry.getValue();
FactoryGenerator factoryGenerator = new FactoryGenerator(constructorInjectionTarget);
TypeElement typeElement = entry.getKey();
String fileDescription = format("Factory for type %s", typeElement);
boolean success = writeToFile(factoryGenerator, fileDescription, typeElement);
if (success) {
elementsWithFactoryCreated.add(typeElement);
}
}
// Generate Registry
//this allows tests to by pass the option mechanism in processors
if (toothpickRegistryPackageName != null) {
RegistryInjectionTarget registryInjectionTarget =
new RegistryInjectionTarget(Factory.class, AbstractFactoryRegistry.class, toothpickRegistryPackageName,
toothpickRegistryChildrenPackageNameList, elementsWithFactoryCreated);
RegistryGenerator registryGenerator = new RegistryGenerator(registryInjectionTarget);
String fileDescription = "Factory registry";
Element[] allTypes = elementsWithFactoryCreated.toArray(new Element[elementsWithFactoryCreated.size()]);
writeToFile(registryGenerator, fileDescription, allTypes);
}
return false;
}
private void findAndParseTargets(RoundEnvironment roundEnv) {
createFactoriesForClassesWithInjectAnnotatedConstructors(roundEnv);
createFactoriesForClassesAnnotatedScopeInstances(roundEnv);
createFactoriesForClassesAnnotatedSingleton(roundEnv);
createFactoriesForClassesWithInjectAnnotatedFields(roundEnv);
createFactoriesForClassesWithInjectAnnotatedMethods(roundEnv);
}
private void createFactoriesForClassesWithInjectAnnotatedMethods(RoundEnvironment roundEnv) {
for (ExecutableElement methodElement : ElementFilter.methodsIn(roundEnv.getElementsAnnotatedWith(Inject.class))) {
processClassContainingInjectAnnotatedMember(methodElement.getEnclosingElement(), mapTypeElementToConstructorInjectionTarget);
}
}
private void createFactoriesForClassesWithInjectAnnotatedFields(RoundEnvironment roundEnv) {
for (VariableElement fieldElement : ElementFilter.fieldsIn(roundEnv.getElementsAnnotatedWith(Inject.class))) {
processClassContainingInjectAnnotatedMember(fieldElement.getEnclosingElement(), mapTypeElementToConstructorInjectionTarget);
}
}
private void createFactoriesForClassesAnnotatedScopeInstances(RoundEnvironment roundEnv) {
for (Element singletonAnnotatedElement : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(ScopeInstances.class))) {
TypeElement singletonAnnotatedTypeElement = (TypeElement) singletonAnnotatedElement;
processClassContainingInjectAnnotatedMember(singletonAnnotatedTypeElement, mapTypeElementToConstructorInjectionTarget);
}
}
private void createFactoriesForClassesAnnotatedSingleton(RoundEnvironment roundEnv) {
for (Element singletonAnnotatedElement : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Singleton.class))) {
TypeElement singletonAnnotatedTypeElement = (TypeElement) singletonAnnotatedElement;
processClassContainingInjectAnnotatedMember(singletonAnnotatedTypeElement, mapTypeElementToConstructorInjectionTarget);
}
}
private void createFactoriesForClassesWithInjectAnnotatedConstructors(RoundEnvironment roundEnv) {
for (ExecutableElement constructorElement : ElementFilter.constructorsIn(roundEnv.getElementsAnnotatedWith(Inject.class))) {
TypeElement enclosingElement = (TypeElement) constructorElement.getEnclosingElement();
if (!isSingleInjectAnnotatedConstructor(constructorElement)) {
error(constructorElement, "Class %s cannot have more than one @Inject annotated constructor.", enclosingElement.getQualifiedName());
}
processInjectAnnotatedConstructor(constructorElement, mapTypeElementToConstructorInjectionTarget);
}
}
private void processClassContainingInjectAnnotatedMember(Element enclosingElement,
Map<TypeElement, ConstructorInjectionTarget> mapTypeElementToConstructorInjectionTarget) {
final TypeElement typeElement = (TypeElement) typeUtils.asElement(enclosingElement.asType());
if (mapTypeElementToConstructorInjectionTarget.containsKey(typeElement)) {
//the class is already known
return;
}
if (isExcludedByFilters(typeElement)) {
return;
}
// Verify common generated code restrictions.
if (!canTypeHaveAFactory(typeElement)) {
return;
}
ConstructorInjectionTarget constructorInjectionTarget = createConstructorInjectionTarget(typeElement);
if (constructorInjectionTarget != null) {
mapTypeElementToConstructorInjectionTarget.put(typeElement, constructorInjectionTarget);
}
}
private boolean isSingleInjectAnnotatedConstructor(Element constructorElement) {
TypeElement enclosingElement = (TypeElement) constructorElement.getEnclosingElement();
boolean isSingleInjectedConstructor = true;
List<ExecutableElement> constructorElements = ElementFilter.constructorsIn(enclosingElement.getEnclosedElements());
for (ExecutableElement constructorElementInClass : constructorElements) {
if (constructorElementInClass.getAnnotation(Inject.class) != null && !constructorElement.equals(constructorElementInClass)) {
isSingleInjectedConstructor = false;
}
}
return isSingleInjectedConstructor;
}
private void processInjectAnnotatedConstructor(ExecutableElement constructorElement, Map<TypeElement, ConstructorInjectionTarget> targetClassMap) {
TypeElement enclosingElement = (TypeElement) constructorElement.getEnclosingElement();
// Verify common generated code restrictions.
if (!isValidInjectAnnotatedConstructor(constructorElement)) {
return;
}
if (isExcludedByFilters(enclosingElement)) {
return;
}
if (!canTypeHaveAFactory(enclosingElement)) {
error(enclosingElement, "The class %s is abstract or private. It cannot have an injected constructor.", enclosingElement.getQualifiedName());
return;
}
targetClassMap.put(enclosingElement, createConstructorInjectionTarget(constructorElement));
}
private boolean isValidInjectAnnotatedConstructor(ExecutableElement element) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify modifiers.
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(PRIVATE)) {
error(element, "@Inject constructors must not be private in class %s.", enclosingElement.getQualifiedName());
return false;
}
// Verify parentScope modifiers.
Set<Modifier> parentModifiers = enclosingElement.getModifiers();
if (!parentModifiers.contains(PUBLIC)) {
error(element, "Class %s is private. @Inject constructors are not allowed in non public classes.", enclosingElement.getQualifiedName());
return false;
}
if (isNonStaticInnerClass(enclosingElement)) {
return false;
}
for (VariableElement paramElement : element.getParameters()) {
if (!isValidInjectedType(paramElement)) {
return false;
}
}
return true;
}
private ConstructorInjectionTarget createConstructorInjectionTarget(ExecutableElement constructorElement) {
TypeElement enclosingElement = (TypeElement) constructorElement.getEnclosingElement();
final String scopeName = getScopeName(enclosingElement);
final boolean hasScopeInstancesAnnotation = enclosingElement.getAnnotation(ScopeInstances.class) != null;
if (hasScopeInstancesAnnotation && scopeName == null) {
error(enclosingElement, "The type %s uses @ScopeInstances but doesn't have a scope annotation.",
enclosingElement.getQualifiedName().toString());
}
TypeElement superClassWithInjectedMembers = getMostDirectSuperClassWithInjectedMembers(enclosingElement, false);
ConstructorInjectionTarget constructorInjectionTarget =
new ConstructorInjectionTarget(enclosingElement, scopeName, hasScopeInstancesAnnotation, superClassWithInjectedMembers);
constructorInjectionTarget.parameters.addAll(getParamInjectionTargetList(constructorElement));
return constructorInjectionTarget;
}
private ConstructorInjectionTarget createConstructorInjectionTarget(TypeElement typeElement) {
final String scopeName = getScopeName(typeElement);
final boolean hasScopeInstancesAnnotation = typeElement.getAnnotation(ScopeInstances.class) != null;
if (hasScopeInstancesAnnotation && scopeName == null) {
error(typeElement, "The type %s uses @ScopeInstances but doesn't have a scope annotation.", typeElement.getQualifiedName().toString());
}
TypeElement superClassWithInjectedMembers = getMostDirectSuperClassWithInjectedMembers(typeElement, false);
List<ExecutableElement> constructorElements = ElementFilter.constructorsIn(typeElement.getEnclosedElements());
//we just need to deal with the case of the default constructor only.
//like Guice, we will call it by default in the optimistic factory
//injected constructors will be handled at some point in the compilation cycle
//if there is an injected constructor, it will be caught later, just leave
for (ExecutableElement constructorElement : constructorElements) {
if (constructorElement.getAnnotation(Inject.class) != null) {
return null;
}
}
//search for default constructor
for (ExecutableElement constructorElement : constructorElements) {
if (constructorElement.getParameters().isEmpty()) {
if (constructorElement.getModifiers().contains(Modifier.PRIVATE)) {
if (!isInjectableWarningSuppressed(typeElement)) {
warning(constructorElement,
"The class %s has a private default constructor, Toothpick can't create a factory for it.",
typeElement.getQualifiedName().toString());
}
return null;
}
ConstructorInjectionTarget constructorInjectionTarget =
new ConstructorInjectionTarget(typeElement, scopeName, hasScopeInstancesAnnotation, superClassWithInjectedMembers);
return constructorInjectionTarget;
}
}
if (!isInjectableWarningSuppressed(typeElement)) {
warning(typeElement,
"The class %s has injected fields but has no injected constructor, and no public default constructor."
+ " Toothpick can't create a factory for it.",
typeElement.getQualifiedName().toString());
}
return null;
}
/**
* Lookup {@link javax.inject.Scope} annotated annotations to provide the name of the scope the {@code typeElement} belongs to.
* The method logs an error if the {@code typeElement} has multiple scope annotations.
*
* @param typeElement the element for which a scope is to be found.
* @return the scope of this {@code typeElement} or {@code null} if it has no scope annotations.
*/
private String getScopeName(TypeElement typeElement) {
String scopeName = null;
for (AnnotationMirror annotationMirror : typeElement.getAnnotationMirrors()) {
TypeElement annotationTypeElement = (TypeElement) annotationMirror.getAnnotationType().asElement();
if (annotationTypeElement.getAnnotation(Scope.class) != null) {
if (scopeName != null) {
error(typeElement, "Only one @Scope qualified annotation is allowed : %s", scopeName);
}
scopeName = annotationTypeElement.getQualifiedName().toString();
}
}
return scopeName;
}
/**
* Checks if the injectable warning is suppressed for the TypeElement,
* through the usage of @SuppressWarning("Injectable").
*
* @param typeElement the element to check if the warning is suppressed.
* @return true is the injectable warning is suppressed, false otherwise.
*/
private boolean isInjectableWarningSuppressed(TypeElement typeElement) {
SuppressWarnings suppressWarnings = typeElement.getAnnotation(SuppressWarnings.class);
if (suppressWarnings != null) {
for (String value : suppressWarnings.value()) {
if (value.equals("Injectable")) {
return true;
}
}
}
return false;
}
private boolean canTypeHaveAFactory(TypeElement typeElement) {
boolean isAbstract = typeElement.getModifiers().contains(Modifier.ABSTRACT);
boolean isPrivate = typeElement.getModifiers().contains(Modifier.PRIVATE);
return !isAbstract && !isPrivate;
}
//used for testing only
void setToothpickRegistryPackageName(String toothpickRegistryPackageName) {
this.toothpickRegistryPackageName = toothpickRegistryPackageName;
}
//used for testing only
void setToothpickRegistryChildrenPackageNameList(List<String> toothpickRegistryChildrenPackageNameList) {
this.toothpickRegistryChildrenPackageNameList = toothpickRegistryChildrenPackageNameList;
}
//used for testing only
void setToothpickExcludeFilters(String toothpickExcludeFilters) {
this.toothpickExcludeFilters = toothpickExcludeFilters;
}
}