package com.arellomobile.mvp.compiler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import com.arellomobile.mvp.MvpPresenter;
import com.arellomobile.mvp.MvpView;
import com.arellomobile.mvp.presenter.InjectPresenter;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.tools.Diagnostic;
import static com.arellomobile.mvp.compiler.Util.fillGenerics;
/**
* Date: 17-Feb-16
* Time: 17:00
*
* @author esorokin
*/
public class PresenterInjectorRules extends AnnotationRule {
public PresenterInjectorRules(ElementKind validKind, Modifier... validModifiers) {
super(validKind, validModifiers);
}
@SuppressWarnings("StringConcatenationInsideStringBufferAppend")
@Override
public void checkAnnotation(Element annotatedField) {
checkEnvironment(annotatedField);
if (annotatedField.getKind() != mValidKind) {
mErrorBuilder.append("Field " + annotatedField + " of " + annotatedField.getEnclosingElement().getSimpleName() + " should be " + mValidKind.name() + ", or not mark it as @" + InjectPresenter.class.getSimpleName()).append("\n");
}
for (Modifier modifier : annotatedField.getModifiers()) {
if (!mValidModifiers.contains(modifier)) {
mErrorBuilder.append("Field " + annotatedField + " of " + annotatedField.getEnclosingElement().getSimpleName() + " can't be a " + modifier).append(". Use ").append(validModifiersToString()).append("\n");
}
}
Element enclosingElement = annotatedField.getEnclosingElement();
while (enclosingElement.getKind() == ElementKind.CLASS) {
if (!enclosingElement.getModifiers().contains(Modifier.PUBLIC)) {
mErrorBuilder.append(enclosingElement.getSimpleName() + " should be PUBLIC ");
break;
}
enclosingElement = enclosingElement.getEnclosingElement();
}
}
private void checkEnvironment(final Element annotatedField) {
if (!(annotatedField.asType() instanceof DeclaredType)) {
return;
}
TypeElement typeElement = (TypeElement) ((DeclaredType) annotatedField.asType()).asElement();
String viewClassFromGeneric = getViewClassFromGeneric(typeElement, (DeclaredType) annotatedField.asType());
Collection<TypeMirror> viewsType = getViewsType((TypeElement) ((DeclaredType) annotatedField.getEnclosingElement().asType()).asElement());
boolean result = false;
for (TypeMirror typeMirror : viewsType) {
if (Util.getFullClassName(typeMirror).equals(viewClassFromGeneric) || Util.fillGenerics(Collections.<String, String>emptyMap(), typeMirror).equals(viewClassFromGeneric)) {
result = true;
break;
}
}
if (!result) {
MvpCompiler.getMessager().printMessage(Diagnostic.Kind.ERROR, "You can not use @InjectPresenter in classes that are not View, which is typified target Presenter", annotatedField);
}
}
private String getViewClassFromGeneric(TypeElement typeElement, DeclaredType declaredType) {
TypeMirror superclass = declaredType;
Map<TypeParameterElement, TypeMirror> mTypedMap = Collections.emptyMap();
if (!typeElement.getTypeParameters().isEmpty()) {
mTypedMap = getChildInstanceOfClassFromGeneric(typeElement, MvpView.class);
}
Map<String, String> parentTypes = Collections.emptyMap();
List<? extends TypeMirror> totalTypeArguments = new ArrayList<>(((DeclaredType) superclass).getTypeArguments());
while (superclass.getKind() != TypeKind.NONE) {
TypeElement superclassElement = (TypeElement) ((DeclaredType) superclass).asElement();
List<? extends TypeMirror> typeArguments = ((DeclaredType) superclass).getTypeArguments();
totalTypeArguments.retainAll(typeArguments);
final List<? extends TypeParameterElement> typeParameters = superclassElement.getTypeParameters();
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(MvpPresenter.class.getCanonicalName())) {
if (!typeArguments.isEmpty()) {
TypeMirror typeMirror = typeArguments.get(0);
if (typeMirror instanceof TypeVariable) {
Element key = ((TypeVariable) typeMirror).asElement();
for (Map.Entry<TypeParameterElement, TypeMirror> entry : mTypedMap.entrySet()) {
if (entry.getKey().toString().equals(key.toString())) {
return Util.getFullClassName(entry.getValue());
}
}
}
}
if (typeArguments.isEmpty() && typeParameters.isEmpty()) {
return ((DeclaredType) superclass).asElement().getSimpleName().toString();
}
// MvpPresenter is typed only on View class
return fillGenerics(parentTypes, typeArguments);
}
parentTypes = types;
superclass = superclassElement.getSuperclass();
}
return "";
}
private Map<TypeParameterElement, TypeMirror> getChildInstanceOfClassFromGeneric(final TypeElement typeElement, final Class<?> aClass) {
Map<TypeParameterElement, TypeMirror> result = new HashMap<>();
for (TypeParameterElement element : typeElement.getTypeParameters()) {
List<? extends TypeMirror> bounds = element.getBounds();
for (TypeMirror bound : bounds) {
if (bound instanceof DeclaredType && ((DeclaredType) bound).asElement() instanceof TypeElement) {
Collection<TypeMirror> viewsType = getViewsType((TypeElement) ((DeclaredType) bound).asElement());
boolean isViewType = false;
for (TypeMirror viewType : viewsType) {
if (((DeclaredType) viewType).asElement().toString().equals(aClass.getCanonicalName())) {
isViewType = true;
}
}
if (isViewType) {
result.put(element, bound);
break;
}
}
}
}
return result;
}
private Collection<TypeMirror> getViewsType(TypeElement typeElement) {
TypeMirror superclass = typeElement.asType();
List<TypeMirror> result = new ArrayList<>();
while (superclass.getKind() != TypeKind.NONE) {
TypeElement superclassElement = (TypeElement) ((DeclaredType) superclass).asElement();
Collection<? extends TypeMirror> interfaces = new HashSet<>(superclassElement.getInterfaces());
for (TypeMirror typeMirror : interfaces) {
if (typeMirror instanceof DeclaredType) {
result.addAll(getViewsType((TypeElement) ((DeclaredType) typeMirror).asElement()));
}
}
result.addAll(interfaces);
result.add(superclass);
superclass = superclassElement.getSuperclass();
}
return result;
}
}