package com.arellomobile.mvp.compiler; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.arellomobile.mvp.MvpProcessor; import com.arellomobile.mvp.presenter.InjectPresenter; import com.arellomobile.mvp.presenter.PresenterType; import com.arellomobile.mvp.presenter.ProvidePresenter; import com.arellomobile.mvp.presenter.ProvidePresenterTag; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; /** * 18.12.2015 * <p> * Generates PresenterBinder for class annotated with @InjectPresenters * <p> * for Sample class with single injected presenter * <pre> * {@code * * @InjectPresenters * public class Sample extends MvpActivity implements MyView * { * * @InjectPresenter(type = PresenterType.LOCAL, tag = "SOME_TAG") * com.arellomobile.example.MyPresenter mMyPresenter; * * } * * } * </pre> * <p> * PresenterBinderClassGenerator generates PresenterBinder * <p> * * @author Yuri Shmakov * @author Alexander Blinov */ final class PresenterBinderClassGenerator extends ClassGenerator<VariableElement> { public static final String PRESENTER_FIELD_ANNOTATION = InjectPresenter.class.getName(); public static final String PROVIDE_PRESENTER = ProvidePresenter.class.getName(); public static final String PROVIDE_PRESENTER_TAG = ProvidePresenterTag.class.getName(); private final Set<TypeElement> mPresentersContainers; public PresenterBinderClassGenerator() { mPresentersContainers = new HashSet<>(); } public boolean generate(VariableElement variableElement, List<ClassGeneratingParams> classGeneratingParamsList) { final Element enclosingElement = variableElement.getEnclosingElement(); if (!(enclosingElement instanceof TypeElement)) { throw new RuntimeException("Only class fields could be annotated as @InjectPresenter: " + variableElement + " at " + enclosingElement); } if (mPresentersContainers.contains(enclosingElement)) { return false; } TypeElement presentersContainer = (TypeElement) enclosingElement; mPresentersContainers.add(presentersContainer); String fullClassName = Util.getFullClassName(presentersContainer); ClassGeneratingParams classGeneratingParams = new ClassGeneratingParams(); classGeneratingParams.setName(fullClassName + MvpProcessor.PRESENTER_BINDER_SUFFIX); String parentClassName = presentersContainer.toString(); final String viewClassName = fullClassName.substring(fullClassName.lastIndexOf(".") + 1); String builder = "package " + fullClassName.substring(0, fullClassName.lastIndexOf(".")) + ";\n" + "\n" + "import java.util.ArrayList;\n" + "import java.util.List;\n" + "\n" + "import com.arellomobile.mvp.PresenterBinder;\n" + "import com.arellomobile.mvp.presenter.PresenterField;\n" + "import com.arellomobile.mvp.MvpPresenter;\n" + "import com.arellomobile.mvp.presenter.PresenterType;\n" + "\n" + "public class " + viewClassName + MvpProcessor.PRESENTER_BINDER_SUFFIX + " extends PresenterBinder<" + parentClassName + "> {\n"; List<Field> fields = collectFields(presentersContainer); List<PresenterProvider> presenterProviders = collectPresenterProviders(presentersContainer); List<TagProvider> tagProviders = collectTagProviders(presentersContainer); bindProvidersToFields(fields, presenterProviders); bindTagProvidersToFields(fields, tagProviders); for (Field field : fields) { builder = generatePresenterBinderClass(builder, parentClassName, field); } builder = generateGetPresentersMethod(builder, fields, parentClassName); builder += "}\n"; classGeneratingParams.setBody(builder); classGeneratingParamsList.add(classGeneratingParams); return true; } private void bindProvidersToFields(List<Field> fields, List<PresenterProvider> presenterProviders) { if (fields.isEmpty() || presenterProviders.isEmpty()) { return; } for (PresenterProvider presenterProvider : presenterProviders) { TypeMirror providerTypeMirror = presenterProvider.mClazz.asElement().asType(); for (Field field : fields) { if ((field.mClazz).equals(providerTypeMirror)) { if (field.mType != presenterProvider.mType) { continue; } if (field.mTag == null && presenterProvider.mTag != null) { continue; } if (field.mTag != null && !field.mTag.equals(presenterProvider.mTag)) { continue; } if (field.mPresenterId == null && presenterProvider.mPresenterId != null) { continue; } if (field.mPresenterId != null && !field.mPresenterId.equals(presenterProvider.mPresenterId)) { continue; } field.setPresenterProviderMethodName(presenterProvider.mName); } } } } private void bindTagProvidersToFields(List<Field> fields, List<TagProvider> tagProviders) { if (fields.isEmpty() || tagProviders.isEmpty()) { return; } for (TagProvider tagProvider : tagProviders) { TypeMirror providerTypeMirror = tagProvider.mPresenterClass.asElement().asType(); for (Field field : fields) { if ((field.mClazz).equals(providerTypeMirror)) { if (field.mType != tagProvider.mType) { continue; } if (field.mPresenterId == null && tagProvider.mPresenterId != null) { continue; } if (field.mPresenterId != null && !field.mPresenterId.equals(tagProvider.mPresenterId)) { continue; } field.setPresenterTagProviderMethodName(tagProvider.mMethodName); } } } } private List<Field> collectFields(TypeElement presentersContainer) { List<Field> fields = new ArrayList<>(); outer: for (Element element : presentersContainer.getEnclosedElements()) { if (!(element instanceof VariableElement)) { continue; } final VariableElement presenterFieldElement = (VariableElement) element; for (AnnotationMirror annotationMirror : presenterFieldElement.getAnnotationMirrors()) { if (annotationMirror.getAnnotationType().asElement().toString().equals(PRESENTER_FIELD_ANNOTATION)) { String type = null; String tag = null; String presenterId = null; final String name = element.toString(); TypeMirror clazz = ((DeclaredType) element.asType()).asElement().asType(); final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues(); final Set<? extends ExecutableElement> keySet = elementValues.keySet(); for (ExecutableElement executableElement : keySet) { String key = executableElement.getSimpleName().toString(); if ("type".equals(key)) { type = elementValues.get(executableElement).getValue().toString(); } else if ("tag".equals(key)) { tag = elementValues.get(executableElement).toString(); } else if ("presenterId".equals(key)) { presenterId = elementValues.get(executableElement).toString(); } } Field field = new Field(clazz, name, type, tag, presenterId); fields.add(field); continue outer; } } } return fields; } private List<PresenterProvider> collectPresenterProviders(TypeElement presentersContainer) { List<PresenterProvider> providers = new ArrayList<>(); outer: for (Element element : presentersContainer.getEnclosedElements()) { if (!(element instanceof ExecutableElement)) { continue; } final ExecutableElement providerMethod = (ExecutableElement) element; for (AnnotationMirror annotationMirror : providerMethod.getAnnotationMirrors()) { if (annotationMirror.getAnnotationType().asElement().toString().equals(PROVIDE_PRESENTER)) { if (providerMethod.getReturnType().getKind() != TypeKind.DECLARED) { continue; } DeclaredType kind = ((DeclaredType) providerMethod.getReturnType()); String type = null; String tag = null; String presenterId = null; final String name = providerMethod.getSimpleName().toString(); final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues(); final Set<? extends ExecutableElement> keySet = elementValues.keySet(); for (ExecutableElement executableElement : keySet) { String key = executableElement.getSimpleName().toString(); if ("type".equals(key)) { type = elementValues.get(executableElement).getValue().toString(); } else if ("tag".equals(key)) { tag = elementValues.get(executableElement).toString(); } else if ("presenterId".equals(key)) { presenterId = elementValues.get(executableElement).toString(); } } PresenterProvider provider = new PresenterProvider(kind, name, type, tag, presenterId); providers.add(provider); continue outer; } } } return providers; } private List<TagProvider> collectTagProviders(TypeElement presentersContainer) { List<TagProvider> providers = new ArrayList<>(); outer: for (Element element : presentersContainer.getEnclosedElements()) { if (!(element instanceof ExecutableElement)) { continue; } final ExecutableElement providerMethod = (ExecutableElement) element; for (AnnotationMirror annotationMirror : providerMethod.getAnnotationMirrors()) { if (annotationMirror.getAnnotationType().asElement().toString().equals(PROVIDE_PRESENTER_TAG)) { if (providerMethod.getReturnType().getKind() != TypeKind.DECLARED) { continue; } DeclaredType kind = null; String type = null; String presenterId = null; final String name = providerMethod.getSimpleName().toString(); final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues(); final Set<? extends ExecutableElement> keySet = elementValues.keySet(); for (ExecutableElement executableElement : keySet) { String key = executableElement.getSimpleName().toString(); if ("presenterClass".equals(key)) { kind = (DeclaredType) elementValues.get(executableElement).getValue(); } else if ("type".equals(key)) { type = elementValues.get(executableElement).getValue().toString(); } else if ("presenterId".equals(key)) { presenterId = elementValues.get(executableElement).toString(); } } TagProvider provider = new TagProvider(kind, name, type, presenterId); providers.add(provider); continue outer; } } } return providers; } public Set<TypeElement> getPresentersContainers() { return mPresentersContainers; } private static String generateGetPresentersMethod(final String builder, final List<Field> fields, String parentClassName) { String s = "\tpublic List<PresenterField<" + parentClassName + ">> getPresenterFields() {\n" + "\t\tList<PresenterField<" + parentClassName + ">> presenters = new ArrayList<>();\n" + "\n"; for (Field field : fields) { s += "\t\tpresenters.add(new " + field.getGeneratedClassName() + "());\n"; } s += "\n" + "\t\treturn presenters;\n" + "\t}\n" + "\n"; return builder + s; } private static String generatePresenterBinderClass(final String builder, String targetClass, final Field field) { TypeElement clazz = (TypeElement) ((DeclaredType) field.getClazz()).asElement(); String tag = field.getTag(); tag = tag != null ? tag : "\"" + field.getName() + "\""; String s = "\tpublic class " + field.getGeneratedClassName() + " extends PresenterField<" + targetClass + "> {\n" + "\t\tpublic " + field.getGeneratedClassName() + "() {\n" + "\t\t\tsuper(" + tag + ", PresenterType." + field.getType().name() + ", " + field.getPresenterId() + ", " + clazz + ".class);\n" + "\t\t}\n" + "\n" + "\t\t@Override\n" + "\t\tpublic void bind(" + targetClass + " target, MvpPresenter presenter) {\n" + "\t\t\ttarget." + field.getName() + " = (" + clazz.getQualifiedName() + ") presenter;\n" + "\t\t}\n"; s += "\n" + "\t\t@Override\n" + "\t\tpublic MvpPresenter<?> providePresenter(" + targetClass + " delegated) {\n"; if (field.getPresenterProviderMethodName() != null) { s+= "\t\t\treturn delegated." + field.getPresenterProviderMethodName() + "();\n"; } else { boolean hasEmptyConstructor = false; List<? extends Element> enclosedElements = clazz.getEnclosedElements(); for (Element enclosedElement : enclosedElements) { if (enclosedElement.getKind() == ElementKind.CONSTRUCTOR) { List<? extends VariableElement> parameters = ((ExecutableElement) enclosedElement).getParameters(); if (parameters == null || parameters.isEmpty()) { hasEmptyConstructor = true; break; } } } if (hasEmptyConstructor) { s += "\t\t\treturn new " + clazz.getQualifiedName() + "();\n"; } else { s += "\t\t\tthrow new IllegalStateException(\"" + clazz.getSimpleName() + " has not default constructor. You can apply @ProvidePresenter to some method which will construct Presenter. Also you can make it default constructor\");\n"; } } s += "\t\t}\n"; if (field.getPresenterTagProviderMethodName() != null) { s += "\n" + "\t\t@Override\n" + "\t\tpublic String getTag(" + targetClass + " delegated) {\n" + "\t\t\treturn String.valueOf(delegated." + field.getPresenterTagProviderMethodName() + "());\n" + "\t\t}\n"; } s += "\t}\n" + "\n"; return builder + s; } private static class Field { private final TypeMirror mClazz; private final String mName; private final PresenterType mType; private final String mTag; private final String mPresenterId; private String mPresenterProviderMethodName; private String mPresenterTagProviderMethodName; Field(final TypeMirror clazz, final String name, final String type, final String tag, String presenterId) { mClazz = clazz; mName = name; mTag = tag; if (type == null) { mType = PresenterType.LOCAL; } else { mType = PresenterType.valueOf(type); } mPresenterId = presenterId; } public TypeMirror getClazz() { return mClazz; } public String getGeneratedClassName() { return mName + MvpProcessor.PRESENTER_BINDER_INNER_SUFFIX; } public String getTag() { return mTag; } public String getName() { return mName; } public PresenterType getType() { return mType; } public String getPresenterId() { return mPresenterId; } public String getPresenterProviderMethodName() { return mPresenterProviderMethodName; } public void setPresenterProviderMethodName(String presenterProviderMethodName) { mPresenterProviderMethodName = presenterProviderMethodName; } public String getPresenterTagProviderMethodName() { return mPresenterTagProviderMethodName; } public void setPresenterTagProviderMethodName(String presenterTagProviderMethodName) { mPresenterTagProviderMethodName = presenterTagProviderMethodName; } @Override public String toString() { return "Field{" + "mPresenterClass=" + mClazz + ", mMethodName='" + mName + '\'' + ", mType=" + mType + ", mTag='" + mTag + '\'' + ", mPresenterId='" + mPresenterId + '\'' + ", mPresenterProviderMethodName='" + mPresenterProviderMethodName + '\'' + '}'; } } private class PresenterProvider { private final DeclaredType mClazz; private final String mName; private final PresenterType mType; private final String mTag; private final String mPresenterId; public PresenterProvider(DeclaredType clazz, String name, String type, String tag, String presenterId) { mClazz = clazz; mName = name; if (type == null) { mType = PresenterType.LOCAL; } else { mType = PresenterType.valueOf(type); } mTag = tag; mPresenterId = presenterId; } @Override public String toString() { return "PresenterProvider{" + "mPresenterClass=" + mClazz + ", mMethodName='" + mName + '\'' + ", mType=" + mType + ", mTag='" + mTag + '\'' + ", mPresenterId='" + mPresenterId + '\'' + '}'; } } private class TagProvider { private final DeclaredType mPresenterClass; private final String mMethodName; private final PresenterType mType; private final String mPresenterId; public TagProvider(DeclaredType presenterClass, String methodName, String type, String presenterId) { mPresenterClass = presenterClass; mMethodName = methodName; if (type == null) { mType = PresenterType.LOCAL; } else { mType = PresenterType.valueOf(type); } mPresenterId = presenterId; } } }