package com.arellomobile.mvp.compiler; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.arellomobile.mvp.DefaultView; import com.arellomobile.mvp.DefaultViewState; import com.arellomobile.mvp.InjectViewState; import com.arellomobile.mvp.MvpPresenter; import com.arellomobile.mvp.MvpProcessor; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.MirroredTypeException; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import static com.arellomobile.mvp.compiler.Util.fillGenerics; /** * Date: 19-Jan-16 * Time: 19:51 * * @author Alexander Blinov */ final class ViewStateProviderClassGenerator extends ClassGenerator<TypeElement> { public static final String MVP_PRESENTER_CLASS = MvpPresenter.class.getCanonicalName(); private Set<TypeElement> mUsedViews; private List<String> mPresenterClassNames; public ViewStateProviderClassGenerator() { mUsedViews = new HashSet<>(); mPresenterClassNames = new ArrayList<>(); } @Override public boolean generate(TypeElement typeElement, List<ClassGeneratingParams> classGeneratingParamsList) { String fullPresenterClassName = typeElement.toString(); mPresenterClassNames.add(fullPresenterClassName); final String presenterClassName = fullPresenterClassName.substring(fullPresenterClassName.lastIndexOf(".") + 1); String viewState = getViewStateClassFromAnnotationParams(typeElement); if (viewState == null) { String view = getViewClassFromAnnotationParams(typeElement); if (view == null) { view = getViewClassFromGeneric(typeElement); } if (view != null) { // Remove generic from view class name if (view.contains("<")) { view = view.substring(0, view.indexOf("<")); } TypeElement viewTypeElement = MvpCompiler.getElementUtils().getTypeElement(view); if (viewTypeElement == null) { throw new IllegalArgumentException("View \"" + view + "\" for " + typeElement + " cannot be found"); } mUsedViews.add(viewTypeElement); viewState = Util.getFullClassName(viewTypeElement) + MvpProcessor.VIEW_STATE_SUFFIX; } } String builder = "package " + fullPresenterClassName.substring(0, fullPresenterClassName.lastIndexOf(".")) + ";\n" + "\n" + "import com.arellomobile.mvp.ViewStateProvider;\n" + "import com.arellomobile.mvp.MvpView;\n" + "import com.arellomobile.mvp.viewstate.MvpViewState;\n" + "\npublic class " + presenterClassName + MvpProcessor.VIEW_STATE_PROVIDER_SUFFIX + " extends ViewStateProvider {\n" + "\t\n" + "\t@Override\n" + "\tpublic MvpViewState<? extends MvpView> getViewState() {\n"; if (viewState == null) { builder += "\t\tthrow new RuntimeException(" + fullPresenterClassName + " should has view\");\n"; } else { builder += "\t\treturn new " + viewState + "();\n"; } builder += "\t}\n" + "}"; ClassGeneratingParams classGeneratingParams = new ClassGeneratingParams(); classGeneratingParams.setName(fullPresenterClassName + MvpProcessor.VIEW_STATE_PROVIDER_SUFFIX); classGeneratingParams.setBody(builder); classGeneratingParamsList.add(classGeneratingParams); return true; } private String getViewClassFromAnnotationParams(TypeElement typeElement) { InjectViewState annotation = typeElement.getAnnotation(InjectViewState.class); String mvpViewClassName = ""; if (annotation != null) { TypeMirror value = null; try { annotation.view(); } catch (MirroredTypeException mte) { value = mte.getTypeMirror(); } mvpViewClassName = Util.getFullClassName(value); } if (mvpViewClassName.isEmpty() || DefaultView.class.getName().equals(mvpViewClassName)) { return null; } return mvpViewClassName; } private String getViewStateClassFromAnnotationParams(TypeElement typeElement) { InjectViewState annotation = typeElement.getAnnotation(InjectViewState.class); String mvpViewStateClassName = ""; if (annotation != null) { TypeMirror value; try { annotation.value(); } catch (MirroredTypeException mte) { value = mte.getTypeMirror(); mvpViewStateClassName = value.toString(); } } if (mvpViewStateClassName.isEmpty() || DefaultViewState.class.getName().equals(mvpViewStateClassName)) { return null; } return mvpViewStateClassName; } private String getViewClassFromGeneric(TypeElement typeElement) { TypeMirror superclass = typeElement.asType(); Map<String, String> parentTypes = Collections.emptyMap(); if (!typeElement.getTypeParameters().isEmpty()) { MvpCompiler.getMessager().printMessage(Diagnostic.Kind.WARNING, "Your " + typeElement.getSimpleName() + " is typed. @InjectViewState may generate wrong code. Your can set view/view state class manually."); } while (superclass.getKind() != TypeKind.NONE) { TypeElement superclassElement = (TypeElement) ((DeclaredType) superclass).asElement(); final List<? extends TypeMirror> typeArguments = ((DeclaredType) superclass).getTypeArguments(); final List<? extends TypeParameterElement> typeParameters = superclassElement.getTypeParameters(); if (typeArguments.size() > typeParameters.size()) { throw new IllegalArgumentException("Code generation for interface " + typeElement.getSimpleName() + " failed. Simplify your generics. (" + typeArguments + " vs " + typeParameters + ")"); } Map<String, String> types = new HashMap<>(); for (int i = 0; i < typeArguments.size(); i++) { types.put(typeParameters.get(i).toString(), fillGenerics(parentTypes, typeArguments.get(i))); } if (superclassElement.toString().equals(MVP_PRESENTER_CLASS)) { // MvpPresenter is typed only on View class return fillGenerics(parentTypes, typeArguments); } parentTypes = types; superclass = superclassElement.getSuperclass(); } return ""; } public Set<TypeElement> getUsedViews() { return mUsedViews; } public List<String> getPresenterClassNames() { return mPresenterClassNames; } }