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.
This commit is contained in:
George Mount
2015-01-20 08:38:57 -08:00
parent 89ac4e022b
commit cbecb900f6
42 changed files with 2212 additions and 498 deletions

View File

@@ -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'
}

View File

@@ -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<String, HashMap<AccessorKey, BindingAdapterDescription>> mAdapters;
private BindingAdapterStore(Object adapters) {
if (adapters == null) {
mAdapters = new HashMap<>();
} else {
mAdapters = (HashMap<String, HashMap<AccessorKey, BindingAdapterDescription>>) 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<AccessorKey, BindingAdapterDescription> 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<AccessorKey> removed = new ArrayList<>();
for (HashMap<AccessorKey, BindingAdapterDescription> 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<AccessorKey, BindingAdapterDescription> 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;
}
}
}
}

View File

@@ -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<String> readIntermediateFile() {
HashSet<String> 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<String>) 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<String> 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");
}
}

View File

@@ -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<? extends TypeElement> 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<? extends VariableElement> 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;
}
}

View File

@@ -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<? extends TypeElement> 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<? extends VariableElement> 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<String> 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);
}
}

View File

@@ -1,2 +1,2 @@
com.android.databinding.annotationprocessor.ProcessBindable
com.android.databinding.annotationprocessor.ProcessBindingAdapters
com.android.databinding.annotationprocessor.ProcessMethodAdapters

View File

@@ -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'
}
}
}

View File

@@ -20,5 +20,5 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD)
public @interface BindingAdapter {
String attribute();
String value();
}

View File

@@ -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 {
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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 {

View File

@@ -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<String, HashMap<AccessorKey, MethodDescription>> mAdapters;
private final HashMap<String, HashMap<String, MethodDescription>> mRenamed;
private final HashMap<String, HashMap<String, MethodDescription>> mConversions;
private ArrayList<ConversionMethod> mConversionMethods;
private HashMap<String, ArrayList<AdaptedMethod>> mAdaptedMethods;
private HashMap<String, ArrayList<RenamedMethod>> mRenamedMethods;
private SetterStore(HashMap<String, HashMap<AccessorKey, MethodDescription>> adapters,
HashMap<String, HashMap<String, MethodDescription>> renamedMethods,
HashMap<String, HashMap<String, MethodDescription>> 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<String, HashMap<AccessorKey, MethodDescription>> adapters = new HashMap<>();
HashMap<String, HashMap<String, MethodDescription>> renamed = new HashMap<>();
HashMap<String, HashMap<String, MethodDescription>> 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<String, HashMap<AccessorKey, MethodDescription>> adapters = new HashMap<>();
HashMap<String, HashMap<String, MethodDescription>> renamedMethods = new HashMap<>();
HashMap<String, HashMap<String, MethodDescription>> conversionMethods = new HashMap<>();
String resourceName = SetterStore.class.getPackage().getName().replace('.', '/') +
"/setter_store.bin";
try {
Enumeration<URL> 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<String, HashMap<AccessorKey, MethodDescription>> adapters
= (HashMap<String, HashMap<AccessorKey, MethodDescription>>) in.readObject();
HashMap<String, HashMap<String, MethodDescription>> renamedMethods
= (HashMap<String, HashMap<String, MethodDescription>>) in.readObject();
HashMap<String, HashMap<String, MethodDescription>> conversionMethods
= (HashMap<String, HashMap<String, MethodDescription>>) in.readObject();
return new SetterStore(adapters, renamedMethods, conversionMethods);
}
public void addRenamedMethod(String attribute, String declaringClass, String method,
TypeElement declaredOn) {
HashMap<String, MethodDescription> 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<AccessorKey, MethodDescription> adapters = mAdapters.get(attribute);
if (adapters == null) {
adapters = new HashMap<>();
mAdapters.put(attribute, adapters);
}
List<? extends VariableElement> 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<? extends VariableElement> parameters = conversionMethod.getParameters();
String fromType = getQualifiedName(parameters.get(0).asType());
String toType = getQualifiedName(conversionMethod.getReturnType());
MethodDescription methodDescription = new MethodDescription(conversionMethod);
HashMap<String, MethodDescription> convertTo = mConversions.get(fromType);
if (convertTo == null) {
convertTo = new HashMap<>();
mConversions.put(fromType, convertTo);
}
convertTo.put(toType, methodDescription);
}
public void clear(Set<String> classes) {
ArrayList<AccessorKey> removedAccessorKeys = new ArrayList<>();
for (HashMap<AccessorKey, MethodDescription> 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<String> removedRenamed = new ArrayList<>();
for (HashMap<String, MethodDescription> 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<String> removedConversions = new ArrayList<>();
for (HashMap<String, MethodDescription> 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<AdaptedMethod> 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<RenamedMethod> 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<String, HashMap<AccessorKey, MethodDescription>> adapters,
HashMap<String, HashMap<String, MethodDescription>> renamed,
HashMap<String, HashMap<String, MethodDescription>> 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<String, HashMap<AccessorKey, MethodDescription>> adapters2 =
(HashMap<String, HashMap<AccessorKey, MethodDescription>>) in.readObject();
merge(adapters, adapters2);
HashMap<String, HashMap<String, MethodDescription>> renamed2 =
(HashMap<String, HashMap<String, MethodDescription>>) in.readObject();
merge(renamed, renamed2);
HashMap<String, HashMap<String, MethodDescription>> conversions2 =
(HashMap<String, HashMap<String, MethodDescription>>) in.readObject();
merge(conversions, conversions2);
} finally {
if (inputStream != null) {
inputStream.close();
}
if (jarFile != null) {
jarFile.close();
}
}
}
private static <K, V> void merge(HashMap<K, HashMap<V, MethodDescription>> first,
HashMap<K, HashMap<V, MethodDescription>> second) {
for (K key : second.keySet()) {
HashMap<V, MethodDescription> firstVals = first.get(key);
HashMap<V, MethodDescription> 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<String, MethodDescription> 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<AdaptedMethod> adaptedMethods = new ArrayList<>();
mAdaptedMethods.put(attribute, adaptedMethods);
HashMap<AccessorKey, MethodDescription> 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<RenamedMethod> renamedMethods = new ArrayList<>();
mRenamedMethods.put(attribute, renamedMethods);
HashMap<String, MethodDescription> 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;
}
}
}

View File

@@ -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<kotlin.Int>()
"short" -> return javaClass<kotlin.Short>()
"long" -> return javaClass<kotlin.Long>()
"float" -> return javaClass<kotlin.Float>()
"double" -> return javaClass<kotlin.Double>()
"boolean" -> return javaClass<kotlin.Boolean>()
"char" -> return javaClass<kotlin.Char>()
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) {

View File

@@ -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()}"}

View File

@@ -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 ->

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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<CharSequence> adapter =
new ArrayAdapter<CharSequence>(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);
}
}
}

View File

@@ -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 {
}

View File

@@ -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);
}
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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);
}
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}
}

View File

@@ -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 {
}

View File

@@ -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'
}

View File

@@ -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));
}
}

View File

@@ -1,6 +1,7 @@
include ':library'
include ':compiler'
include ':gradlePlugin'
include ':annotations'
include ':grammerBuilder'
include ':annotationprocessor'
include 'xmlGrammer'
include ':xmlGrammer'