package pocketknife.internal.codegen.binding;
import android.os.Build;
import com.google.common.base.CaseFormat;
import pocketknife.NotRequired;
import pocketknife.internal.codegen.Access;
import pocketknife.internal.codegen.BaseProcessor;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.lang.annotation.Annotation;
import java.util.Set;
import static javax.lang.model.element.ElementKind.CLASS;
import static javax.lang.model.element.ElementKind.METHOD;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PROTECTED;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.tools.Diagnostic.Kind.ERROR;
import static pocketknife.internal.GeneratedAdapters.ANDROID_PREFIX;
import static pocketknife.internal.GeneratedAdapters.JAVA_PREFIX;
public abstract class BindingProcessor extends BaseProcessor {
protected Messager messager;
private static final String IS = "is";
private static final String GET = "get";
private static final String SET = "set";
private static final String[] GETTER_PREFIXES = {IS, GET};
public BindingProcessor(Messager messager, Elements elements, Types types) {
super(elements, types);
this.messager = messager;
}
protected String getPackageName(TypeElement type) {
return elements.getPackageOf(type).getQualifiedName().toString();
}
protected String getClassName(TypeElement typeElement, String packageName) {
int packageLen = packageName.length() + 1;
return typeElement.getQualifiedName().toString().substring(packageLen).replace('.', '$');
}
protected void error(Element element, String message, Object... args) {
if (args.length > 0) {
message = String.format(message, args);
}
messager.printMessage(ERROR, message, element);
}
protected void validateNotRequiredArguments(Element element) {
NotRequired notRequired = element.getAnnotation(NotRequired.class);
if (notRequired != null && notRequired.value() < Build.VERSION_CODES.FROYO) {
throw new IllegalStateException("NotRequired value must be FROYO(8)+");
}
}
protected void validateForCodeGeneration(Class<? extends Annotation> annotationClass, Element element) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify method modifiers
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(STATIC)) {
throw new IllegalStateException(String.format("@%s fields must not be static. (%s.%s)",
annotationClass.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName()));
}
// Verify Containing type.
if (enclosingElement.getKind() != CLASS) {
throw new IllegalStateException(String.format("@%s fields may only be contained in classes. (%s.%s)",
annotationClass.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName()));
}
// Verify containing class visibility is not private
if (enclosingElement.getModifiers().contains(PRIVATE)) {
throw new IllegalStateException(String.format("@%s fields may not be contained in private classes (%s.%s)", annotationClass.getSimpleName(),
enclosingElement.getQualifiedName(), element.getSimpleName()));
}
}
protected Access getAccess(Class<? extends Annotation> annotationClass, Element element, TypeElement enclosingElement) {
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(PROTECTED) || modifiers.contains(PRIVATE)) {
String getter = findGetter(element);
String setter = findSetter(element);
if (getter == null || setter == null) {
throw new IllegalStateException(String.format("@%s fields must have a Java Bean getter and a setter if it is private or protected. (%s.%s)",
annotationClass.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName()));
}
return new Access(Access.Type.METHOD, getter, setter);
}
String name = element.getSimpleName().toString();
return new Access(Access.Type.FIELD, name, name);
}
private String findGetter(Element element) {
String field = element.getSimpleName().toString();
Element parent = element.getEnclosingElement();
for (Element child : parent.getEnclosedElements()) {
if (child.getKind() == ElementKind.METHOD && child instanceof ExecutableElement && ((ExecutableElement) child).getParameters().isEmpty()) {
String name = child.getSimpleName().toString();
for (String prefix : GETTER_PREFIXES) {
if (name.startsWith(prefix) && name.substring(prefix.length()).equals(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, field))) {
return name;
}
}
}
}
return null;
}
private String findSetter(Element element) {
String field = element.getSimpleName().toString();
Element parent = element.getEnclosingElement();
for (Element child : parent.getEnclosedElements()) {
if (child.getKind() == METHOD && child instanceof ExecutableElement && ((ExecutableElement) child).getParameters().size() == 1 && !(
(ExecutableElement) child).isVarArgs()) {
String name = child.getSimpleName().toString();
if (name.startsWith(SET) && name.substring(SET.length()).equals(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, field))) {
return name;
}
}
}
return null;
}
protected void validateBindingPackage(Class<? extends Annotation> annotationClass, Element element) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();
if (qualifiedName.startsWith(ANDROID_PREFIX)) {
throw new IllegalStateException(String.format("@%s-annotated class incorrectly in Android framework package. (%s)",
annotationClass.getSimpleName(), qualifiedName));
}
if (qualifiedName.startsWith(JAVA_PREFIX)) {
throw new IllegalStateException(String.format("@%s-annotated class incorrectly in Java framework package. (%s",
annotationClass.getSimpleName(), qualifiedName));
}
}
protected TypeElement findParent(TypeElement typeElement, Set<String> parents) {
TypeMirror type;
while (true) {
type = typeElement.getSuperclass();
if (type.getKind() == TypeKind.NONE) {
return null;
}
typeElement = (TypeElement) ((DeclaredType) type).asElement();
if (parents.contains(typeElement.toString())) {
return typeElement;
}
}
}
}