From f0b735144d24592d665eeff90051b6adea2e564a Mon Sep 17 00:00:00 2001 From: Warren Smith Date: Thu, 14 Jun 2018 12:57:14 -0700 Subject: [PATCH] Add AndroidX support. (#1289) --- .../java/butterknife/compiler/BindingSet.java | 49 +++++++++++-------- .../compiler/ButterKnifeProcessor.java | 37 +++++++++++--- .../compiler/FieldDrawableBinding.java | 8 ++- .../compiler/FieldResourceBinding.java | 5 ++ .../compiler/FieldTypefaceBinding.java | 9 +++- 5 files changed, 77 insertions(+), 31 deletions(-) diff --git a/butterknife-compiler/src/main/java/butterknife/compiler/BindingSet.java b/butterknife-compiler/src/main/java/butterknife/compiler/BindingSet.java index b20aff4a0..1c44014eb 100644 --- a/butterknife-compiler/src/main/java/butterknife/compiler/BindingSet.java +++ b/butterknife-compiler/src/main/java/butterknife/compiler/BindingSet.java @@ -44,14 +44,20 @@ final class BindingSet { private static final ClassName RESOURCES = ClassName.get("android.content.res", "Resources"); private static final ClassName UI_THREAD = ClassName.get("android.support.annotation", "UiThread"); + private static final ClassName UI_THREAD_ANDROIDX = + ClassName.get("androidx.annotation", "UiThread"); private static final ClassName CALL_SUPER = ClassName.get("android.support.annotation", "CallSuper"); + private static final ClassName CALL_SUPER_ANDROIDX = + ClassName.get("androidx.annotation", "CallSuper"); private static final ClassName SUPPRESS_LINT = ClassName.get("android.annotation", "SuppressLint"); private static final ClassName UNBINDER = ClassName.get("butterknife", "Unbinder"); static final ClassName BITMAP_FACTORY = ClassName.get("android.graphics", "BitmapFactory"); static final ClassName CONTEXT_COMPAT = ClassName.get("android.support.v4.content", "ContextCompat"); + static final ClassName CONTEXT_COMPAT_ANDROIDX = + ClassName.get("androidx.core.content", "ContextCompat"); static final ClassName ANIMATION_UTILS = ClassName.get("android.view.animation", "AnimationUtils"); @@ -82,13 +88,14 @@ private BindingSet(TypeName targetTypeName, ClassName bindingClassName, boolean this.parentBinding = parentBinding; } - JavaFile brewJava(int sdk, boolean debuggable) { - return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable)) + JavaFile brewJava(int sdk, boolean debuggable, boolean useAndroidX) { + TypeSpec bindingConfiguration = createType(sdk, debuggable, useAndroidX); + return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); } - private TypeSpec createType(int sdk, boolean debuggable) { + private TypeSpec createType(int sdk, boolean debuggable, boolean useAndroidX) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC); if (isFinal) { @@ -106,32 +113,32 @@ private TypeSpec createType(int sdk, boolean debuggable) { } if (isView) { - result.addMethod(createBindingConstructorForView()); + result.addMethod(createBindingConstructorForView(useAndroidX)); } else if (isActivity) { - result.addMethod(createBindingConstructorForActivity()); + result.addMethod(createBindingConstructorForActivity(useAndroidX)); } else if (isDialog) { - result.addMethod(createBindingConstructorForDialog()); + result.addMethod(createBindingConstructorForDialog(useAndroidX)); } if (!constructorNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. - result.addMethod(createBindingViewDelegateConstructor()); + result.addMethod(createBindingViewDelegateConstructor(useAndroidX)); } - result.addMethod(createBindingConstructor(sdk, debuggable)); + result.addMethod(createBindingConstructor(sdk, debuggable, useAndroidX)); if (hasViewBindings() || parentBinding == null) { - result.addMethod(createBindingUnbindMethod(result)); + result.addMethod(createBindingUnbindMethod(result, useAndroidX)); } return result.build(); } - private MethodSpec createBindingViewDelegateConstructor() { + private MethodSpec createBindingViewDelegateConstructor(boolean useAndroidX) { return MethodSpec.constructorBuilder() .addJavadoc("@deprecated Use {@link #$T($T, $T)} for direct creation.\n " + "Only present for runtime invocation through {@code ButterKnife.bind()}.\n", bindingClassName, targetTypeName, CONTEXT) .addAnnotation(Deprecated.class) - .addAnnotation(UI_THREAD) + .addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD) .addModifiers(PUBLIC) .addParameter(targetTypeName, "target") .addParameter(VIEW, "source") @@ -139,9 +146,9 @@ private MethodSpec createBindingViewDelegateConstructor() { .build(); } - private MethodSpec createBindingConstructorForView() { + private MethodSpec createBindingConstructorForView(boolean useAndroidX) { MethodSpec.Builder builder = MethodSpec.constructorBuilder() - .addAnnotation(UI_THREAD) + .addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD) .addModifiers(PUBLIC) .addParameter(targetTypeName, "target"); if (constructorNeedsView()) { @@ -152,9 +159,9 @@ private MethodSpec createBindingConstructorForView() { return builder.build(); } - private MethodSpec createBindingConstructorForActivity() { + private MethodSpec createBindingConstructorForActivity(boolean useAndroidX) { MethodSpec.Builder builder = MethodSpec.constructorBuilder() - .addAnnotation(UI_THREAD) + .addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD) .addModifiers(PUBLIC) .addParameter(targetTypeName, "target"); if (constructorNeedsView()) { @@ -165,9 +172,9 @@ private MethodSpec createBindingConstructorForActivity() { return builder.build(); } - private MethodSpec createBindingConstructorForDialog() { + private MethodSpec createBindingConstructorForDialog(boolean useAndroidX) { MethodSpec.Builder builder = MethodSpec.constructorBuilder() - .addAnnotation(UI_THREAD) + .addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD) .addModifiers(PUBLIC) .addParameter(targetTypeName, "target"); if (constructorNeedsView()) { @@ -178,9 +185,9 @@ private MethodSpec createBindingConstructorForDialog() { return builder.build(); } - private MethodSpec createBindingConstructor(int sdk, boolean debuggable) { + private MethodSpec createBindingConstructor(int sdk, boolean debuggable, boolean useAndroidX) { MethodSpec.Builder constructor = MethodSpec.constructorBuilder() - .addAnnotation(UI_THREAD) + .addAnnotation(useAndroidX ? UI_THREAD_ANDROIDX : UI_THREAD) .addModifiers(PUBLIC); if (hasMethodBindings()) { @@ -255,12 +262,12 @@ private MethodSpec createBindingConstructor(int sdk, boolean debuggable) { return constructor.build(); } - private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) { + private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass, boolean useAndroidX) { MethodSpec.Builder result = MethodSpec.methodBuilder("unbind") .addAnnotation(Override.class) .addModifiers(PUBLIC); if (!isFinal && parentBinding == null) { - result.addAnnotation(CALL_SUPER); + result.addAnnotation(useAndroidX ? CALL_SUPER_ANDROIDX : CALL_SUPER); } if (hasTargetField()) { diff --git a/butterknife-compiler/src/main/java/butterknife/compiler/ButterKnifeProcessor.java b/butterknife-compiler/src/main/java/butterknife/compiler/ButterKnifeProcessor.java index 6ea7e4a7c..d1f65953a 100644 --- a/butterknife-compiler/src/main/java/butterknife/compiler/ButterKnifeProcessor.java +++ b/butterknife-compiler/src/main/java/butterknife/compiler/ButterKnifeProcessor.java @@ -71,6 +71,7 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; @@ -120,6 +121,7 @@ public final class ButterKnifeProcessor extends AbstractProcessor { private int sdk = 1; private boolean debuggable = true; + private boolean useAndroidX = false; private final RScanner rScanner = new RScanner(); @@ -139,6 +141,7 @@ public final class ButterKnifeProcessor extends AbstractProcessor { } debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE)); + useAndroidX = hasAndroidX(env.getElementUtils()); typeUtils = env.getTypeUtils(); filer = env.getFiler(); @@ -188,7 +191,7 @@ private Set> getSupportedAnnotations() { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); - JavaFile javaFile = binding.brewJava(sdk, debuggable); + JavaFile javaFile = binding.brewJava(sdk, debuggable, useAndroidX); try { javaFile.writeTo(filer); } catch (IOException e) { @@ -666,9 +669,17 @@ private void parseResourceColor(Element element, int id = element.getAnnotation(BindColor.class).value(); Id resourceId = elementToId(element, BindColor.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); - builder.addResource(new FieldResourceBinding(resourceId, name, - isColorStateList ? FieldResourceBinding.Type.COLOR_STATE_LIST - : FieldResourceBinding.Type.COLOR)); + + FieldResourceBinding.Type colorStateList = useAndroidX + ? FieldResourceBinding.Type.COLOR_STATE_LIST_ANDROIDX + : FieldResourceBinding.Type.COLOR_STATE_LIST; + FieldResourceBinding.Type color = useAndroidX + ? FieldResourceBinding.Type.COLOR_ANDROIDX + : FieldResourceBinding.Type.COLOR; + builder.addResource(new FieldResourceBinding( + resourceId, + name, + isColorStateList ? colorStateList : color)); erasedTargetNames.add(enclosingElement); } @@ -769,7 +780,8 @@ private void parseResourceDrawable(Element element, Map resourceIds = elementToIds(element, BindDrawable.class, new int[] {id, tint}); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); - builder.addResource(new FieldDrawableBinding(resourceIds.get(id), name, resourceIds.get(tint))); + builder.addResource(new FieldDrawableBinding(resourceIds.get(id), name, resourceIds.get(tint), + useAndroidX)); erasedTargetNames.add(enclosingElement); } @@ -841,7 +853,7 @@ private void parseResourceFont(Element element, BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); Id resourceId = elementToId(element, BindFont.class, bindFont.value()); - builder.addResource(new FieldTypefaceBinding(resourceId, name, style)); + builder.addResource(new FieldTypefaceBinding(resourceId, name, style, useAndroidX)); erasedTargetNames.add(enclosingElement); } @@ -1348,6 +1360,19 @@ private static AnnotationMirror getMirror(Element element, return null; } + /** + * Perform two lookups to see if the androidx annotation and core libraries are on the application + * classpath. If both aren't present butterknife will leverage support annotations and + * compat libraries instead. + */ + private static boolean hasAndroidX(Elements elementUtils) { + boolean annotationsPresent + = elementUtils.getTypeElement("androidx.annotation.NonNull") != null; + boolean corePresent + = elementUtils.getTypeElement("androidx.core.content.ContextCompat") != null; + return annotationsPresent && corePresent; + } + private static class RScanner extends TreeScanner { Map resourceIds = new LinkedHashMap<>(); diff --git a/butterknife-compiler/src/main/java/butterknife/compiler/FieldDrawableBinding.java b/butterknife-compiler/src/main/java/butterknife/compiler/FieldDrawableBinding.java index b888a3f03..2bdac846d 100644 --- a/butterknife-compiler/src/main/java/butterknife/compiler/FieldDrawableBinding.java +++ b/butterknife-compiler/src/main/java/butterknife/compiler/FieldDrawableBinding.java @@ -3,6 +3,7 @@ import com.squareup.javapoet.CodeBlock; import static butterknife.compiler.BindingSet.CONTEXT_COMPAT; +import static butterknife.compiler.BindingSet.CONTEXT_COMPAT_ANDROIDX; import static butterknife.compiler.BindingSet.UTILS; import static butterknife.internal.Constants.NO_RES_ID; @@ -10,11 +11,13 @@ final class FieldDrawableBinding implements ResourceBinding { private final Id id; private final String name; private final Id tintAttributeId; + private final boolean androidX; - FieldDrawableBinding(Id id, String name, Id tintAttributeId) { + FieldDrawableBinding(Id id, String name, Id tintAttributeId, boolean useAndroidX) { this.id = id; this.name = name; this.tintAttributeId = tintAttributeId; + this.androidX = useAndroidX; } @Override public Id id() { @@ -33,6 +36,7 @@ final class FieldDrawableBinding implements ResourceBinding { if (sdk >= 21) { return CodeBlock.of("target.$L = context.getDrawable($L)", name, id.code); } - return CodeBlock.of("target.$L = $T.getDrawable(context, $L)", name, CONTEXT_COMPAT, id.code); + return CodeBlock.of("target.$L = $T.getDrawable(context, $L)", name, + androidX ? CONTEXT_COMPAT_ANDROIDX : CONTEXT_COMPAT, id.code); } } diff --git a/butterknife-compiler/src/main/java/butterknife/compiler/FieldResourceBinding.java b/butterknife-compiler/src/main/java/butterknife/compiler/FieldResourceBinding.java index d95105255..7213781e1 100644 --- a/butterknife-compiler/src/main/java/butterknife/compiler/FieldResourceBinding.java +++ b/butterknife-compiler/src/main/java/butterknife/compiler/FieldResourceBinding.java @@ -15,8 +15,13 @@ enum Type { BOOL("getBoolean"), COLOR(new ResourceMethod(BindingSet.CONTEXT_COMPAT, "getColor", false, 1), new ResourceMethod(null, "getColor", false, 23)), + COLOR_ANDROIDX(new ResourceMethod(BindingSet.CONTEXT_COMPAT_ANDROIDX, "getColor", false, 1), + new ResourceMethod(null, "getColor", false, 23)), COLOR_STATE_LIST(new ResourceMethod(BindingSet.CONTEXT_COMPAT, "getColorStateList", false, 1), new ResourceMethod(null, "getColorStateList", false, 23)), + COLOR_STATE_LIST_ANDROIDX(new ResourceMethod(BindingSet.CONTEXT_COMPAT_ANDROIDX, + "getColorStateList", false, 1), + new ResourceMethod(null, "getColorStateList", false, 23)), DIMEN_AS_INT("getDimensionPixelSize"), DIMEN_AS_FLOAT("getDimension"), FLOAT(new ResourceMethod(BindingSet.UTILS, "getFloat", false, 1)), diff --git a/butterknife-compiler/src/main/java/butterknife/compiler/FieldTypefaceBinding.java b/butterknife-compiler/src/main/java/butterknife/compiler/FieldTypefaceBinding.java index 5707f2bdb..7ee422a41 100644 --- a/butterknife-compiler/src/main/java/butterknife/compiler/FieldTypefaceBinding.java +++ b/butterknife-compiler/src/main/java/butterknife/compiler/FieldTypefaceBinding.java @@ -7,6 +7,8 @@ final class FieldTypefaceBinding implements ResourceBinding { private static final ClassName RESOURCES_COMPAT = ClassName.get("android.support.v4.content.res", "ResourcesCompat"); + private static final ClassName RESOURCES_COMPAT_ANDROIDX = + ClassName.get("androidx.core.content.res", "ResourcesCompat"); private static final ClassName TYPEFACE = ClassName.get("android.graphics", "Typeface"); /** Keep in sync with {@link android.graphics.Typeface} constants. */ @@ -35,11 +37,13 @@ enum TypefaceStyles { private final Id id; private final String name; private final TypefaceStyles style; + private final boolean useAndroidX; - FieldTypefaceBinding(Id id, String name, TypefaceStyles style) { + FieldTypefaceBinding(Id id, String name, TypefaceStyles style, boolean useAndroidX) { this.id = id; this.name = name; this.style = style; + this.useAndroidX = useAndroidX; } @Override public Id id() { @@ -53,7 +57,8 @@ enum TypefaceStyles { @Override public CodeBlock render(int sdk) { CodeBlock typeface = sdk >= 26 ? CodeBlock.of("res.getFont($L)", id.code) - : CodeBlock.of("$T.getFont(context, $L)", RESOURCES_COMPAT, id.code); + : CodeBlock.of("$T.getFont(context, $L)", + useAndroidX ? RESOURCES_COMPAT_ANDROIDX : RESOURCES_COMPAT, id.code); if (style != TypefaceStyles.NORMAL) { typeface = CodeBlock.of("$1T.create($2L, $1T.$3L)", TYPEFACE, typeface, style); }