diff --git a/app/src/main/java/com/ebr163/android/attributesdispatcher/MyCustomView.java b/app/src/main/java/com/ebr163/android/attributesdispatcher/MyCustomView.java new file mode 100644 index 0000000..ce233f6 --- /dev/null +++ b/app/src/main/java/com/ebr163/android/attributesdispatcher/MyCustomView.java @@ -0,0 +1,42 @@ +package com.ebr163.android.attributesdispatcher; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.EditText; + +import com.ebr163.attributesdispatcher.Attribute; +import com.ebr163.attributesdispatcher.CustomView; +import com.ebr163.attributesdispatcher.attr.ColorAttr; +import com.ebr163.attributesdispatcher.attr.StringAttr; + +/** + * Created by mac1 on 24.11.16. + */ + +@CustomView +public class MyCustomView extends EditText { + + @ColorAttr("custom_color") + protected int color; + @StringAttr("custom_text") + protected String text; + + public MyCustomView(Context context) { + super(context); + } + + public MyCustomView(Context context, AttributeSet attrs) { + super(context, attrs); + MyCustomViewAttribute.init(this, attrs); + } + + public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + MyCustomViewAttribute.init(this, attrs); + } + + @Attribute + protected void setCustomAttr(@ColorAttr("custom_color") int color) { + this.setTextColor(color); + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index f1702f2..0c5d6d9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,18 +1,26 @@ - + android:paddingTop="@dimen/activity_vertical_margin"> + + - - + app:custom_text="Text2" + app:custom_color="@android:color/holo_red_dark" /> + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..7ac7c2d --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/compiler/src/main/java/com/ebr163/attributesdispatcher/internal/AttributesProcessor.java b/compiler/src/main/java/com/ebr163/attributesdispatcher/internal/AttributesProcessor.java index c427bb5..3c50b5e 100644 --- a/compiler/src/main/java/com/ebr163/attributesdispatcher/internal/AttributesProcessor.java +++ b/compiler/src/main/java/com/ebr163/attributesdispatcher/internal/AttributesProcessor.java @@ -1,14 +1,220 @@ package com.ebr163.attributesdispatcher.internal; +import com.ebr163.attributesdispatcher.Attribute; +import com.ebr163.attributesdispatcher.CustomView; +import com.ebr163.attributesdispatcher.attr.BooleanAttr; +import com.ebr163.attributesdispatcher.attr.ColorAttr; +import com.ebr163.attributesdispatcher.attr.DimenAttr; +import com.ebr163.attributesdispatcher.attr.FloatAttr; +import com.ebr163.attributesdispatcher.attr.IntAttr; +import com.ebr163.attributesdispatcher.attr.ReferenceAttr; +import com.ebr163.attributesdispatcher.attr.StringAttr; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +@SupportedAnnotationTypes("com.ebr163.attributesdispatcher.CustomView") public class AttributesProcessor extends AbstractProcessor { + private static final String METHOD_PREFIX = "Generation"; + + private ClassName AttributeSet = ClassName.get("android.util", "AttributeSet"); + private ClassName TypedArray = ClassName.get("android.content.res", "TypedArray"); + private ClassName Build = ClassName.get("android.os", "Build"); + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public Set getSupportedAnnotationTypes() { + Set set = new HashSet<>(); + set.add(CustomView.class.getCanonicalName()); + return Collections.unmodifiableSet(set); + } + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - return false; + for (Element element : roundEnv.getElementsAnnotatedWith(CustomView.class)) { + JavaFile javaFile = createJavaFile(new CustomViewElement((TypeElement) element)); + try { + javaFile.writeTo(processingEnv.getFiler()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return true; + } + + private JavaFile createJavaFile(CustomViewElement customViewElement) { + return JavaFile.builder(customViewElement.packageName, createTypeSpec(customViewElement)) + .addFileComment("This file was generated by AttrGenerator. Do not modify!") + .build(); + } + + private TypeSpec createTypeSpec(CustomViewElement customViewElement) { + TypeSpec.Builder builder = TypeSpec.classBuilder(customViewElement.generatedClassName) + .addModifiers(Modifier.FINAL) + .addMethod(createConstructor()) + .addMethods(createAttrMethods(customViewElement)) + .addMethod(createInitMethod(customViewElement)); + return builder.build(); + + + } + + private MethodSpec createConstructor() { + return MethodSpec.constructorBuilder() + .addModifiers(Modifier.PRIVATE) + .build(); + } + + private MethodSpec createInitMethod(CustomViewElement customViewElement) { + MethodSpec.Builder builder = MethodSpec.methodBuilder("init") + .addTypeVariables(customViewElement.typeVariables) + .addModifiers(Modifier.STATIC) + .returns(TypeName.VOID) + .addParameter(customViewElement.typeName, "target") + .addParameter(ParameterSpec.builder(AttributeSet, "attributeSet").build()); + + addAttrGenBodyInit(builder, customViewElement); + return builder.build(); + } + + private List createAttrMethods(CustomViewElement customViewElement) { + List methods = new ArrayList<>(); + for (ExecutableElement method : customViewElement.attrElements) { + methods.add(createAttrGenMethod(customViewElement, method)); + } + + return methods; + } + + private void addAttrGenBodyInit(MethodSpec.Builder builder, CustomViewElement customViewElement) { + builder.addStatement("$T typedArray = $N.getContext().getTheme().obtainStyledAttributes(\n" + + " $N,\n" + + " R.styleable.$L,\n" + + " 0, 0)", TypedArray, "target", "attributeSet", customViewElement.inputClassName); + + addInitTargetField(builder, customViewElement); + + for (ExecutableElement method : customViewElement.attrElements) { + builder.addStatement(method.getSimpleName().toString() + METHOD_PREFIX + "($N, $N)", "target", "typedArray"); + } + builder.addStatement("$N.recycle()", "typedArray"); + } + + private void addInitTargetField(MethodSpec.Builder builder, CustomViewElement customViewElement) { + for (Element field : customViewElement.booleanElements) { + String value = field.getAnnotation(BooleanAttr.class).value(); + builder.addStatement("$N." + field.getSimpleName().toString() + " = $N.getBoolean(R.styleable.$L_$L, false)", "target", "typedArray", customViewElement.inputClassName, value); + } + + for (Element field : customViewElement.colorElements) { + String value = field.getAnnotation(ColorAttr.class).value(); + builder.addStatement("$N." + field.getSimpleName().toString() + " = $N.getColor(R.styleable.$L_$L, 0)", "target", "typedArray", customViewElement.inputClassName, value); + } + + for (Element field : customViewElement.dimenElements) { + String value = field.getAnnotation(DimenAttr.class).value(); + builder.addStatement("$N." + field.getSimpleName().toString() + " = $N.getDimension(R.styleable.$L_$L, 0f)", "target", "typedArray", customViewElement.inputClassName, value); + } + + for (Element field : customViewElement.floatElements) { + String value = field.getAnnotation(FloatAttr.class).value(); + builder.addStatement("$N." + field.getSimpleName().toString() + " = $N.getFloat(R.styleable.$L_$L, 0f)", "target", "typedArray", customViewElement.inputClassName, value); + } + + for (Element field : customViewElement.integerElements) { + String value = field.getAnnotation(IntAttr.class).value(); + builder.addStatement("$N." + field.getSimpleName().toString() + " = $N.getInt(R.styleable.$L_$L, 0)", "target", "typedArray", customViewElement.inputClassName, value); + } + + for (Element field : customViewElement.referenceElements) { + String value = field.getAnnotation(ReferenceAttr.class).value(); + builder.addStatement("$N." + field.getSimpleName().toString() + " = $N.getResourceId(R.styleable.$L_$L, -1)", "target", "typedArray", customViewElement.inputClassName, value); + } + + for (Element field : customViewElement.stringElements) { + String value = field.getAnnotation(StringAttr.class).value(); + builder.addStatement("$N." + field.getSimpleName().toString() + " = $N.getString(R.styleable.$L_$L)", "target", "typedArray", customViewElement.inputClassName, value); + } + } + + private MethodSpec createAttrGenMethod(CustomViewElement customViewElement, ExecutableElement method) { + MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getSimpleName().toString() + METHOD_PREFIX) + .addTypeVariables(customViewElement.typeVariables) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(TypeName.VOID) + .addParameter(customViewElement.typeName, "target") + .addParameter(ParameterSpec.builder(TypedArray, "typedArray").build()); + + addAttrGenBody(builder, method, customViewElement); + return builder.build(); + } + + private void addAttrGenBody(MethodSpec.Builder builder, ExecutableElement method, CustomViewElement customViewElement) { + int maxSdkVersion = method.getAnnotation(Attribute.class).maxSdkVersion(); + builder.beginControlFlow("if ($T.VERSION.SDK_INT < $L)", Build, maxSdkVersion); + builder.addStatement("return"); + builder.endControlFlow(); + + String params = ""; + for (int i = 0; i < method.getParameters().size(); i++) { + TypeName type = TypeName.get(method.getParameters().get(i).asType()); + String name = method.getParameters().get(i).getSimpleName().toString(); + + Element parameter = method.getParameters().get(i); + DeclaredType annotation = parameter.getAnnotationMirrors().get(0).getAnnotationType(); + + if (annotation.toString().equals(IntAttr.class.getCanonicalName())) { + String key = parameter.getAnnotation(IntAttr.class).value(); + builder.addStatement("$T $N = $N.getInt(R.styleable.$L_$L, 0)", type, name, "typedArray", customViewElement.inputClassName, key); + } else if (annotation.toString().equals(BooleanAttr.class.getCanonicalName())) { + String key = parameter.getAnnotation(BooleanAttr.class).value(); + builder.addStatement("$T $N = $N.getBoolean(R.styleable.$L_$L, false)", type, name, "typedArray", customViewElement.inputClassName, key); + } else if (annotation.toString().equals(ColorAttr.class.getCanonicalName())) { + String key = parameter.getAnnotation(ColorAttr.class).value(); + builder.addStatement("$T $N = $N.getColor(R.styleable.$L_$L, 0)", type, name, "typedArray", customViewElement.inputClassName, key); + } else if (annotation.toString().equals(DimenAttr.class.getCanonicalName())) { + String key = parameter.getAnnotation(DimenAttr.class).value(); + builder.addStatement("$T $N = $N.getDimension(R.styleable.$L_$L, 0f)", type, name, "typedArray", customViewElement.inputClassName, key); + } else if (annotation.toString().equals(FloatAttr.class.getCanonicalName())) { + String key = parameter.getAnnotation(FloatAttr.class).value(); + builder.addStatement("$T $N = $N.getFloat(R.styleable.$L_$L, 0f)", type, name, "typedArray", customViewElement.inputClassName, key); + } else if (annotation.toString().equals(StringAttr.class.getCanonicalName())) { + String key = parameter.getAnnotation(StringAttr.class).value(); + builder.addStatement("$T $N = $N.getString(R.styleable.$L_$L)", type, name, "typedArray", customViewElement.inputClassName, key); + } else if (annotation.toString().equals(ReferenceAttr.class.getCanonicalName())) { + String key = parameter.getAnnotation(ReferenceAttr.class).value(); + builder.addStatement("$T $N = $N.getResourceId(R.styleable.$L_$L, -1)", type, name, "typedArray", customViewElement.inputClassName, key); + } + if (i == 0) { + params += name; + } else { + params += ", " + name; + } + } + builder.addStatement("$N." + method.getSimpleName().toString() + "($L)", "target", params); } } diff --git a/compiler/src/main/java/com/ebr163/attributesdispatcher/internal/CustomViewElement.java b/compiler/src/main/java/com/ebr163/attributesdispatcher/internal/CustomViewElement.java new file mode 100644 index 0000000..ad320b0 --- /dev/null +++ b/compiler/src/main/java/com/ebr163/attributesdispatcher/internal/CustomViewElement.java @@ -0,0 +1,94 @@ +package com.ebr163.attributesdispatcher.internal; + +import com.ebr163.attributesdispatcher.Attribute; +import com.ebr163.attributesdispatcher.attr.BooleanAttr; +import com.ebr163.attributesdispatcher.attr.ColorAttr; +import com.ebr163.attributesdispatcher.attr.DimenAttr; +import com.ebr163.attributesdispatcher.attr.FloatAttr; +import com.ebr163.attributesdispatcher.attr.IntAttr; +import com.ebr163.attributesdispatcher.attr.ReferenceAttr; +import com.ebr163.attributesdispatcher.attr.StringAttr; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeVariableName; + +import java.util.ArrayList; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; + +/** + * Created by mac1 on 23.11.16. + */ + +class CustomViewElement { + + private static final String GEN_CLASS_SUFFIX = "Attribute"; + + final TypeName typeName; + final List typeVariables; + final String packageName; + final String inputClassName; + final String generatedClassName; + final List attrElements; + final TypeElement typeElement; + final List booleanElements; + final List colorElements; + final List dimenElements; + final List floatElements; + final List integerElements; + final List referenceElements; + final List stringElements; + + CustomViewElement(TypeElement typeElement) { + this.typeElement = typeElement; + typeName = TypeName.get(typeElement.asType()); + typeVariables = getTypeVariables(typeElement); + packageName = getPackageName(typeElement); + inputClassName = typeElement.getSimpleName().toString(); + generatedClassName = inputClassName + GEN_CLASS_SUFFIX; + attrElements = getChildElementsAnnotatedWith(typeElement, Attribute.class); + booleanElements = getChildFieldsAnnotatedWith(typeElement, BooleanAttr.class); + colorElements = getChildFieldsAnnotatedWith(typeElement, ColorAttr.class); + dimenElements = getChildFieldsAnnotatedWith(typeElement, DimenAttr.class); + floatElements = getChildFieldsAnnotatedWith(typeElement, FloatAttr.class); + integerElements = getChildFieldsAnnotatedWith(typeElement, IntAttr.class); + referenceElements = getChildFieldsAnnotatedWith(typeElement, ReferenceAttr.class); + stringElements = getChildFieldsAnnotatedWith(typeElement, StringAttr.class); + } + + private List getTypeVariables(TypeElement typeElement) { + List names = new ArrayList<>(); + for (TypeParameterElement e : typeElement.getTypeParameters()) { + names.add(TypeVariableName.get(e)); + } + return names; + } + + private String getPackageName(TypeElement typeElement) { + String packageName = typeElement.getQualifiedName().toString(); + return packageName.substring(0, packageName.lastIndexOf(".")); + } + + private List getChildElementsAnnotatedWith(TypeElement typeElement, Class clazz) { + List childElements = new ArrayList<>(); + for (Element e : typeElement.getEnclosedElements()) { + if (e.getAnnotation(clazz) != null) { + childElements.add((ExecutableElement) e); + } + } + return childElements; + } + + private List getChildFieldsAnnotatedWith(TypeElement typeElement, Class clazz) { + List childElements = new ArrayList<>(); + for (Element e : typeElement.getEnclosedElements()) { + if (e.getAnnotation(clazz) != null) { + childElements.add(e); + } + } + return childElements; + } +} diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/Attribute.java b/library/src/main/java/com/ebr163/attributesdispatcher/Attribute.java new file mode 100644 index 0000000..96fe7ad --- /dev/null +++ b/library/src/main/java/com/ebr163/attributesdispatcher/Attribute.java @@ -0,0 +1,15 @@ +package com.ebr163.attributesdispatcher; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Created by mac1 on 22.11.16. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface Attribute { + int maxSdkVersion() default 0; +} \ No newline at end of file diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/CustomView.java b/library/src/main/java/com/ebr163/attributesdispatcher/CustomView.java new file mode 100644 index 0000000..4a1a133 --- /dev/null +++ b/library/src/main/java/com/ebr163/attributesdispatcher/CustomView.java @@ -0,0 +1,14 @@ +package com.ebr163.attributesdispatcher; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Created by mac1 on 22.11.16. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface CustomView { +} diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/MyClass.java b/library/src/main/java/com/ebr163/attributesdispatcher/MyClass.java deleted file mode 100644 index 8985f2e..0000000 --- a/library/src/main/java/com/ebr163/attributesdispatcher/MyClass.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.ebr163.attributesdispatcher; - -public class MyClass { -} diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/attr/BooleanAttr.java b/library/src/main/java/com/ebr163/attributesdispatcher/attr/BooleanAttr.java new file mode 100644 index 0000000..1b3570e --- /dev/null +++ b/library/src/main/java/com/ebr163/attributesdispatcher/attr/BooleanAttr.java @@ -0,0 +1,11 @@ +package com.ebr163.attributesdispatcher.attr; + +/** + * Created by mac1 on 29.11.16. + */ + +public @interface BooleanAttr { + String value(); + + int maxSdkVersion() default 0; +} diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/attr/ColorAttr.java b/library/src/main/java/com/ebr163/attributesdispatcher/attr/ColorAttr.java new file mode 100644 index 0000000..3dc9c14 --- /dev/null +++ b/library/src/main/java/com/ebr163/attributesdispatcher/attr/ColorAttr.java @@ -0,0 +1,11 @@ +package com.ebr163.attributesdispatcher.attr; + +/** + * Created by mac1 on 29.11.16. + */ + +public @interface ColorAttr { + String value(); + + int maxSdkVersion() default 0; +} diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/attr/DimenAttr.java b/library/src/main/java/com/ebr163/attributesdispatcher/attr/DimenAttr.java new file mode 100644 index 0000000..8ade2b9 --- /dev/null +++ b/library/src/main/java/com/ebr163/attributesdispatcher/attr/DimenAttr.java @@ -0,0 +1,11 @@ +package com.ebr163.attributesdispatcher.attr; + +/** + * Created by mac1 on 29.11.16. + */ + +public @interface DimenAttr { + String value(); + + int maxSdkVersion() default 0; +} diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/attr/FloatAttr.java b/library/src/main/java/com/ebr163/attributesdispatcher/attr/FloatAttr.java new file mode 100644 index 0000000..06a9b11 --- /dev/null +++ b/library/src/main/java/com/ebr163/attributesdispatcher/attr/FloatAttr.java @@ -0,0 +1,11 @@ +package com.ebr163.attributesdispatcher.attr; + +/** + * Created by mac1 on 29.11.16. + */ + +public @interface FloatAttr { + String value(); + + int maxSdkVersion() default 0; +} diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/attr/IntAttr.java b/library/src/main/java/com/ebr163/attributesdispatcher/attr/IntAttr.java new file mode 100644 index 0000000..c668f66 --- /dev/null +++ b/library/src/main/java/com/ebr163/attributesdispatcher/attr/IntAttr.java @@ -0,0 +1,11 @@ +package com.ebr163.attributesdispatcher.attr; + +/** + * Created by mac1 on 29.11.16. + */ + +public @interface IntAttr { + String value(); + + int maxSdkVersion() default 0; +} diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/attr/ReferenceAttr.java b/library/src/main/java/com/ebr163/attributesdispatcher/attr/ReferenceAttr.java new file mode 100644 index 0000000..1dca771 --- /dev/null +++ b/library/src/main/java/com/ebr163/attributesdispatcher/attr/ReferenceAttr.java @@ -0,0 +1,11 @@ +package com.ebr163.attributesdispatcher.attr; + +/** + * Created by mac1 on 29.11.16. + */ + +public @interface ReferenceAttr { + String value(); + + int maxSdkVersion() default 0; +} diff --git a/library/src/main/java/com/ebr163/attributesdispatcher/attr/StringAttr.java b/library/src/main/java/com/ebr163/attributesdispatcher/attr/StringAttr.java new file mode 100644 index 0000000..c57f8c2 --- /dev/null +++ b/library/src/main/java/com/ebr163/attributesdispatcher/attr/StringAttr.java @@ -0,0 +1,11 @@ +package com.ebr163.attributesdispatcher.attr; + +/** + * Created by mac1 on 29.11.16. + */ + +public @interface StringAttr { + String value(); + + int maxSdkVersion() default 0; +}