Skip to content
This repository was archived by the owner on Dec 17, 2020. It is now read-only.

Commit 07d45ef

Browse files
committed
Merge branch 'develop'
2 parents 371289b + 1595db8 commit 07d45ef

File tree

63 files changed

+2119
-698
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+2119
-698
lines changed

DOCUMENTATION.md

Lines changed: 322 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 62 additions & 265 deletions
Large diffs are not rendered by default.

annotations-processor/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

annotations-processor/build.gradle

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import org.gradle.internal.jvm.Jvm
2+
3+
apply plugin: 'java'
4+
5+
sourceCompatibility = JavaVersion.VERSION_1_7
6+
targetCompatibility = JavaVersion.VERSION_1_7
7+
8+
//Without this line the Bintray upload will fail as it uses the module directory name as artifactId
9+
archivesBaseName = POM_ARTIFACT_ID
10+
group = GROUP
11+
version = VERSION_NAME
12+
13+
dependencies {
14+
compile fileTree(include: ['*.jar'], dir: 'libs')
15+
compile project(':annotations')
16+
17+
// Java Poet to create Java source files
18+
compile 'com.squareup:javapoet:1.9.0'
19+
20+
// For NotNull, Nullable etc. annotations
21+
compile 'com.google.code.findbugs:jsr305:3.0.2'
22+
23+
// Fix for warning: "bootstrap class path not set in conjunction with -source 1.7"
24+
compileOnly files(Jvm.current().getToolsJar())
25+
}
26+
27+
apply from: rootProject.file('publish-library.gradle')
28+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
POM_ARTIFACT_ID=android-retainable-tasks-compiler
2+
POM_NAME=Android-Retainable-Tasks-Compiler
3+
POM_PACKAGING=jar
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package org.neotech.library.retainabletasks;
2+
3+
import com.squareup.javapoet.ClassName;
4+
import com.squareup.javapoet.JavaFile;
5+
import com.squareup.javapoet.MethodSpec;
6+
import com.squareup.javapoet.TypeName;
7+
import com.squareup.javapoet.TypeSpec;
8+
import com.squareup.javapoet.TypeVariableName;
9+
10+
import java.io.IOException;
11+
import java.lang.annotation.Annotation;
12+
import java.util.HashMap;
13+
import java.util.LinkedHashSet;
14+
import java.util.List;
15+
import java.util.Map;
16+
import java.util.Set;
17+
18+
import javax.annotation.Nullable;
19+
import javax.annotation.processing.AbstractProcessor;
20+
import javax.annotation.processing.Filer;
21+
import javax.annotation.processing.ProcessingEnvironment;
22+
import javax.annotation.processing.RoundEnvironment;
23+
import javax.lang.model.SourceVersion;
24+
import javax.lang.model.element.Element;
25+
import javax.lang.model.element.ExecutableElement;
26+
import javax.lang.model.element.Modifier;
27+
import javax.lang.model.element.TypeElement;
28+
import javax.lang.model.element.VariableElement;
29+
import javax.lang.model.type.TypeMirror;
30+
import javax.tools.Diagnostic;
31+
32+
public final class AnnotationsProcessor extends AbstractProcessor {
33+
34+
private static final String LIBRARY_PACKAGE = "org.neotech.library.retainabletasks";
35+
36+
private static final ClassName CLASS_TASKATTACHBINDING = ClassName.get(LIBRARY_PACKAGE + ".internal", "TaskAttachBinding");
37+
private static final ClassName CLASS_TASK = ClassName.get(LIBRARY_PACKAGE, "Task");
38+
private static final ClassName CLASS_TASK_CALLBACK = CLASS_TASK.nestedClass("Callback");
39+
private static final ClassName CLASS_TASK_ADVANCEDCALLBACK = CLASS_TASK.nestedClass("AdvancedCallback");
40+
private static final ClassName CLASS_TASKMANAGEROWNER = ClassName.get(LIBRARY_PACKAGE, "TaskManagerOwner");
41+
42+
private Filer filer;
43+
44+
@Override
45+
public synchronized void init(ProcessingEnvironment processingEnv) {
46+
super.init(processingEnv);
47+
filer = processingEnv.getFiler();
48+
}
49+
50+
@Override
51+
public SourceVersion getSupportedSourceVersion() {
52+
return SourceVersion.latestSupported();
53+
}
54+
55+
@Override
56+
public Set<String> getSupportedAnnotationTypes() {
57+
final Set<String> types = new LinkedHashSet<>();
58+
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
59+
types.add(annotation.getCanonicalName());
60+
}
61+
return types;
62+
}
63+
64+
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
65+
final Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
66+
annotations.add(TaskAttach.class);
67+
annotations.add(TaskPreExecute.class);
68+
annotations.add(TaskPostExecute.class);
69+
annotations.add(TaskCancel.class);
70+
annotations.add(TaskProgress.class);
71+
return annotations;
72+
}
73+
74+
@Override
75+
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
76+
final HashMap<TypeElement, TaskBindingContainer> classMap = new HashMap<>();
77+
78+
for(TypeElement annotationType: annotations){
79+
processAnnotation(classMap, roundEnv, annotationType);
80+
}
81+
82+
for(Map.Entry<TypeElement, TaskBindingContainer> entry: classMap.entrySet()) {
83+
try {
84+
createJavaFile(entry.getKey(), entry.getValue()).writeTo(filer);
85+
} catch (IOException e) {
86+
error(entry.getKey(), "Unable to write generate java binding file for class %s: %s", entry.getKey(), e.getMessage());
87+
}
88+
}
89+
return true;
90+
}
91+
92+
private void processAnnotation(HashMap<TypeElement, TaskBindingContainer> classMap, RoundEnvironment roundEnvironment, TypeElement type){
93+
for (Element element : roundEnvironment.getElementsAnnotatedWith(type)) {
94+
final TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
95+
TaskBindingContainer binding = classMap.get(enclosingElement);
96+
System.out.println(enclosingElement);
97+
if(binding == null){
98+
binding = new TaskBindingContainer();
99+
classMap.put(enclosingElement, binding);
100+
}
101+
final Class<? extends Annotation> classType;
102+
try {
103+
//noinspection unchecked
104+
classType = (Class<? extends Annotation>) Class.forName(type.getQualifiedName().toString());
105+
} catch (ClassNotFoundException e) {
106+
e.printStackTrace();
107+
error(element, "Could not find the Class object for TypeElement %s", type);
108+
return;
109+
}
110+
binding.add(element, classType);
111+
}
112+
}
113+
114+
private JavaFile createJavaFile(TypeElement forTypeElement, TaskBindingContainer binding){
115+
116+
final String packageName = processingEnv.getElementUtils().getPackageOf(forTypeElement).getQualifiedName().toString();
117+
final String className = forTypeElement.getQualifiedName().toString().substring(packageName.length() + 1).replace('.', '$');
118+
final ClassName bindingClassName = ClassName.get(packageName, className + "_TaskBinding");
119+
120+
// Create the constructor which takes the target class (the class containing the annotated
121+
// methods) as argument.
122+
final MethodSpec constructor = MethodSpec.constructorBuilder()
123+
.addParameter(TypeVariableName.get("T"), "target", Modifier.FINAL)
124+
.addModifiers(Modifier.PUBLIC)
125+
.addStatement("this.target = target").build();
126+
127+
128+
// create (and implement) the getListenerFor method from the TaskAttachBinding interface
129+
final MethodSpec.Builder getListenerForMethod = MethodSpec.methodBuilder("getListenerFor")
130+
.addParameter(CLASS_TASK, "task")
131+
.addParameter(TypeName.BOOLEAN, "isReAttach")
132+
.addModifiers(Modifier.PUBLIC)
133+
.returns(CLASS_TASK_CALLBACK)
134+
.addAnnotation(Override.class);
135+
136+
// Create a switch statement to switch between the different tasks
137+
getListenerForMethod.beginControlFlow("switch(task.getTag())");
138+
139+
// Loop through the tasks and create switch cases for them.
140+
for(Map.Entry<String, TaskBinding> entry: binding.getTaskBindings().entrySet()){
141+
142+
final TaskBinding methods = entry.getValue();
143+
144+
if(methods.getElementForPostExecute() == null){
145+
note(forTypeElement, "No @TaskPostExecute annotated method found for task with tag '%s' while other annotated methods for that task have been found!");
146+
}
147+
148+
getListenerForMethod.beginControlFlow("case \"$L\":\n", entry.getKey());
149+
150+
// The getListenerFor method requires us to return an implementation of the
151+
// Task.Callback class, in this case (even though we might not need it) we use the
152+
// Task.AdvancedCallback instead of a simple Task.Callback implementation.
153+
final TypeSpec.Builder callbackImplementation = TypeSpec.anonymousClassBuilder("")
154+
.addSuperinterface(CLASS_TASK_ADVANCEDCALLBACK);
155+
156+
157+
callbackImplementation.addMethod(createTaskCallbackMethod("onPreExecute", methods.getElementForPreExecute()));
158+
callbackImplementation.addMethod(createTaskCallbackMethod("onPostExecute", methods.getElementForPostExecute()));
159+
callbackImplementation.addMethod(createTaskCallbackMethod("onCanceled", methods.getElementForCancel()));
160+
161+
MethodSpec.Builder progressUpdateMethod = MethodSpec.methodBuilder("onProgressUpdate")
162+
.addAnnotation(Override.class)
163+
.addModifiers(Modifier.PUBLIC)
164+
.addParameter(CLASS_TASK, "task")
165+
.addParameter(TypeName.OBJECT, "object");
166+
167+
if(methods.getElementForProgress() != null) {
168+
progressUpdateMethod.addStatement("target.$L(task, object)", methods.getElementForProgress().getSimpleName());
169+
}
170+
callbackImplementation.addMethod(progressUpdateMethod.build());
171+
172+
173+
if(methods.getElementForAttach() != null) {
174+
final boolean onlyCallOnReAttach = methods.getElementForAttach().getAnnotation(TaskAttach.class).onlyCallOnReAttach();
175+
if(onlyCallOnReAttach){
176+
getListenerForMethod.beginControlFlow("if(isReAttach)");
177+
}
178+
final List<? extends VariableElement> parameters = ((ExecutableElement) methods.getElementForAttach()).getParameters();
179+
if(parameters.size() == 0) {
180+
getListenerForMethod.addStatement("target.$L()", methods.getElementForAttach().getSimpleName());
181+
} else {
182+
getListenerForMethod.addStatement("target.$L(task)", methods.getElementForAttach().getSimpleName());
183+
}
184+
if(onlyCallOnReAttach){
185+
getListenerForMethod.endControlFlow();
186+
}
187+
}
188+
getListenerForMethod.addStatement("return $L", callbackImplementation.build());
189+
getListenerForMethod.endControlFlow();
190+
191+
}
192+
193+
// Add the default switch case
194+
getListenerForMethod.addCode("default:\n");
195+
getListenerForMethod.addStatement("return null");
196+
getListenerForMethod.endControlFlow();
197+
198+
// Create an implementation of TaskAttachBinding interface.
199+
final TypeSpec generatedClass = TypeSpec.classBuilder(bindingClassName)
200+
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
201+
.addField(TypeVariableName.get("T"), "target", Modifier.FINAL)
202+
.addMethod(constructor)
203+
.addTypeVariable(TypeVariableName.get("T", TypeName.get(forTypeElement.asType()), CLASS_TASKMANAGEROWNER))
204+
.addSuperinterface(CLASS_TASKATTACHBINDING)
205+
.addMethod(getListenerForMethod.build())
206+
.build();
207+
208+
return JavaFile.builder(bindingClassName.packageName(), generatedClass)
209+
.addFileComment("Generated code from the Android Retainable Tasks annotations processor. Do not modify!")
210+
.build();
211+
}
212+
213+
private MethodSpec createTaskCallbackMethod(String methodName, @Nullable Element methodToCall){
214+
// Create the implementation of the onPreExecute method.
215+
final MethodSpec.Builder onPreExecuteMethod = MethodSpec.methodBuilder(methodName)
216+
.addAnnotation(Override.class)
217+
.addModifiers(Modifier.PUBLIC)
218+
.addParameter(CLASS_TASK, "task");
219+
220+
if(methodToCall != null) {
221+
// There is a method known that should be called, check the parameters.
222+
final List<? extends VariableElement> parameters = ((ExecutableElement) methodToCall).getParameters();
223+
if(parameters.size() == 0){
224+
// Zero parameters, just call the method.
225+
onPreExecuteMethod.addStatement("target.$L()", methodToCall.getSimpleName());
226+
} else if(parameters.size() == 1){
227+
// One parameter, check it and call the method.
228+
final VariableElement parameter = parameters.get(0);
229+
final TypeMirror taskType = processingEnv.getTypeUtils().erasure(processingEnv.getElementUtils().getTypeElement(CLASS_TASK.reflectionName()).asType());
230+
231+
if(!processingEnv.getTypeUtils().isAssignable(parameter.asType(), taskType)){
232+
// Parameter not an instance of Task
233+
error(parameter, "Type of parameter '%s' is not an instance of '%s'!", parameter.getSimpleName(), taskType);
234+
} else {
235+
// Check if the class to cast too is accessible.
236+
final Element requiredElement = processingEnv.getTypeUtils().asElement(parameter.asType());
237+
if (!requiredElement.getModifiers().contains(Modifier.PUBLIC) && !requiredElement.getModifiers().contains(Modifier.PROTECTED)) {
238+
error(parameter, "Type of parameter '%s' is not public or protected accessible! This prevents Android-Retainable-Tasks from casting '%s' to '%s'.\nTo fix this either the type of the parameter or make the class accessible by adding the public or protected modifier!", parameter.getSimpleName(), taskType, parameter.asType().toString());
239+
}
240+
onPreExecuteMethod.addStatement("target.$L(($T) task)", methodToCall.getSimpleName(), parameter.asType());
241+
}
242+
}
243+
} else {
244+
onPreExecuteMethod.addComment("No annotated method found for $L", methodName);
245+
}
246+
return onPreExecuteMethod.build();
247+
}
248+
249+
private void error(Element element, String message, Object... args) {
250+
printMessage(Diagnostic.Kind.ERROR, element, message, args);
251+
}
252+
253+
private void note(Element element, String message, Object... args) {
254+
printMessage(Diagnostic.Kind.NOTE, element, message, args);
255+
}
256+
257+
private void printMessage(Diagnostic.Kind kind, Element element, String message, Object[] args) {
258+
if (args.length > 0) {
259+
message = String.format(message, args);
260+
}
261+
processingEnv.getMessager().printMessage(kind, message, element);
262+
}
263+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.neotech.library.retainabletasks;
2+
3+
import javax.annotation.Nullable;
4+
5+
import java.lang.annotation.Annotation;
6+
import java.util.HashMap;
7+
8+
import javax.lang.model.element.Element;
9+
10+
/**
11+
* TODO documentation
12+
*
13+
* Created by Rolf Smit on 24-May-17.
14+
*/
15+
public final class TaskBinding {
16+
17+
private final HashMap<Class<? extends Annotation>, Element> elementForType = new HashMap<>(6);
18+
19+
public boolean add(Class<? extends Annotation> annotation, Element element){
20+
if(elementForType.containsKey(annotation)){
21+
return false;
22+
}
23+
elementForType.put(annotation, element);
24+
return true;
25+
}
26+
27+
public @Nullable
28+
Element getElementForPostExecute(){
29+
return elementForType.get(TaskPostExecute.class);
30+
}
31+
public @Nullable Element getElementForPreExecute(){
32+
return elementForType.get(TaskPreExecute.class);
33+
}
34+
35+
public @Nullable Element getElementForCancel(){
36+
return elementForType.get(TaskCancel.class);
37+
}
38+
39+
public @Nullable Element getElementForProgress(){
40+
return elementForType.get(TaskProgress.class);
41+
}
42+
43+
public @Nullable Element getElementForAttach(){
44+
return elementForType.get(TaskAttach.class);
45+
}
46+
}

0 commit comments

Comments
 (0)