From cbecb900f629ca0bf07fb0cb689de8d64523e200 Mon Sep 17 00:00:00 2001 From: George Mount Date: Tue, 20 Jan 2015 08:38:57 -0800 Subject: [PATCH] Added implementations of BindingAdapters. Added renaming attribute-to-setter and automatic Conversions. Moved intermediate store (SetterStore) to compiler project. Moved annotations to their own project. --- .../annotationprocessor/build.gradle | 7 +- .../BindingAdapterStore.java | 336 -------- .../annotationprocessor/ProcessBindable.java | 47 +- .../ProcessBindingAdapters.java | 94 --- .../ProcessMethodAdapters.java | 158 ++++ .../javax.annotation.processing.Processor | 2 +- tools/data-binding/annotations/build.gradle | 48 ++ .../main/java/android/binding/Bindable.java | 0 .../java/android/binding/BindingAdapter.java | 2 +- .../android/binding/BindingConversion.java | 24 + .../java/android/binding/BindingMethod.java | 25 + .../java/android/binding/BindingMethods.java | 24 + tools/data-binding/compiler/build.gradle | 3 +- .../databinding/store/SetterStore.java | 750 ++++++++++++++++++ .../databinding/util/class_analyzer.kt | 38 +- .../kotlin/com/android/databinding/vo/vo.kt | 6 +- tools/data-binding/library/build.gradle | 5 + .../adapters/AbsListViewBindingAdapter.java | 28 + .../adapters/AbsSeekBarBindingAdapter.java | 27 + .../adapters/AbsSpinnerBindingAdapter.java | 50 ++ .../AutoCompleteTextViewBindingAdapter.java | 27 + .../adapters/CardViewBindingAdapter.java | 71 ++ .../CheckedTextViewBindingAdapter.java | 27 + .../CompoundButtonBindingAdapter.java | 26 + .../android/binding/adapters/Converters.java | 32 + .../adapters/FrameLayoutBindingAdapter.java | 26 + .../adapters/ImageViewBindingAdapter.java | 28 + .../adapters/LinearLayoutBindingAdapter.java | 27 + .../adapters/ProgressBarBindingAdapter.java | 28 + .../adapters/RadioGroupBindingAdapter.java | 26 + .../adapters/SpinnerBindingAdapter.java | 26 + .../adapters/SwitchBindingAdapter.java | 36 + .../adapters/SwitchCompatBindingAdapter.java | 33 + .../adapters/TabWidgetBindingAdapter.java | 29 + .../adapters/TableLayoutBindingAdapter.java | 93 +++ .../adapters/TextViewBindingAdapter.java | 301 +++++++ .../binding/adapters/ViewBindingAdapter.java | 97 +++ .../adapters/ViewGroupBindingAdapter.java | 40 + .../adapters/ViewStubBindingAdapter.java | 26 + .../samples/BindingDemo/app/build.gradle | 11 +- .../bindingdemo/vo/TestBindingAdapter.java | 23 - tools/data-binding/settings.gradle | 3 +- 42 files changed, 2212 insertions(+), 498 deletions(-) delete mode 100644 tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/BindingAdapterStore.java delete mode 100644 tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindingAdapters.java create mode 100644 tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessMethodAdapters.java create mode 100644 tools/data-binding/annotations/build.gradle rename tools/data-binding/{annotationprocessor => annotations}/src/main/java/android/binding/Bindable.java (100%) rename tools/data-binding/{annotationprocessor => annotations}/src/main/java/android/binding/BindingAdapter.java (97%) create mode 100644 tools/data-binding/annotations/src/main/java/android/binding/BindingConversion.java create mode 100644 tools/data-binding/annotations/src/main/java/android/binding/BindingMethod.java create mode 100644 tools/data-binding/annotations/src/main/java/android/binding/BindingMethods.java create mode 100644 tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/AbsListViewBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/AbsSeekBarBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/AbsSpinnerBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/AutoCompleteTextViewBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/CardViewBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/CheckedTextViewBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/CompoundButtonBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/Converters.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/FrameLayoutBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/ImageViewBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/LinearLayoutBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/ProgressBarBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/RadioGroupBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/SpinnerBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/SwitchBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/SwitchCompatBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/TabWidgetBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/TableLayoutBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/TextViewBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/ViewBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/ViewGroupBindingAdapter.java create mode 100644 tools/data-binding/library/src/main/java/android/binding/adapters/ViewStubBindingAdapter.java delete mode 100644 tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/TestBindingAdapter.java diff --git a/tools/data-binding/annotationprocessor/build.gradle b/tools/data-binding/annotationprocessor/build.gradle index 19c563def5142..a6cd563c84602 100644 --- a/tools/data-binding/annotationprocessor/build.gradle +++ b/tools/data-binding/annotationprocessor/build.gradle @@ -36,11 +36,16 @@ sourceSets { } } +dependencies { + compile project(":annotations") + compile project(":compiler") +} + uploadArchives { repositories { mavenDeployer { repository(url: mavenLocal().url) - pom.version = '0.1-SNAPSHOT' + pom.version = '0.3-SNAPSHOT' pom.artifactId = 'annotationprocessor' pom.groupId='com.android.databinding' } diff --git a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/BindingAdapterStore.java b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/BindingAdapterStore.java deleted file mode 100644 index a4a9b0604654e..0000000000000 --- a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/BindingAdapterStore.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.databinding.annotationprocessor; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Objects; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; - -public class BindingAdapterStore { - private static final BindingAdapterStore sStore = load(); - private final HashMap> mAdapters; - - private BindingAdapterStore(Object adapters) { - if (adapters == null) { - mAdapters = new HashMap<>(); - } else { - mAdapters = (HashMap>) adapters; - } - } - - public static BindingAdapterStore get() { - return sStore; - } - - private static BindingAdapterStore load() { - Object adapters = null; - File outputFile = getOutputFile(); - if (outputFile.exists()) { - try { - ObjectInputStream in = new ObjectInputStream(new FileInputStream(outputFile)); - adapters = in.readObject(); - in.close(); - } catch (IOException e) { - System.err.println("Could not read BindingAdapter intermediate file: " + - e.getLocalizedMessage()); - } catch (ClassNotFoundException e) { - System.err.println("Could not read BindingAdapter intermediate file: " + - e.getLocalizedMessage()); - } - } - return new BindingAdapterStore(adapters); - } - - public void add(String attribute, TypeMirror viewType, TypeMirror valueType, - TypeElement bindingAdapterType, ExecutableElement bindingMethod) { - HashMap adapters = mAdapters.get(attribute); - - if (adapters == null) { - adapters = new HashMap<>(); - mAdapters.put(attribute, adapters); - } - String view = viewType.toString(); - String value = valueType.toString(); - - AccessorKey key = new AccessorKey(view, value); - if (adapters.containsKey(key)) { - throw new IllegalArgumentException("Already exists!"); - } - - String type = bindingAdapterType.getQualifiedName().toString(); - String method = bindingMethod.getSimpleName().toString(); - adapters.put(key, new BindingAdapterDescription(type, method)); - } - - public void clear(TypeElement bindingAdapter) { - String className = bindingAdapter.getQualifiedName().toString(); - ArrayList removed = new ArrayList<>(); - for (HashMap adapters : mAdapters.values()) { - for (AccessorKey key : adapters.keySet()) { - BindingAdapterDescription description = adapters.get(key); - if (description.type.equals(className)) { - removed.add(key); - } - } - for (AccessorKey key : removed) { - adapters.remove(key); - } - removed.clear(); - } - } - - public void write() throws IOException { - ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(getOutputFile())); - out.writeObject(mAdapters); - out.close(); - } - - private static File getOutputFile() { - File dir = new File(new File("build"),"intermediates"); - dir.mkdirs(); - return new File(dir, "binding_adapters.bin"); - } - - public String getSetterCall(String attribute, Class viewType, Class valueType, - String viewExpression, String valueExpression, ClassLoader classLoader) { - HashMap adapters = mAdapters.get(attribute); - - BindingAdapterDescription adapter = null; - System.out.println("adapters for " + attribute + " are " + adapters); - if (adapters != null) { - Class bestViewType = null; - Class bestValueType = null; - boolean bestTypeEquals = false; - boolean bestTypeIsBoxed = false; - int bestTypeImplicitConversion = Integer.MAX_VALUE; - - for (AccessorKey key : adapters.keySet()) { - try { - Class keyView = loadClass(key.viewType, classLoader); - Class keyValue = loadClass(key.valueType, classLoader); - if (!keyView.isAssignableFrom(viewType)) { - System.out.println("View type is wrong: " + keyView + " is not assignable from " + viewType); - } - if (keyView.isAssignableFrom(viewType)) { - boolean isBetterView = bestViewType == null || - bestValueType.isAssignableFrom(keyView); - System.out.println("View type is right: " + keyView + " is better? " + isBetterView); - boolean isBetterValueType; - // Right view type. Check the value - if (!isBetterView && bestTypeEquals) { - System.out.println("best type is already equal"); - isBetterValueType = false; - } else if (valueType.equals(keyValue)) { - // Exact match - isBetterValueType = true; - bestTypeEquals = true; - System.out.println("new type equals"); - } else if (!isBetterView && bestTypeIsBoxed) { - isBetterValueType = false; - } else if (isBoxingConversion(keyValue, valueType)) { - // Boxing/unboxing is second best - isBetterValueType = true; - bestTypeIsBoxed = true; - } else if (isImplicitConversion(valueType, keyValue)) { - // Better implicit conversion - int conversionLevel = getConversionLevel(keyValue); - isBetterValueType = conversionLevel < bestTypeImplicitConversion; - if (isBetterValueType) { - bestTypeImplicitConversion = conversionLevel; - } - } else if (bestTypeImplicitConversion < Integer.MAX_VALUE) { - isBetterValueType = false; - } else if (keyValue.isAssignableFrom(valueType)) { - // Right type, see if it is better than the current best match. - if (bestValueType == null) { - isBetterValueType = true; - } else { - isBetterValueType = bestValueType.isAssignableFrom(keyValue); - } - } else { - isBetterValueType = false; - } - if (isBetterValueType) { - bestViewType = keyView; - bestValueType = keyValue; - adapter = adapters.get(key); - } - } - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - } - } - if (adapter == null) { - int colonIndex = attribute.indexOf(':'); - String propertyName; - if (colonIndex >= 0 && colonIndex + 1 < attribute.length()) { - propertyName = Character.toUpperCase(attribute.charAt(colonIndex + 1)) + - attribute.substring(colonIndex + 2); - } else { - propertyName = ""; - } - return viewExpression + ".set" + propertyName + "(" + valueExpression + ")"; - } else { - return adapter.type + "." + adapter.method + "(" + viewExpression + ", " + - valueExpression + ")"; - } - } - - private static boolean isImplicitConversion(Class from, Class to) { - if (from != null && to != null && from.isPrimitive() && to.isPrimitive()) { - if (from.equals(boolean.class) || to.equals(boolean.class) || - to.equals(char.class)) { - return false; - } - int fromConversionLevel = getConversionLevel(from); - int toConversionLevel = getConversionLevel(to); - return fromConversionLevel < toConversionLevel; - } else { - return false; - } - } - - private static int getConversionLevel(Class primitive) { - if (byte.class.equals(primitive)) { - return 0; - } else if (char.class.equals(primitive)) { - return 1; - } else if (short.class.equals(primitive)) { - return 2; - } else if (int.class.equals(primitive)) { - return 3; - } else if (long.class.equals(primitive)) { - return 4; - } else if (float.class.equals(primitive)) { - return 5; - } else if (double.class.equals(primitive)) { - return 6; - } else { - return -1; - } - } - - private static boolean isBoxingConversion(Class class1, Class class2) { - if (class1.isPrimitive() != class2.isPrimitive()) { - return (getWrappedType(class1).equals(getWrappedType(class2))); - } else { - return false; - } - } - - private static Class getWrappedType(Class type) { - if (!type.isPrimitive()) { - return type; - } - if (int.class.equals(type)) { - return Integer.class; - } else if (long.class.equals(type)) { - return Long.class; - } else if (short.class.equals(type)) { - return Short.class; - } else if (byte.class.equals(type)) { - return Byte.class; - } else if (char.class.equals(type)) { - return Character.class; - } else if (double.class.equals(type)) { - return Double.class; - } else if (float.class.equals(type)) { - return Float.class; - } else if (boolean.class.equals(type)) { - return Boolean.class; - } else { - // what type is this? - return type; - } - } - - public static Class loadClass(String className, ClassLoader classLoader) - throws ClassNotFoundException { - switch (className) { - case "long": return long.class; - case "int": return int.class; - case "short": return short.class; - case "byte": return byte.class; - case "char": return char.class; - case "float": return float.class; - case "double": return double.class; - case "boolean": return boolean.class; - default: return Class.forName(className, false, classLoader); - } - } - - private static class BindingAdapterDescription implements Serializable { - public final String type; - public final String method; - - public BindingAdapterDescription(String type, String method) { - this.type = type; - this.method = method; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof BindingAdapterDescription) { - BindingAdapterDescription that = (BindingAdapterDescription) obj; - return that.type.equals(this.type) && that.method.equals(this.method); - } else { - return false; - } - } - - @Override - public int hashCode() { - return Objects.hash(type, method); - } - } - - private static class AccessorKey implements Serializable { - public final String viewType; - public final String valueType; - - public AccessorKey(String viewType, String valueType) { - this.viewType = viewType; - this.valueType = valueType; - } - - @Override - public int hashCode() { - return Objects.hash(viewType, valueType); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof AccessorKey) { - AccessorKey that = (AccessorKey) obj; - return viewType.equals(that.valueType) && valueType.equals(that.valueType); - } else { - return false; - } - } - } -} diff --git a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindable.java b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindable.java index f1affc437daf6..d3b3ea3a88204 100644 --- a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindable.java +++ b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindable.java @@ -3,8 +3,6 @@ package com.android.databinding.annotationprocessor; import android.binding.Bindable; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -15,7 +13,6 @@ import java.util.HashSet; import java.util.Set; import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; @@ -27,7 +24,9 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.tools.Diagnostic; +import javax.tools.FileObject; import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; @SupportedAnnotationTypes({"android.binding.Bindable"}) @SupportedSourceVersion(SourceVersion.RELEASE_7) @@ -168,18 +167,28 @@ public class ProcessBindable extends AbstractProcessor { private HashSet readIntermediateFile() { HashSet properties = null; - File intermediate = getIntermediateFile(); - if (intermediate.exists()) { - try { - ObjectInputStream in = new ObjectInputStream(new FileInputStream(intermediate)); + ObjectInputStream in = null; + try { + FileObject intermediate = processingEnv.getFiler() + .getResource(StandardLocation.CLASS_OUTPUT, + ProcessBindable.class.getPackage().getName(), "binding_properties.bin"); + if (new File(intermediate.getName()).exists()) { + in = new ObjectInputStream(intermediate.openInputStream()); properties = (HashSet) in.readObject(); - in.close(); + } + } catch (IOException e) { + System.err.println("Could not read Binding properties intermediate file: " + + e.getLocalizedMessage()); + } catch (ClassNotFoundException e) { + System.err.println("Could not read Binding properties intermediate file: " + + e.getLocalizedMessage()); + } finally { + try { + if (in != null) { + in.close(); + } } catch (IOException e) { - System.err.println("Could not read Binding properties intermediate file: " + - e.getLocalizedMessage()); - } catch (ClassNotFoundException e) { - System.err.println("Could not read Binding properties intermediate file: " + - e.getLocalizedMessage()); + e.printStackTrace(); } } if (properties == null) { @@ -190,8 +199,10 @@ public class ProcessBindable extends AbstractProcessor { private void writeIntermediateFile(HashSet properties) { try { - File intermediate = getIntermediateFile(); - ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(intermediate)); + FileObject intermediate = processingEnv.getFiler().createResource( + StandardLocation.CLASS_OUTPUT, ProcessBindable.class.getPackage().getName(), + "binding_properties.bin"); + ObjectOutputStream out = new ObjectOutputStream(intermediate.openOutputStream()); out.writeObject(properties); out.close(); } catch (IOException e) { @@ -199,10 +210,4 @@ public class ProcessBindable extends AbstractProcessor { "Could not write to intermediate file: " + e.getLocalizedMessage()); } } - - private static File getIntermediateFile() { - File dir = new File(new File("build"),"intermediates"); - dir.mkdirs(); - return new File(dir, "binding_properties.bin"); - } } diff --git a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindingAdapters.java b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindingAdapters.java deleted file mode 100644 index 27949def7e08e..0000000000000 --- a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindingAdapters.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.databinding.annotationprocessor; - -import android.binding.BindingAdapter; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.SourceVersion; -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.element.VariableElement; -import javax.tools.Diagnostic; - -@SupportedAnnotationTypes({"android.binding.BindingAdapter"}) -@SupportedSourceVersion(SourceVersion.RELEASE_7) -public class ProcessBindingAdapters extends AbstractProcessor { - private boolean mProcessed; - - public ProcessBindingAdapters() { - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - if (mProcessed) { - return true; - } - - BindingAdapterStore store = BindingAdapterStore.get(); - for (Element element : roundEnv.getElementsAnnotatedWith(BindingAdapter.class)) { - TypeElement containingClass = (TypeElement) element.getEnclosingElement(); - store.clear(containingClass); - } - for (Element element : roundEnv.getElementsAnnotatedWith(BindingAdapter.class)) { - if (element.getKind() != ElementKind.METHOD || - !element.getModifiers().contains(Modifier.STATIC)) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, - "@BindingAdapter on invalid element: " + element); - continue; - } - BindingAdapter bindingAdapter = element.getAnnotation(BindingAdapter.class); - - ExecutableElement executableElement = (ExecutableElement) element; - List parameters = executableElement.getParameters(); - if (parameters.size() != 2) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, - "@BindingAdapter does not take two parameters: " + element); - continue; - } - TypeElement containingClass = (TypeElement) executableElement.getEnclosingElement(); - try { - store.add(bindingAdapter.attribute(), parameters.get(0).asType(), - parameters.get(1).asType(), containingClass, executableElement); - } catch (IllegalArgumentException e) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, - "@BindingAdapter for duplicate View and parameter type: " + element); - } - } - try { - store.write(); - } catch (IOException e) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, - "Could not write BindingAdapter intermediate file: " + e.getLocalizedMessage()); - e.printStackTrace(); - } - mProcessed = true; - return true; - } -} diff --git a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessMethodAdapters.java b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessMethodAdapters.java new file mode 100644 index 0000000000000..93ab1b1a1c966 --- /dev/null +++ b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessMethodAdapters.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.databinding.annotationprocessor; + +import android.binding.BindingAdapter; +import android.binding.BindingConversion; +import android.binding.BindingMethod; +import android.binding.BindingMethods; +import com.android.databinding.store.SetterStore; + +import java.io.IOException; +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.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +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.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.tools.Diagnostic; + +@SupportedAnnotationTypes({ + "android.binding.BindingAdapter", + "android.binding.BindingMethods", + "android.binding.BindingConversion"}) +@SupportedSourceVersion(SourceVersion.RELEASE_7) +public class ProcessMethodAdapters extends AbstractProcessor { + private boolean mProcessed; + + public ProcessMethodAdapters() { + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (mProcessed) { + return true; + } + + SetterStore store = SetterStore.get(processingEnv); + clearIncrementalClasses(roundEnv, store); + + addBindingAdapters(roundEnv, store); + addRenamed(roundEnv, store); + addConversions(roundEnv, store); + try { + store.write(processingEnv); + } catch (IOException e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "Could not write BindingAdapter intermediate file: " + e.getLocalizedMessage()); + e.printStackTrace(); + } + mProcessed = true; + return true; + } + + private void addBindingAdapters(RoundEnvironment roundEnv, SetterStore store) { + for (Element element : roundEnv.getElementsAnnotatedWith(BindingAdapter.class)) { + if (element.getKind() != ElementKind.METHOD || + !element.getModifiers().contains(Modifier.STATIC) || + !element.getModifiers().contains(Modifier.PUBLIC)) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@BindingAdapter on invalid element: " + element); + continue; + } + BindingAdapter bindingAdapter = element.getAnnotation(BindingAdapter.class); + + ExecutableElement executableElement = (ExecutableElement) element; + List parameters = executableElement.getParameters(); + if (parameters.size() != 2) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@BindingAdapter does not take two parameters: " + element); + continue; + } + try { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, + "------------------ @BindingAdapter for " + element); + store.addBindingAdapter(bindingAdapter.value(), executableElement); + } catch (IllegalArgumentException e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@BindingAdapter for duplicate View and parameter type: " + element); + } + } + } + + private void addRenamed(RoundEnvironment roundEnv, SetterStore store) { + for (Element element : roundEnv.getElementsAnnotatedWith(BindingMethods.class)) { + BindingMethods bindingMethods = element.getAnnotation(BindingMethods.class); + for (BindingMethod bindingMethod : bindingMethods.value()) { + store.addRenamedMethod(bindingMethod.attribute(), + bindingMethod.type(), bindingMethod.method(), (TypeElement) element); + } + } + } + + private void addConversions(RoundEnvironment roundEnv, SetterStore store) { + for (Element element : roundEnv.getElementsAnnotatedWith(BindingConversion.class)) { + if (element.getKind() != ElementKind.METHOD || + !element.getModifiers().contains(Modifier.STATIC) || + !element.getModifiers().contains(Modifier.PUBLIC)) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@BindingConversion is only allowed on public static methods: " + element); + continue; + } + + ExecutableElement executableElement = (ExecutableElement) element; + if (executableElement.getParameters().size() != 1) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@BindingConversion method should have one parameter: " + element); + continue; + } + if (executableElement.getReturnType().getKind() == TypeKind.VOID) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@BindingConversion method must return a value: " + element); + continue; + } + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, + "added conversion: " + element); + store.addConversionMethod(executableElement); + } + } + + private void clearIncrementalClasses(RoundEnvironment roundEnv, SetterStore store) { + HashSet classes = new HashSet<>(); + + for (Element element : roundEnv.getElementsAnnotatedWith(BindingAdapter.class)) { + TypeElement containingClass = (TypeElement) element.getEnclosingElement(); + classes.add(containingClass.getQualifiedName().toString()); + } + for (Element element : roundEnv.getElementsAnnotatedWith(BindingMethods.class)) { + classes.add(((TypeElement) element).getQualifiedName().toString()); + } + for (Element element : roundEnv.getElementsAnnotatedWith(BindingConversion.class)) { + classes.add(((TypeElement) element.getEnclosingElement()).getQualifiedName().toString()); + } + store.clear(classes); + } +} diff --git a/tools/data-binding/annotationprocessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/tools/data-binding/annotationprocessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor index 0e0046699eba4..aed310b988f5c 100644 --- a/tools/data-binding/annotationprocessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/tools/data-binding/annotationprocessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1,2 +1,2 @@ com.android.databinding.annotationprocessor.ProcessBindable -com.android.databinding.annotationprocessor.ProcessBindingAdapters \ No newline at end of file +com.android.databinding.annotationprocessor.ProcessMethodAdapters \ No newline at end of file diff --git a/tools/data-binding/annotations/build.gradle b/tools/data-binding/annotations/build.gradle new file mode 100644 index 0000000000000..8b3ba9eb4986c --- /dev/null +++ b/tools/data-binding/annotations/build.gradle @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'java' +apply plugin: 'maven' + +buildscript { + repositories { + mavenLocal() + mavenCentral() + } +} + +repositories { + mavenCentral() +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + } +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: mavenLocal().url) + pom.version = '0.3-SNAPSHOT' + pom.artifactId = 'annotations' + pom.groupId='com.android.databinding' + } + } +} diff --git a/tools/data-binding/annotationprocessor/src/main/java/android/binding/Bindable.java b/tools/data-binding/annotations/src/main/java/android/binding/Bindable.java similarity index 100% rename from tools/data-binding/annotationprocessor/src/main/java/android/binding/Bindable.java rename to tools/data-binding/annotations/src/main/java/android/binding/Bindable.java diff --git a/tools/data-binding/annotationprocessor/src/main/java/android/binding/BindingAdapter.java b/tools/data-binding/annotations/src/main/java/android/binding/BindingAdapter.java similarity index 97% rename from tools/data-binding/annotationprocessor/src/main/java/android/binding/BindingAdapter.java rename to tools/data-binding/annotations/src/main/java/android/binding/BindingAdapter.java index 59b44a9c8439c..8d06773f5a539 100644 --- a/tools/data-binding/annotationprocessor/src/main/java/android/binding/BindingAdapter.java +++ b/tools/data-binding/annotations/src/main/java/android/binding/BindingAdapter.java @@ -20,5 +20,5 @@ import java.lang.annotation.Target; @Target(ElementType.METHOD) public @interface BindingAdapter { - String attribute(); + String value(); } diff --git a/tools/data-binding/annotations/src/main/java/android/binding/BindingConversion.java b/tools/data-binding/annotations/src/main/java/android/binding/BindingConversion.java new file mode 100644 index 0000000000000..3fb70caedd561 --- /dev/null +++ b/tools/data-binding/annotations/src/main/java/android/binding/BindingConversion.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +public @interface BindingConversion { +} diff --git a/tools/data-binding/annotations/src/main/java/android/binding/BindingMethod.java b/tools/data-binding/annotations/src/main/java/android/binding/BindingMethod.java new file mode 100644 index 0000000000000..0eb0b73e3abfe --- /dev/null +++ b/tools/data-binding/annotations/src/main/java/android/binding/BindingMethod.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding; + +/** + * Created by mount on 1/12/15. + */ +public @interface BindingMethod { + String type(); + String attribute(); + String method(); +} diff --git a/tools/data-binding/annotations/src/main/java/android/binding/BindingMethods.java b/tools/data-binding/annotations/src/main/java/android/binding/BindingMethods.java new file mode 100644 index 0000000000000..599001d265ffc --- /dev/null +++ b/tools/data-binding/annotations/src/main/java/android/binding/BindingMethods.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +public @interface BindingMethods { + BindingMethod[] value(); +} diff --git a/tools/data-binding/compiler/build.gradle b/tools/data-binding/compiler/build.gradle index 06eced67fc776..18962b218cb9a 100644 --- a/tools/data-binding/compiler/build.gradle +++ b/tools/data-binding/compiler/build.gradle @@ -32,10 +32,9 @@ buildscript { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + compile project(":annotations") compile project(":grammerBuilder") compile project(":xmlGrammer") - compile "com.android.databinding:annotationprocessor:0.1-SNAPSHOT" - //compile project(":annotationprocessor") } uploadArchives { repositories { diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java new file mode 100644 index 0000000000000..549f9d302fdaa --- /dev/null +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java @@ -0,0 +1,750 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.databinding.store; + +import com.android.databinding.util.ClassAnalyzer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import javax.annotation.processing.Filer; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; + +import sun.net.www.protocol.jar.URLJarFile; + +public class SetterStore { + + private static SetterStore sStore; + + private final HashMap> mAdapters; + + private final HashMap> mRenamed; + + private final HashMap> mConversions; + + private ArrayList mConversionMethods; + + private HashMap> mAdaptedMethods; + + private HashMap> mRenamedMethods; + + private SetterStore(HashMap> adapters, + HashMap> renamedMethods, + HashMap> conversionMethods) { + if (adapters == null || renamedMethods == null || conversionMethods == null) { + mAdapters = new HashMap<>(); + mRenamed = new HashMap<>(); + mConversions = new HashMap<>(); + } else { + mAdapters = adapters; + mRenamed = renamedMethods; + mConversions = conversionMethods; + } + } + + public static SetterStore get(ProcessingEnvironment processingEnvironment) { + if (sStore == null) { + InputStream in = null; + try { + Filer filer = processingEnvironment.getFiler(); + FileObject resource = filer.getResource(StandardLocation.CLASS_OUTPUT, + SetterStore.class.getPackage().getName(), "setter_store.bin"); + if (resource != null && new File(resource.getName()).exists()) { + in = resource.openInputStream(); + if (in != null) { + sStore = load(in); + } + } + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + if (sStore == null) { + HashMap> adapters = new HashMap<>(); + HashMap> renamed = new HashMap<>(); + HashMap> conversions = new HashMap<>(); + sStore = new SetterStore(adapters, renamed, conversions); + } + } + return sStore; + } + + public static SetterStore get(ClassAnalyzer classAnalyzer) { + if (sStore == null) { + sStore = load(classAnalyzer); + sStore.applyReflections(classAnalyzer); + } + return sStore; + } + + private static SetterStore load(ClassAnalyzer classAnalyzer) { + HashMap> adapters = new HashMap<>(); + HashMap> renamedMethods = new HashMap<>(); + HashMap> conversionMethods = new HashMap<>(); + String resourceName = SetterStore.class.getPackage().getName().replace('.', '/') + + "/setter_store.bin"; + try { + Enumeration resources = classAnalyzer.getClassLoader().getResources(resourceName); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + merge(adapters, renamedMethods, conversionMethods, resource); + } + return new SetterStore(adapters, renamedMethods, conversionMethods); + } catch (IOException e) { + System.err.println("Could not read SetterStore intermediate file: " + + e.getLocalizedMessage()); + e.printStackTrace(); + } catch (ClassNotFoundException e) { + System.err.println("Could not read SetterStore intermediate file: " + + e.getLocalizedMessage()); + e.printStackTrace(); + } + return new SetterStore(adapters, renamedMethods, conversionMethods); + } + + private static SetterStore load(InputStream inputStream) + throws IOException, ClassNotFoundException { + ObjectInputStream in = new ObjectInputStream(inputStream); + HashMap> adapters + = (HashMap>) in.readObject(); + HashMap> renamedMethods + = (HashMap>) in.readObject(); + HashMap> conversionMethods + = (HashMap>) in.readObject(); + return new SetterStore(adapters, renamedMethods, conversionMethods); + } + + public void addRenamedMethod(String attribute, String declaringClass, String method, + TypeElement declaredOn) { + HashMap renamed = mRenamed.get(attribute); + if (renamed == null) { + renamed = new HashMap<>(); + mRenamed.put(attribute, renamed); + } + MethodDescription methodDescription = + new MethodDescription(declaredOn.getQualifiedName().toString(), method); + renamed.put(declaringClass, methodDescription); + } + + public void addBindingAdapter(String attribute, ExecutableElement bindingMethod) { + HashMap adapters = mAdapters.get(attribute); + + if (adapters == null) { + adapters = new HashMap<>(); + mAdapters.put(attribute, adapters); + } + List parameters = bindingMethod.getParameters(); + String view = getQualifiedName(parameters.get(0).asType()); + String value = getQualifiedName(parameters.get(1).asType()); + + AccessorKey key = new AccessorKey(view, value); + if (adapters.containsKey(key)) { + throw new IllegalArgumentException("Already exists!"); + } + + adapters.put(key, new MethodDescription(bindingMethod)); + } + + private static String getQualifiedName(TypeMirror type) { + switch (type.getKind()) { + case BOOLEAN: + case BYTE: + case SHORT: + case INT: + case LONG: + case CHAR: + case FLOAT: + case DOUBLE: + case VOID: + return type.toString(); + case ARRAY: + return "[" + getArrayType(((ArrayType) type).getComponentType()); + case DECLARED: + return ((TypeElement) ((DeclaredType) type).asElement()).getQualifiedName() + .toString(); + default: + return "-- no type --"; + } + } + + private static String getArrayType(TypeMirror type) { + switch (type.getKind()) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case SHORT: + return "S"; + case INT: + return "I"; + case LONG: + return "J"; + case CHAR: + return "C"; + case FLOAT: + return "F"; + case DOUBLE: + return "D"; + case ARRAY: + return "[" + getArrayType(((ArrayType) type).getComponentType()); + case DECLARED: + return "L" + ((TypeElement) ((DeclaredType) type).asElement()).getQualifiedName() + .toString() + ";"; + } + return "-- no type --"; + } + + public void addConversionMethod(ExecutableElement conversionMethod) { + List parameters = conversionMethod.getParameters(); + String fromType = getQualifiedName(parameters.get(0).asType()); + String toType = getQualifiedName(conversionMethod.getReturnType()); + MethodDescription methodDescription = new MethodDescription(conversionMethod); + HashMap convertTo = mConversions.get(fromType); + if (convertTo == null) { + convertTo = new HashMap<>(); + mConversions.put(fromType, convertTo); + } + convertTo.put(toType, methodDescription); + } + + public void clear(Set classes) { + ArrayList removedAccessorKeys = new ArrayList<>(); + for (HashMap adapters : mAdapters.values()) { + for (AccessorKey key : adapters.keySet()) { + MethodDescription description = adapters.get(key); + if (classes.contains(description.type)) { + removedAccessorKeys.add(key); + } + } + for (AccessorKey key : removedAccessorKeys) { + adapters.remove(key); + } + removedAccessorKeys.clear(); + } + + ArrayList removedRenamed = new ArrayList<>(); + for (HashMap renamed : mRenamed.values()) { + for (String key : renamed.keySet()) { + if (classes.contains(renamed.get(key).type)) { + removedRenamed.add(key); + } + } + for (String key : removedRenamed) { + renamed.remove(key); + } + removedRenamed.clear(); + } + + ArrayList removedConversions = new ArrayList<>(); + for (HashMap convertTos : mConversions.values()) { + for (String toType : convertTos.keySet()) { + MethodDescription methodDescription = convertTos.get(toType); + if (classes.contains(methodDescription.type)) { + removedConversions.add(toType); + } + } + for (String key : removedConversions) { + convertTos.remove(key); + } + removedConversions.clear(); + } + } + + public void write(ProcessingEnvironment processingEnvironment) throws IOException { + Filer filer = processingEnvironment.getFiler(); + FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, + SetterStore.class.getPackage().getName(), "setter_store.bin"); + processingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE, + "============= Writing intermediate file: " + resource.getName()); + ObjectOutputStream out = null; + try { + out = new ObjectOutputStream(resource.openOutputStream()); + + processingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE, + "============= adapters: " + mAdapters); + out.writeObject(mAdapters); + out.writeObject(mRenamed); + out.writeObject(mConversions); + } finally { + if (out != null) { + out.close(); + } + } + } + + public String getSetterCall(String attribute, Class viewType, Class valueType, + String viewExpression, String valueExpression) { + ArrayList adapters = mAdaptedMethods.get(attribute); + + AdaptedMethod adapter = null; + String setterName = null; + Method bestSetterMethod = getBestSetter(viewType, valueType, attribute); + Class bestViewType = null; + Class bestValueType = null; + if (bestSetterMethod != null) { + bestViewType = bestSetterMethod.getDeclaringClass(); + bestValueType = bestSetterMethod.getParameterTypes()[0]; + setterName = bestSetterMethod.getName(); + } + + if (adapters != null) { + for (AdaptedMethod adaptedMethod : adapters) { + if (adaptedMethod.viewType.isAssignableFrom(viewType)) { + boolean isBetterView = bestViewType == null || + bestValueType.isAssignableFrom(adaptedMethod.valueType); + if (isBetterParameter(valueType, adaptedMethod.valueType, bestValueType, + isBetterView)) { + bestViewType = adaptedMethod.viewType; + bestValueType = adaptedMethod.valueType; + adapter = adaptedMethod; + } + } + } + } + + ConversionMethod conversionMethod = getConversionMethod(valueType, bestValueType); + if (conversionMethod != null) { + valueExpression = conversionMethod.type + "." + conversionMethod.method + "(" + + valueExpression + ")"; + } + if (adapter == null) { + if (setterName == null) { + setterName = getDefaultSetter(attribute); + } + return viewExpression + "." + setterName + "(" + valueExpression + ")"; + } else { + return adapter.type + "." + adapter.method + "(" + viewExpression + ", " + + valueExpression + ")"; + } + } + + private Method getBestSetter(Class viewType, Class argumentType, String attribute) { + String setterName = null; + + ArrayList renamed = mRenamedMethods.get(attribute); + if (renamed != null) { + for (RenamedMethod renamedMethod : renamed) { + if (renamedMethod.viewType.isAssignableFrom(viewType)) { + setterName = renamedMethod.method; + break; + } + } + } + if (setterName == null) { + setterName = getDefaultSetter(attribute); + } + Method[] methods = viewType.getMethods(); + + Class bestParameterType = null; + Method bestMethod = null; + for (Method method : methods) { + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length == 1 && setterName.equals(method.getName()) && + void.class.equals(method.getReturnType()) && + !Modifier.isStatic(method.getModifiers()) && + Modifier.isPublic(method.getModifiers())) { + Class param = parameterTypes[0]; + if (isBetterParameter(argumentType, param, bestParameterType, true)) { + bestParameterType = param; + bestMethod = method; + } + } + } + return bestMethod; + } + + private static String getDefaultSetter(String attribute) { + int colonIndex = attribute.indexOf(':'); + String propertyName; + if (colonIndex >= 0 && colonIndex + 1 < attribute.length()) { + propertyName = Character.toUpperCase(attribute.charAt(colonIndex + 1)) + + attribute.substring(colonIndex + 2); + } else { + propertyName = ""; + } + return "set" + propertyName; + } + + private boolean isBetterParameter(Class argument, Class parameter, + Class oldParameter, boolean isBetterViewTypeMatch) { + // Right view type. Check the value + if (!isBetterViewTypeMatch && oldParameter.equals(argument)) { + return false; + } else if (argument.equals(parameter)) { + // Exact match + return true; + } else if (!isBetterViewTypeMatch && isBoxingConversion(oldParameter, argument)) { + return false; + } else if (isBoxingConversion(parameter, argument)) { + // Boxing/unboxing is second best + return true; + } else { + int oldConversionLevel = getConversionLevel(oldParameter); + if (isImplicitConversion(argument, parameter)) { + // Better implicit conversion + int conversionLevel = getConversionLevel(parameter); + return oldConversionLevel < 0 || conversionLevel < oldConversionLevel; + } else if (oldConversionLevel >= 0) { + return false; + } else if (parameter.isAssignableFrom(argument)) { + // Right type, see if it is better than the current best match. + if (oldParameter == null) { + return true; + } else { + return oldParameter.isAssignableFrom(parameter); + } + } else { + return getConversionMethod(argument, parameter) != null; + } + } + } + + private static boolean isImplicitConversion(Class from, Class to) { + if (from != null && to != null && from.isPrimitive() && to.isPrimitive()) { + if (from.equals(boolean.class) || to.equals(boolean.class) || + to.equals(char.class)) { + return false; + } + int fromConversionLevel = getConversionLevel(from); + int toConversionLevel = getConversionLevel(to); + return fromConversionLevel < toConversionLevel; + } else { + return false; + } + } + + private ConversionMethod getConversionMethod(Class from, Class to) { + System.out.println("Getting conversion from " + from + " to " + to); + if (from != null && to != null) { + for (ConversionMethod conversion : mConversionMethods) { + System.out.println("Testing " + conversion.fromType + " to " + conversion.toType); + if (canUseForConversion(from, conversion.fromType) && + canUseForConversion(conversion.toType, to)) { + System.out.println("Yes!"); + return conversion; + } + System.out.println("Nope!"); + } + } + return null; + } + + private static boolean canUseForConversion(Class from, Class to) { + return from.equals(to) || isBoxingConversion(from, to) || to.isAssignableFrom(from); + } + + private static int getConversionLevel(Class primitive) { + if (byte.class.equals(primitive)) { + return 0; + } else if (char.class.equals(primitive)) { + return 1; + } else if (short.class.equals(primitive)) { + return 2; + } else if (int.class.equals(primitive)) { + return 3; + } else if (long.class.equals(primitive)) { + return 4; + } else if (float.class.equals(primitive)) { + return 5; + } else if (double.class.equals(primitive)) { + return 6; + } else { + return -1; + } + } + + private static boolean isBoxingConversion(Class class1, Class class2) { + if (class1.isPrimitive() != class2.isPrimitive()) { + return (getWrappedType(class1).equals(getWrappedType(class2))); + } else { + return false; + } + } + + private static Class getWrappedType(Class type) { + if (!type.isPrimitive()) { + return type; + } + if (int.class.equals(type)) { + return Integer.class; + } else if (long.class.equals(type)) { + return Long.class; + } else if (short.class.equals(type)) { + return Short.class; + } else if (byte.class.equals(type)) { + return Byte.class; + } else if (char.class.equals(type)) { + return Character.class; + } else if (double.class.equals(type)) { + return Double.class; + } else if (float.class.equals(type)) { + return Float.class; + } else if (boolean.class.equals(type)) { + return Boolean.class; + } else { + // what type is this? + return type; + } + } + + private static void merge(HashMap> adapters, + HashMap> renamed, + HashMap> conversions, + URL nextUrl) throws IOException, ClassNotFoundException { + InputStream inputStream = null; + JarFile jarFile = null; + try { + System.out.println("merging " + nextUrl); + inputStream = nextUrl.openStream(); + ObjectInputStream in = new ObjectInputStream(inputStream); + HashMap> adapters2 = + (HashMap>) in.readObject(); + merge(adapters, adapters2); + HashMap> renamed2 = + (HashMap>) in.readObject(); + merge(renamed, renamed2); + HashMap> conversions2 = + (HashMap>) in.readObject(); + merge(conversions, conversions2); + } finally { + if (inputStream != null) { + inputStream.close(); + } + if (jarFile != null) { + jarFile.close(); + } + } + } + + private static void merge(HashMap> first, + HashMap> second) { + for (K key : second.keySet()) { + HashMap firstVals = first.get(key); + HashMap secondVals = second.get(key); + if (firstVals == null) { + first.put(key, secondVals); + } else { + for (V key2 : secondVals.keySet()) { + if (!firstVals.containsKey(key2)) { + firstVals.put(key2, secondVals.get(key2)); + } + } + } + } + } + + private void applyReflections(ClassAnalyzer classAnalyzer) { + if (mConversionMethods == null) { + mConversionMethods = new ArrayList<>(); + for (String key : mConversions.keySet()) { + Class fromType = classAnalyzer.loadClass(key); + HashMap conversion = mConversions.get(key); + for (String toName : conversion.keySet()) { + Class toType = classAnalyzer.loadClass(toName); + MethodDescription methodDescription = conversion.get(toName); + mConversionMethods + .add(new ConversionMethod(fromType, toType, methodDescription)); + } + } + + mAdaptedMethods = new HashMap<>(); + for (String attribute : mAdapters.keySet()) { + ArrayList adaptedMethods = new ArrayList<>(); + mAdaptedMethods.put(attribute, adaptedMethods); + HashMap adapted = mAdapters.get(attribute); + for (AccessorKey key : adapted.keySet()) { + MethodDescription methodDescription = adapted.get(key); + Class viewType = classAnalyzer.loadClass(key.viewType); + Class valueType = classAnalyzer.loadClass(key.valueType); + adaptedMethods.add(new AdaptedMethod(viewType, valueType, + methodDescription)); + } + } + + mRenamedMethods = new HashMap<>(); + for (String attribute : mRenamed.keySet()) { + ArrayList renamedMethods = new ArrayList<>(); + mRenamedMethods.put(attribute, renamedMethods); + HashMap renamed = mRenamed.get(attribute); + for (String declaredClassName : renamed.keySet()) { + MethodDescription methodDescription = renamed.get(declaredClassName); + Class viewType = classAnalyzer.loadClass(declaredClassName); + renamedMethods.add(new RenamedMethod(viewType, methodDescription.method)); + } + } + } + } + + private static class MethodDescription implements Serializable { + + private static final long serialVersionUID = 1; + + public final String type; + + public final String method; + + public MethodDescription(String type, String method) { + this.type = type; + this.method = method; + } + + public MethodDescription(ExecutableElement method) { + TypeElement enclosingClass = (TypeElement) method.getEnclosingElement(); + this.type = enclosingClass.getQualifiedName().toString(); + this.method = method.getSimpleName().toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof MethodDescription) { + MethodDescription that = (MethodDescription) obj; + return that.type.equals(this.type) && that.method.equals(this.method); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(type, method); + } + + @Override + public String toString() { + return type + "." + method + "()"; + } + } + + private static class AccessorKey implements Serializable { + + private static final long serialVersionUID = 1; + + public final String viewType; + + public final String valueType; + + public AccessorKey(String viewType, String valueType) { + this.viewType = viewType; + this.valueType = valueType; + } + + @Override + public int hashCode() { + return Objects.hash(viewType, valueType); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof AccessorKey) { + AccessorKey that = (AccessorKey) obj; + return viewType.equals(that.valueType) && valueType.equals(that.valueType); + } else { + return false; + } + } + + @Override + public String toString() { + return "AK(" + viewType + ", " + valueType + ")"; + } + } + + private static class ConversionMethod { + + public final Class fromType; + + public final Class toType; + + public final String type; + + public final String method; + + public ConversionMethod(Class fromType, Class toType, MethodDescription method) { + this.fromType = fromType; + this.toType = toType; + this.type = method.type; + this.method = method.method; + } + } + + private static class AdaptedMethod { + + public final Class viewType; + + public final Class valueType; + + public final String type; + + public final String method; + + public AdaptedMethod(Class viewType, Class valueType, MethodDescription method) { + this.viewType = viewType; + this.valueType = valueType; + this.type = method.type; + this.method = method.method; + } + } + + private static class RenamedMethod { + + public final Class viewType; + + public final String method; + + public RenamedMethod(Class viewType, String method) { + this.viewType = viewType; + this.method = method; + } + } +} diff --git a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/util/class_analyzer.kt b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/util/class_analyzer.kt index 56ac264493364..cf84b90d3b188 100644 --- a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/util/class_analyzer.kt +++ b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/util/class_analyzer.kt @@ -146,22 +146,42 @@ class ClassAnalyzer(val classLoader : URLClassLoader) { } fun loadClass(klassName : String) : Class<*> { - var loaded = loadedClasses[klassName] ?: loadPrimitive(klassName) - if (loaded == null) { - loaded = loadRecursively(klassName) - System.out.println("loaded ${loaded}") - loaded!!.getInterfaces().forEach { - System.out.println("interfaces ${it}") + when (klassName) { + "int" -> return javaClass() + "short" -> return javaClass() + "long" -> return javaClass() + "float" -> return javaClass() + "double" -> return javaClass() + "boolean" -> return javaClass() + "char" -> return javaClass() + else -> { + var loaded = loadedClasses[klassName] ?: loadPrimitive(klassName) + if (loaded == null) { + if (klassName.startsWith("[") && klassName.contains("L")) { + val indexOfL = klassName.indexOf('L'); + val baseClass = loadClass(klassName.substring(indexOfL + 1, klassName.length() - 1)); + val realClassName = klassName.substring(0, indexOfL + 1) + + baseClass.getCanonicalName() + ';' + loaded = Class.forName(realClassName, false, classLoader); + loadedClasses.put(klassName, loaded) + } else { + loaded = loadRecursively(klassName) + System.out.println("loaded ${loaded}") + loaded!!.getInterfaces().forEach { + System.out.println("interfaces ${it}") + } + loadedClasses.put(klassName, loaded) + } + } + return loaded!! } - loadedClasses.put(klassName, loaded) } - return loaded!! } fun loadRecursively(klassName : String) : Class<*> { System.out.println("trying to find class ${klassName}") try { - return classLoader.loadClass(klassName) + return Class.forName(klassName, false, classLoader) } catch(ex : ClassNotFoundException) { val lastIndexOfDot = klassName.lastIndexOf(".") if (lastIndexOfDot == -1) { diff --git a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/vo/vo.kt b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/vo/vo.kt index 4a85a730c6a39..c0f147cc24d2b 100644 --- a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/vo/vo.kt +++ b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/vo/vo.kt @@ -31,7 +31,7 @@ import com.android.databinding.util.getMethodOrField import com.android.databinding.util.Log import com.android.databinding.ext.joinToCamelCaseAsVar import com.android.databinding.util.isObservable -import com.android.databinding.annotationprocessor.BindingAdapterStore +import com.android.databinding.store.SetterStore class Binding(val target : BindingTarget, val attr : String, val targetFieldName : String, val expr : Expr) { @@ -47,8 +47,8 @@ class Binding(val target : BindingTarget, val attr : String, val targetFieldName val setter by Delegates.lazy { val viewType = ClassAnalyzer.instance.loadClass(target.viewClass); - BindingAdapterStore.get().getSetterCall(attr, viewType, expr.resolvedClass, - target.resolvedViewName, expr.toJava(), ClassAnalyzer.instance.classLoader) + SetterStore.get(ClassAnalyzer.instance).getSetterCall(attr, viewType, + expr.resolvedClass, target.resolvedViewName, expr.toJava()) } val isDirtyName by Delegates.lazy {"sDirtyFlag${target.resolvedUniqueName}_${targetFieldName.capitalize()}"} diff --git a/tools/data-binding/library/build.gradle b/tools/data-binding/library/build.gradle index dd49c3f87aee8..f1751513b22ab 100644 --- a/tools/data-binding/library/build.gradle +++ b/tools/data-binding/library/build.gradle @@ -55,6 +55,11 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) + compile project(":annotations") + provided 'com.android.support:cardview-v7:+' + provided 'com.android.support:appcompat-v7:+' + provided 'com.android.databinding:compiler:+' + provided 'com.android.databinding:annotationprocessor:+' } //create jar tasks android.libraryVariants.all { variant -> diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/AbsListViewBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/AbsListViewBindingAdapter.java new file mode 100644 index 0000000000000..c8b481d41c590 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/AbsListViewBindingAdapter.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.AbsListView", attribute = "android:listSelector", method = "setSelector"), + @BindingMethod(type = "android.widget.AbsListView", attribute = "android:scrollingCache", method = "setScrollingCacheEnabled"), + @BindingMethod(type = "android.widget.AbsListView", attribute = "android:smoothScrollbar", method = "setSmoothScrollbarEnabled"), +}) +public class AbsListViewBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/AbsSeekBarBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/AbsSeekBarBindingAdapter.java new file mode 100644 index 0000000000000..5d93c3b3a1463 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/AbsSeekBarBindingAdapter.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.AbsSeekBar", attribute = "android:thumbTint", method = "setThumbTintTintList"), + +}) +public class AbsSeekBarBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/AbsSpinnerBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/AbsSpinnerBindingAdapter.java new file mode 100644 index 0000000000000..0dc0b48e8a8e2 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/AbsSpinnerBindingAdapter.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingAdapter; +import android.widget.AbsSpinner; +import android.widget.ArrayAdapter; +import android.widget.SpinnerAdapter; + +public class AbsSpinnerBindingAdapter { + + @BindingAdapter("android:entries") + public static void setEntries(AbsSpinner view, CharSequence[] entries) { + if (entries != null) { + SpinnerAdapter oldAdapter = view.getAdapter(); + boolean changed = true; + if (oldAdapter.getCount() == entries.length) { + changed = false; + for (int i = 0; i < entries.length; i++) { + if (!entries[i].toString().equals(oldAdapter.getItem(i))) { + changed = true; + break; + } + } + } + if (changed) { + ArrayAdapter adapter = + new ArrayAdapter(view.getContext(), + android.R.layout.simple_spinner_item, entries); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + view.setAdapter(adapter); + } + } else { + view.setAdapter(null); + } + } +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/AutoCompleteTextViewBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/AutoCompleteTextViewBindingAdapter.java new file mode 100644 index 0000000000000..79c06a8a88813 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/AutoCompleteTextViewBindingAdapter.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.AutoCompleteTextView", attribute = "android:completionThreshold", method = "setThreshold"), + @BindingMethod(type = "android.widget.AutoCompleteTextView", attribute = "android:popupBackground", method = "setDropDownBackgroundDrawable"), +}) +public class AutoCompleteTextViewBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/CardViewBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/CardViewBindingAdapter.java new file mode 100644 index 0000000000000..89e2a973ee328 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/CardViewBindingAdapter.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingAdapter; +import android.binding.BindingMethod; +import android.binding.BindingMethods; +import android.support.v7.widget.CardView; + +@BindingMethods({ + @BindingMethod(type = "android.support.v7.widget.CardView", attribute = "cardCornerRadius", method = "setRadius"), + @BindingMethod(type = "android.support.v7.widget.CardView", attribute = "cardMaxElevation", method = "setMaxCardElevation"), + @BindingMethod(type = "android.support.v7.widget.CardView", attribute = "cardPreventCornerOverlap", method = "setPreventCornerOverlap"), + @BindingMethod(type = "android.support.v7.widget.CardView", attribute = "cardUseCompatPadding", method = "setUseCompatPadding"), +}) +public class CardViewBindingAdapter { + + @BindingAdapter("contentPadding") + public static void setContentPadding(CardView view, int padding) { + int left = view.getContentPaddingLeft(); + int top = view.getContentPaddingTop(); + int right = view.getContentPaddingRight(); + int bottom = view.getContentPaddingBottom(); + view.setContentPadding(padding, padding, padding, padding); + } + + @BindingAdapter("contentPaddingLeft") + public static void setContentPaddingLeft(CardView view, int left) { + int top = view.getContentPaddingTop(); + int right = view.getContentPaddingRight(); + int bottom = view.getContentPaddingBottom(); + view.setContentPadding(left, top, right, bottom); + } + + @BindingAdapter("contentPaddingTop") + public static void setContentPaddingTop(CardView view, int top) { + int left = view.getContentPaddingLeft(); + int right = view.getContentPaddingRight(); + int bottom = view.getContentPaddingBottom(); + view.setContentPadding(left, top, right, bottom); + } + + @BindingAdapter("contentPaddingRight") + public static void setContentPaddingRight(CardView view, int right) { + int left = view.getContentPaddingLeft(); + int top = view.getContentPaddingTop(); + int bottom = view.getContentPaddingBottom(); + view.setContentPadding(left, top, right, bottom); + } + + @BindingAdapter("contentPaddingBottom") + public static void setContentPaddingBottom(CardView view, int bottom) { + int left = view.getContentPaddingLeft(); + int top = view.getContentPaddingTop(); + int right = view.getContentPaddingRight(); + view.setContentPadding(left, top, right, bottom); + } +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/CheckedTextViewBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/CheckedTextViewBindingAdapter.java new file mode 100644 index 0000000000000..3b16d080214f2 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/CheckedTextViewBindingAdapter.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.CheckedTextView", attribute = "android:checkMark", method = "setCheckMarkDrawable"), + @BindingMethod(type = "android.widget.CheckedTextView", attribute = "android:checkMarkTint", method = "setCheckMarkTintList"), +}) +public class CheckedTextViewBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/CompoundButtonBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/CompoundButtonBindingAdapter.java new file mode 100644 index 0000000000000..7115bc5c4f1c7 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/CompoundButtonBindingAdapter.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.CompoundButton", attribute = "android:buttonTint", method = "setButtonTintList"), +}) +public class CompoundButtonBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/Converters.java b/tools/data-binding/library/src/main/java/android/binding/adapters/Converters.java new file mode 100644 index 0000000000000..5982dc123f2a1 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/Converters.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingConversion; +import android.content.res.ColorStateList; +import android.graphics.drawable.ColorDrawable; + +public class Converters { + @BindingConversion + public static ColorDrawable convertColorToDrawable(int color) { + return new ColorDrawable(color); + } + + @BindingConversion + public static ColorStateList convertColorToColorStateList(int color) { + return ColorStateList.valueOf(color); + } +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/FrameLayoutBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/FrameLayoutBindingAdapter.java new file mode 100644 index 0000000000000..49aa6a5b0d9c8 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/FrameLayoutBindingAdapter.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.FrameLayout", attribute = "android:foregroundTint", method = "setForegroundTintList"), +}) +public class FrameLayoutBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/ImageViewBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/ImageViewBindingAdapter.java new file mode 100644 index 0000000000000..f8f06df1853c8 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/ImageViewBindingAdapter.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.ImageView", attribute = "src", method = "setImageDrawable"), + @BindingMethod(type = "android.widget.ImageView", attribute = "tint", method = "setImageTintList"), + @BindingMethod(type = "android.widget.ImageView", attribute = "tintMode", method = "setImageTintMode"), +}) +public class ImageViewBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/LinearLayoutBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/LinearLayoutBindingAdapter.java new file mode 100644 index 0000000000000..b0a4cc43e8d56 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/LinearLayoutBindingAdapter.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.LinearLayout", attribute = "android:divider", method = "setDividerDrawable"), + @BindingMethod(type = "android.widget.LinearLayout", attribute = "android:measureWithLargestChild", method = "setMeasureWithLargestChildEnabled"), +}) +public class LinearLayoutBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/ProgressBarBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/ProgressBarBindingAdapter.java new file mode 100644 index 0000000000000..b0d6249e0dcfa --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/ProgressBarBindingAdapter.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.ProgressBar", attribute = "android:indeterminateTint", method = "setIndeterminateTintList"), + @BindingMethod(type = "android.widget.ProgressBar", attribute = "android:progressTint", method = "setProgressTintList"), + @BindingMethod(type = "android.widget.ProgressBar", attribute = "android:secondaryProgressTint", method = "setSecondaryProgressTintList"), +}) +public class ProgressBarBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/RadioGroupBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/RadioGroupBindingAdapter.java new file mode 100644 index 0000000000000..8d21b7af3108e --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/RadioGroupBindingAdapter.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.RadioGroup", attribute = "android:checkedButton", method = "check"), +}) +public class RadioGroupBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/SpinnerBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/SpinnerBindingAdapter.java new file mode 100644 index 0000000000000..34a62c7eebc59 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/SpinnerBindingAdapter.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.Spinner", attribute = "android:popupBackground", method = "setPopupBackgroundDrawable"), +}) +public class SpinnerBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/SwitchBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/SwitchBindingAdapter.java new file mode 100644 index 0000000000000..3645118a3a2dc --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/SwitchBindingAdapter.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.annotation.TargetApi; +import android.binding.BindingAdapter; +import android.binding.BindingMethod; +import android.binding.BindingMethods; +import android.os.Build; +import android.widget.Switch; + +@BindingMethods({ + @BindingMethod(type = "android.widget.Switch", attribute = "android:thumb", method = "setThumbDrawable"), + @BindingMethod(type = "android.widget.Switch", attribute = "android:track", method = "setTrackDrawable"), +}) +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) +public class SwitchBindingAdapter { + + @BindingAdapter("android:switchTextAppearance") + public static void setSwitchTextAppearance(Switch view, int value) { + view.setSwitchTextAppearance(null, value); + } +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/SwitchCompatBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/SwitchCompatBindingAdapter.java new file mode 100644 index 0000000000000..4c3c996a9f63d --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/SwitchCompatBindingAdapter.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingAdapter; +import android.binding.BindingMethod; +import android.binding.BindingMethods; +import android.support.v7.widget.SwitchCompat; + +@BindingMethods({ + @BindingMethod(type = "android.support.v7.widget.SwitchCompat", attribute = "android:thumb", method = "setThumbDrawable"), + @BindingMethod(type = "android.support.v7.widget.SwitchCompat", attribute = "android:track", method = "setTrackDrawable"), +}) +public class SwitchCompatBindingAdapter { + + @BindingAdapter("android:switchTextAppearance") + public static void setSwitchTextAppearance(SwitchCompat view, int value) { + view.setSwitchTextAppearance(null, value); + } +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/TabWidgetBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/TabWidgetBindingAdapter.java new file mode 100644 index 0000000000000..8c58e91589192 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/TabWidgetBindingAdapter.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.widget.TabWidget", attribute = "android:divider", method = "setDividerDrawable"), + @BindingMethod(type = "android.widget.TabWidget", attribute = "android:tabStripEnabled", method = "setStripEnabled"), + @BindingMethod(type = "android.widget.TabWidget", attribute = "android:tabStripLeft", method = "setLeftStripDrawable"), + @BindingMethod(type = "android.widget.TabWidget", attribute = "android:tabStripRight", method = "setRightStripDrawable"), +}) +public class TabWidgetBindingAdapter { + +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/TableLayoutBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/TableLayoutBindingAdapter.java new file mode 100644 index 0000000000000..a8bc916013073 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/TableLayoutBindingAdapter.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingAdapter; +import android.util.SparseBooleanArray; +import android.widget.TableLayout; + +import java.util.regex.Pattern; + +public class TableLayoutBindingAdapter { + + private static final int MAX_COLUMNS = 20; + + private static Pattern sColumnPattern = Pattern.compile("\\s*,\\s*"); + + @BindingAdapter("android:collapseColumns") + public static void setCollapseColumns(TableLayout view, CharSequence columnsStr) { + SparseBooleanArray columns = parseColumns(columnsStr); + for (int i = 0; i < MAX_COLUMNS; i++) { + boolean isCollapsed = columns.get(i, false); + if (isCollapsed != view.isColumnCollapsed(i)) { + view.setColumnCollapsed(i, isCollapsed); + } + } + } + + @BindingAdapter("android:shrinkColumns") + public static void setShrinkColumns(TableLayout view, CharSequence columnsStr) { + if (columnsStr.charAt(0) == '*') { + view.setShrinkAllColumns(true); + } else { + view.setShrinkAllColumns(false); + SparseBooleanArray columns = parseColumns(columnsStr); + for (int i = 0; i < MAX_COLUMNS; i++) { + boolean shrinkable = columns.get(i, false); + if (shrinkable != view.isColumnShrinkable(i)) { + view.setColumnShrinkable(i, shrinkable); + } + } + } + } + + @BindingAdapter("android:stretchColumns") + public static void setStretchColumns(TableLayout view, CharSequence columnsStr) { + if (columnsStr.charAt(0) == '*') { + view.setStretchAllColumns(true); + } else { + view.setStretchAllColumns(false); + SparseBooleanArray columns = parseColumns(columnsStr); + for (int i = 0; i < MAX_COLUMNS; i++) { + boolean stretchable = columns.get(i, false); + if (stretchable != view.isColumnStretchable(i)) { + view.setColumnStretchable(i, stretchable); + } + } + } + } + + private static SparseBooleanArray parseColumns(CharSequence sequence) { + SparseBooleanArray columns = new SparseBooleanArray(); + String[] columnDefs = sColumnPattern.split(sequence); + + for (String columnIdentifier : columnDefs) { + try { + int columnIndex = Integer.parseInt(columnIdentifier); + // only valid, i.e. positive, columns indexes are handled + if (columnIndex >= 0) { + // putting true in this sparse array indicates that the + // column index was defined in the XML file + columns.put(columnIndex, true); + } + } catch (NumberFormatException e) { + // we just ignore columns that don't exist + } + } + + return columns; + } +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/TextViewBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/TextViewBindingAdapter.java new file mode 100644 index 0000000000000..d98704e1370ea --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/TextViewBindingAdapter.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingAdapter; +import android.binding.BindingMethod; +import android.binding.BindingMethods; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.InputFilter; +import android.text.method.DialerKeyListener; +import android.text.method.DigitsKeyListener; +import android.text.method.KeyListener; +import android.text.method.PasswordTransformationMethod; +import android.text.method.TextKeyListener; +import android.util.Log; +import android.util.TypedValue; +import android.widget.TextView; + +@BindingMethods({ + @BindingMethod(type = "android.widget.TextView", attribute = "android:autoLink", method = "setAutoLinkMask"), + @BindingMethod(type = "android.widget.TextView", attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"), + @BindingMethod(type = "android.widget.TextView", attribute = "android:editorExtras", method = "setInputExtras"), + @BindingMethod(type = "android.widget.TextView", attribute = "android:inputType", method = "setRawInputType"), + @BindingMethod(type = "android.widget.TextView", attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"), + @BindingMethod(type = "android.widget.TextView", attribute = "android:textAllCaps", method = "setAllCaps"), + @BindingMethod(type = "android.widget.TextView", attribute = "android:textColorHighlight", method = "setHighlightColor"), + @BindingMethod(type = "android.widget.TextView", attribute = "android:textColorHint", method = "setHintTextColor"), + @BindingMethod(type = "android.widget.TextView", attribute = "android:textColorLink", method = "setLinkTextColor"), +}) +public class TextViewBindingAdapter { + + private static final String TAG = "TextViewBindingAdapters"; + + public static final int INTEGER = 0x01; + + public static final int SIGNED = 0x03; + + public static final int DECIMAL = 0x05; + + @BindingAdapter("android:autoText") + public static void setAutoText(TextView view, boolean autoText) { + KeyListener listener = view.getKeyListener(); + + TextKeyListener.Capitalize capitalize = TextKeyListener.Capitalize.NONE; + + if (listener instanceof AutoCapKeyListener) { + capitalize = ((AutoCapKeyListener) listener).getCapitalize(); + } + view.setKeyListener(new AutoCapKeyListener(capitalize, autoText)); + } + + @BindingAdapter("android:capitalize") + public static void setAutoText(TextView view, TextKeyListener.Capitalize capitalize) { + KeyListener listener = view.getKeyListener(); + + boolean autoText = false; + + if (listener instanceof AutoCapKeyListener) { + autoText = ((AutoCapKeyListener) listener).isAutoText(); + } + view.setKeyListener(new AutoCapKeyListener(capitalize, autoText)); + } + + @BindingAdapter("android:bufferType") + public static void setBufferType(TextView view, TextView.BufferType bufferType) { + view.setText(view.getText(), bufferType); + } + + @BindingAdapter("android:digits") + public static void setDigits(TextView view, CharSequence digits) { + if (digits != null) { + view.setKeyListener(DigitsKeyListener.getInstance(digits.toString())); + } else if (view.getKeyListener() instanceof DigitsKeyListener) { + view.setKeyListener(null); + } + } + + @BindingAdapter("android:numeric") + public static void setNumeric(TextView view, int numeric) { + view.setKeyListener(DigitsKeyListener.getInstance((numeric & SIGNED) != 0, + (numeric & DECIMAL) != 0)); + } + + @BindingAdapter("android:phoneNumber") + public static void setPhoneNumber(TextView view, boolean phoneNumber) { + if (phoneNumber) { + view.setKeyListener(DialerKeyListener.getInstance()); + } else if (view.getKeyListener() instanceof DialerKeyListener) { + view.setKeyListener(null); + } + } + + @BindingAdapter("android:drawableBottom") + public static void setDrawableBottom(TextView view, Drawable drawable) { + Drawable[] drawables = view.getCompoundDrawables(); + view.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawable); + } + + @BindingAdapter("android:drawableLeft") + public static void setDrawableLeft(TextView view, Drawable drawable) { + Drawable[] drawables = view.getCompoundDrawables(); + view.setCompoundDrawables(drawable, drawables[1], drawables[2], drawables[3]); + } + + @BindingAdapter("android:drawableRight") + public static void setDrawableRight(TextView view, Drawable drawable) { + Drawable[] drawables = view.getCompoundDrawables(); + view.setCompoundDrawables(drawables[0], drawables[1], drawable, drawables[3]); + } + + @BindingAdapter("android:drawableTop") + public static void setDrawableTop(TextView view, Drawable drawable) { + Drawable[] drawables = view.getCompoundDrawables(); + view.setCompoundDrawables(drawables[0], drawable, drawables[2], drawables[3]); + } + + @BindingAdapter("android:drawableStart") + public static void setDrawableStart(TextView view, Drawable drawable) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + setDrawableLeft(view, drawable); + } else { + Drawable[] drawables = view.getCompoundDrawablesRelative(); + view.setCompoundDrawables(drawable, drawables[1], drawables[2], drawables[3]); + } + } + + @BindingAdapter("android:drawableEnd") + public static void setDrawableEnd(TextView view, Drawable drawable) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + setDrawableRight(view, drawable); + } else { + Drawable[] drawables = view.getCompoundDrawablesRelative(); + view.setCompoundDrawables(drawables[0], drawables[1], drawable, drawables[3]); + } + } + + @BindingAdapter("android:imeActionLabel") + public static void setImeActionLabel(TextView view, CharSequence value) { + view.setImeActionLabel(value, view.getImeActionId()); + } + + @BindingAdapter("android:imeActionId") + public static void setImeActionLabel(TextView view, int value) { + view.setImeActionLabel(view.getImeActionLabel(), value); + } + + @BindingAdapter("android:inputMethod") + public static void setInputMethod(TextView view, CharSequence inputMethod) { + try { + Class c = Class.forName(inputMethod.toString()); + view.setKeyListener((KeyListener) c.newInstance()); + } catch (ClassNotFoundException e) { + Log.e(TAG, "Could not create input method: " + inputMethod, e); + } catch (InstantiationException e) { + Log.e(TAG, "Could not create input method: " + inputMethod, e); + } catch (IllegalAccessException e) { + Log.e(TAG, "Could not create input method: " + inputMethod, e); + } + } + + @BindingAdapter("android:lineSpacingExtra") + public static void setLineSpacingExtra(TextView view, float value) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.setLineSpacing(value, view.getLineSpacingMultiplier()); + } else { + view.setLineSpacing(value, 1); + } + } + + @BindingAdapter("android:lineSpacingMultiplier") + public static void setLineSpacingMultiplier(TextView view, float value) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.setLineSpacing(view.getLineSpacingExtra(), value); + } else { + view.setLineSpacing(0, value); + } + } + + @BindingAdapter("android:maxLength") + public static void setMaxLength(TextView view, int value) { + InputFilter[] filters = view.getFilters(); + if (filters == null) { + filters = new InputFilter[]{ + new InputFilter.LengthFilter(value) + }; + } else { + boolean foundMaxLength = false; + for (int i = 0; i < filters.length; i++) { + InputFilter filter = filters[i]; + if (filter instanceof InputFilter.LengthFilter) { + foundMaxLength = true; + boolean replace = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + replace = ((InputFilter.LengthFilter) filter).getMax() != value; + } + if (replace) { + filters[i] = new InputFilter.LengthFilter(value); + } + break; + } + } + if (!foundMaxLength) { + // can't use Arrays.copyOf -- it shows up in API 9 + InputFilter[] oldFilters = filters; + filters = new InputFilter[oldFilters.length + 1]; + System.arraycopy(oldFilters, 0, filters, 0, oldFilters.length); + filters[filters.length - 1] = new InputFilter.LengthFilter(value); + } + } + view.setFilters(filters); + } + + @BindingAdapter("android:password") + public static void setPassword(TextView view, boolean password) { + if (password) { + view.setTransformationMethod(PasswordTransformationMethod.getInstance()); + } else if (view.getTransformationMethod() instanceof PasswordTransformationMethod) { + view.setTransformationMethod(null); + } + } + + @BindingAdapter("android:shadowColor") + public static void setShadowColor(TextView view, int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + float dx = view.getShadowDx(); + float dy = view.getShadowDy(); + float r = view.getShadowRadius(); + view.setShadowLayer(r, dx, dy, color); + } + } + + @BindingAdapter("android:shadowDx") + public static void setShadowDx(TextView view, float dx) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + int color = view.getShadowColor(); + float dy = view.getShadowDy(); + float r = view.getShadowRadius(); + view.setShadowLayer(r, dx, dy, color); + } + } + + @BindingAdapter("android:shadowDy") + public static void setShadowDy(TextView view, float dy) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + int color = view.getShadowColor(); + float dx = view.getShadowDx(); + float r = view.getShadowRadius(); + view.setShadowLayer(r, dx, dy, color); + } + } + + @BindingAdapter("android:shadowRadius") + public static void setShadowRadius(TextView view, float r) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + int color = view.getShadowColor(); + float dx = view.getShadowDx(); + float dy = view.getShadowDy(); + view.setShadowLayer(r, dx, dy, color); + } + } + + @BindingAdapter("android:textSize") + public static void setTextSize(TextView view, float size) { + view.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); + } + + private static class AutoCapKeyListener extends TextKeyListener { + + private final Capitalize mCapitalize; + + private final boolean mAutoText; + + public AutoCapKeyListener(Capitalize cap, boolean autoText) { + super(cap, autoText); + mCapitalize = cap; + mAutoText = autoText; + } + + public Capitalize getCapitalize() { + return mCapitalize; + } + + public boolean isAutoText() { + return mAutoText; + } + } +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/ViewBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/ViewBindingAdapter.java new file mode 100644 index 0000000000000..01ec43c536ce2 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/ViewBindingAdapter.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingAdapter; +import android.binding.BindingMethod; +import android.binding.BindingMethods; +import android.os.Build; +import android.view.View; + +@BindingMethods({ + @BindingMethod(type = "android.view.View", attribute = "android:backgroundTint", method = "setBackgroundTintList"), + @BindingMethod(type = "android.view.View", attribute = "android:fadeScrollbars", method = "setScrollbarFadingEnabled"), + @BindingMethod(type = "android.view.View", attribute = "android:nextFocusForward", method = "setNextFocusForwardId"), + @BindingMethod(type = "android.view.View", attribute = "android:nextFocusLeft", method = "setNextFocusLeftId"), + @BindingMethod(type = "android.view.View", attribute = "android:nextFocusRight", method = "setNextFocusRightId"), + @BindingMethod(type = "android.view.View", attribute = "android:nextFocusUp", method = "setNextFocusUpId"), + @BindingMethod(type = "android.view.View", attribute = "android:padding", method = "setPaddingRelative"), + @BindingMethod(type = "android.view.View", attribute = "android:requiresFadingEdge", method = "setVerticalFadingEdgeEnabled"), + @BindingMethod(type = "android.view.View", attribute = "android:scrollbarDefaultDelayBeforeFade", method = "setScrollBarDefaultDelayBeforeFade"), + @BindingMethod(type = "android.view.View", attribute = "android:scrollbarFadeDuration", method = "setScrollBarFadeDuration"), + @BindingMethod(type = "android.view.View", attribute = "android:scrollbarSize", method = "setScrollBarSize"), + @BindingMethod(type = "android.view.View", attribute = "android:scrollbarStyle", method = "setScrollBarStyle"), + @BindingMethod(type = "android.view.View", attribute = "android:transformPivotX", method = "setPivotX"), + @BindingMethod(type = "android.view.View", attribute = "android:transformPivotY", method = "setPivotY"), +}) +public class ViewBindingAdapter { + + @BindingAdapter("android:background") + public static void setBackground(View view, int color) { + view.setBackgroundColor(color); + } + + @BindingAdapter("android:padding") + public static void setPadding(View view, int padding) { + view.setPadding(padding, padding, padding, padding); + } + + @BindingAdapter("android:paddingBottom") + public static void setPaddingBottom(View view, int padding) { + view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), + padding); + } + + @BindingAdapter("android:paddingEnd") + public static void setPaddingEnd(View view, int padding) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(), padding, + view.getPaddingBottom()); + } else { + view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), padding, + view.getPaddingBottom()); + } + } + + @BindingAdapter("android:paddingLeft") + public static void setPaddingLeft(View view, int padding) { + view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), + view.getPaddingBottom()); + } + + @BindingAdapter("android:paddingRight") + public static void setPaddingRight(View view, int padding) { + view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), padding, + view.getPaddingBottom()); + } + + @BindingAdapter("android:paddingStart") + public static void setPaddingStart(View view, int padding) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + view.setPaddingRelative(padding, view.getPaddingTop(), view.getPaddingEnd(), + view.getPaddingBottom()); + } else { + view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), + view.getPaddingBottom()); + } + } + + @BindingAdapter("android:paddingTop") + public static void setPaddingTop(View view, int padding) { + view.setPadding(view.getPaddingLeft(), padding, view.getPaddingRight(), + view.getPaddingBottom()); + } +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/ViewGroupBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/ViewGroupBindingAdapter.java new file mode 100644 index 0000000000000..5c2179da21973 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/ViewGroupBindingAdapter.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.animation.LayoutTransition; +import android.annotation.TargetApi; +import android.binding.BindingMethod; +import android.binding.BindingMethods; +import android.os.Build; +import android.view.ViewGroup; + +@BindingMethods({ + @BindingMethod(type = "android.view.ViewGroup", attribute = "android:alwaysDrawnWithCache", method = "setAlwaysDrawnWithCacheEnabled"), + @BindingMethod(type = "android.view.ViewGroup", attribute = "android:animationCache", method = "setAnimationCacheEnabled"), + @BindingMethod(type = "android.view.ViewGroup", attribute = "android:splitMotionEvents", method = "setMotionEventSplittingEnabled"), +}) +public class ViewGroupBindingAdapter { + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static void setAnimateLayoutChanges(ViewGroup view, boolean animate) { + if (animate) { + view.setLayoutTransition(new LayoutTransition()); + } else { + view.setLayoutTransition(null); + } + } +} diff --git a/tools/data-binding/library/src/main/java/android/binding/adapters/ViewStubBindingAdapter.java b/tools/data-binding/library/src/main/java/android/binding/adapters/ViewStubBindingAdapter.java new file mode 100644 index 0000000000000..48860be3ce2e8 --- /dev/null +++ b/tools/data-binding/library/src/main/java/android/binding/adapters/ViewStubBindingAdapter.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.binding.adapters; + +import android.binding.BindingMethod; +import android.binding.BindingMethods; + +@BindingMethods({ + @BindingMethod(type = "android.view.ViewStub", attribute = "android:layout", method = "setLayoutResource") +}) +public class ViewStubBindingAdapter { + +} diff --git a/tools/data-binding/samples/BindingDemo/app/build.gradle b/tools/data-binding/samples/BindingDemo/app/build.gradle index 247331c93365c..06bbe32ad57ec 100644 --- a/tools/data-binding/samples/BindingDemo/app/build.gradle +++ b/tools/data-binding/samples/BindingDemo/app/build.gradle @@ -40,9 +40,6 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - //sourceSets { - // main.java.srcDirs += generatedSources - //} } android.applicationVariants.all { variant -> @@ -60,7 +57,9 @@ dependencies { compile 'com.android.support:appcompat-v7:21.+' compile 'com.android.databinding:library:0.3-SNAPSHOT@aar' compile 'com.android.support:recyclerview-v7:21.0.2' - compile 'com.android.support:gridlayout-v7:21+' - compile 'com.android.support:cardview-v7:21.0.2' - provided 'com.android.databinding:annotationprocessor:0.1-SNAPSHOT' + compile 'com.android.support:gridlayout-v7:21.+' + compile 'com.android.support:cardview-v7:21.+' + compile 'com.android.databinding:annotations:0.3-SNAPSHOT' + provided 'com.android.databinding:compiler:0.3-SNAPSHOT' + provided 'com.android.databinding:annotationprocessor:0.3-SNAPSHOT' } diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/TestBindingAdapter.java b/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/TestBindingAdapter.java deleted file mode 100644 index f1a1b1e4d323d..0000000000000 --- a/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/TestBindingAdapter.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.android.example.bindingdemo.vo; - -import android.binding.BindingAdapter; -import android.widget.TextView; - -public class TestBindingAdapter { - @BindingAdapter(attribute = "android:text") - public static void setText(TextView view, String value) { - view.setText(value); - } - @BindingAdapter(attribute = "android:text") - public static void setObjectText(Object view, String value) { - ((TextView)view).setText(String.valueOf(value)); - } - @BindingAdapter(attribute = "android:text") - public static void setTextObject(TextView view, Object value) { - view.setText(String.valueOf(value)); - } - @BindingAdapter(attribute = "android:text") - public static void setTextFloat(TextView view, float value) { - view.setText(String.valueOf(value)); - } -} diff --git a/tools/data-binding/settings.gradle b/tools/data-binding/settings.gradle index 5dea8c3c417ab..0ef7dc5e7147f 100644 --- a/tools/data-binding/settings.gradle +++ b/tools/data-binding/settings.gradle @@ -1,6 +1,7 @@ include ':library' include ':compiler' include ':gradlePlugin' +include ':annotations' include ':grammerBuilder' include ':annotationprocessor' -include 'xmlGrammer' +include ':xmlGrammer'