package shortbread;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.Filer;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.NoType;
class CodeGenerator {
private static final String EXTRA_METHOD = "shortbread_method";
private final ClassName suppressLint = ClassName.get("android.annotation", "SuppressLint");
private final ClassName context = ClassName.get("android.content", "Context");
private final ClassName shortcutInfo = ClassName.get("android.content.pm", "ShortcutInfo");
private final ClassName intent = ClassName.get("android.content", "Intent");
private final ClassName icon = ClassName.get("android.graphics.drawable", "Icon");
private final ClassName activity = ClassName.get("android.app", "Activity");
private final ClassName componentName = ClassName.get("android.content", "ComponentName");
private final ClassName list = ClassName.get("java.util", "List");
private final TypeName listOfShortcutInfo = ParameterizedTypeName.get(list, shortcutInfo);
private final TypeName listOfListOfShortcutInfo = ParameterizedTypeName.get(list, listOfShortcutInfo);
private final ClassName taskStackBuilder = ClassName.get("android.app", "TaskStackBuilder");
private final ClassName shortcutUtils = ClassName.get("shortbread", "ShortcutUtils");
private Filer filer;
private List<ShortcutAnnotatedElement> annotatedElements;
CodeGenerator(final Filer filer, final List<ShortcutAnnotatedElement> annotatedElements) {
this.filer = filer;
this.annotatedElements = annotatedElements;
}
void generate() {
TypeSpec shortbread = TypeSpec.classBuilder("ShortbreadGenerated")
.addAnnotation(AnnotationSpec.builder(suppressLint)
.addMember("value", "$S", "NewApi")
.addMember("value", "$S", "ResourceType")
.build())
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(createShortcuts())
.addMethod(callMethodShortcut())
.build();
JavaFile javaFile = JavaFile.builder("shortbread", shortbread)
.indent(" ")
.build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
private MethodSpec createShortcuts() {
final MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("createShortcuts")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(listOfListOfShortcutInfo)
.addParameter(context, "context")
.addStatement("$T<$T> enabledShortcuts = new $T<>()", List.class, shortcutInfo, ArrayList.class)
.addStatement("$T<$T> disabledShortcuts = new $T<>()", List.class, shortcutInfo, ArrayList.class);
for (final ShortcutAnnotatedElement annotatedElement : annotatedElements) {
Shortcut shortcut = annotatedElement.getShortcut();
methodBuilder.addCode("$L.add(", shortcut.enabled() ? "enabledShortcuts" : "disabledShortcuts");
methodBuilder.addCode(createShortcut(annotatedElement));
methodBuilder.addStatement(")");
}
return methodBuilder
.addStatement("return $T.asList(enabledShortcuts, disabledShortcuts)", Arrays.class)
.build();
}
private CodeBlock createShortcut(ShortcutAnnotatedElement annotatedElement) {
Shortcut shortcut = annotatedElement.getShortcut();
final CodeBlock.Builder blockBuilder = CodeBlock.builder();
blockBuilder.add("new $T.Builder(context, $S)\n", shortcutInfo, shortcut.id())
.indent()
.indent();
if (!"".equals(shortcut.shortLabel())) {
blockBuilder.add(".setShortLabel($S)\n", shortcut.shortLabel());
} else if (shortcut.shortLabelRes() != 0) {
blockBuilder.add(".setShortLabel(context.getString($L))\n", shortcut.shortLabelRes());
} else {
blockBuilder.add(".setShortLabel($T.getActivityLabel(context, $T.class))\n", shortcutUtils,
ClassName.bestGuess(annotatedElement.getClassName()));
}
if (!"".equals(shortcut.longLabel())) {
blockBuilder.add(".setLongLabel($S)\n", shortcut.longLabel());
} else if (shortcut.longLabelRes() != 0) {
blockBuilder.add(".setLongLabel(context.getString($L))\n", shortcut.longLabelRes());
}
if (shortcut.icon() != 0) {
blockBuilder.add(".setIcon($T.createWithResource(context, $L))\n", icon, shortcut.icon());
}
if (!"".equals(shortcut.disabledMessage())) {
blockBuilder.add(".setDisabledMessage($S)\n", shortcut.disabledMessage());
} else if (shortcut.disabledMessageRes() != 0) {
blockBuilder.add(".setDisabledMessage(context.getString($L))\n", shortcut.disabledMessageRes());
}
try {
shortcut.activity();
} catch (MirroredTypeException mte) {
if (!(mte.getTypeMirror() instanceof NoType)) {
DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
String targetActivityClassName = classTypeElement.getQualifiedName().toString();
blockBuilder.add(".setActivity(new $T(context, $T.class))\n", componentName,
ClassName.bestGuess(targetActivityClassName));
}
}
blockBuilder.add(".setIntents(")
.indent().indent()
.add("$T.create(context)\n", taskStackBuilder);
final ClassName activityClassName = ClassName.get((annotatedElement.getActivity()));
blockBuilder.add(".addParentStack($T.class)\n", activityClassName);
// because we can't just get an array of classes, we have to use AnnotationMirrors
final List<? extends AnnotationMirror> annotationMirrors = annotatedElement.getElement().getAnnotationMirrors();
for (final AnnotationMirror annotationMirror : annotationMirrors) {
if (annotationMirror.getAnnotationType().toString().equals(Shortcut.class.getName())) {
for (Map.Entry<? extends Element, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) {
if ("backStack".equals(entry.getKey().getSimpleName().toString())) {
final String value = entry.getValue().getValue().toString();
if (!"".equals(value.trim())) {
for (final String backstackActivityClassName : value.split(",")) {
blockBuilder.add(".addNextIntent(new $T($T.ACTION_VIEW).setClass(context, $T.class))\n",
intent, intent, ClassName.bestGuess(backstackActivityClassName.replace(".class", "")));
}
}
}
}
}
}
blockBuilder.add(".addNextIntent(new $T(context, $T.class)\n", intent, activityClassName);
blockBuilder.indent().indent();
if ("".equals(shortcut.action())) {
blockBuilder.add(".setAction($T.ACTION_VIEW)", intent);
} else {
blockBuilder.add(".setAction($S)", shortcut.action());
}
if (annotatedElement instanceof ShortcutAnnotatedMethod) {
blockBuilder.add("\n.putExtra($S, $S)", EXTRA_METHOD, ((ShortcutAnnotatedMethod) annotatedElement).getMethodName());
}
blockBuilder.unindent().unindent();
return blockBuilder.add(")\n")
.add(".getIntents())\n")
.unindent().unindent()
.add(".setRank($L)\n", shortcut.rank())
.add(".build()")
.unindent().unindent()
.build();
}
private MethodSpec callMethodShortcut() {
HashMap<String, List<String>> classMethodsMap = new HashMap<>();
for (final ShortcutAnnotatedElement annotatedElement : annotatedElements) {
if (annotatedElement instanceof ShortcutAnnotatedMethod) {
final ShortcutAnnotatedMethod annotatedMethod = (ShortcutAnnotatedMethod) annotatedElement;
if (classMethodsMap.containsKey(annotatedMethod.getClassName())) {
classMethodsMap.get(annotatedElement.getClassName()).add(annotatedMethod.getMethodName());
} else {
classMethodsMap.put(annotatedMethod.getClassName(), new ArrayList<String>() {{
add(annotatedMethod.getMethodName());
}});
}
}
}
final MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("callMethodShortcut")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(activity, "activity");
for (final Map.Entry<String, List<String>> annotatedMethodName : classMethodsMap.entrySet()) {
ClassName activityClassName = ClassName.bestGuess(annotatedMethodName.getKey());
List<String> methodNames = annotatedMethodName.getValue();
methodBuilder.beginControlFlow("if (activity instanceof $T)", activityClassName);
for (final String methodName : methodNames) {
methodBuilder.beginControlFlow("if ($S.equals(activity.getIntent().getStringExtra($S)))", methodName, EXTRA_METHOD);
methodBuilder.addStatement("(($T) activity).$L()", activityClassName, methodName);
methodBuilder.endControlFlow();
}
methodBuilder.endControlFlow();
}
return methodBuilder
.build();
}
}