summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Mount <mount@google.com>2016-03-15 16:35:50 -0700
committerGeorge Mount <mount@google.com>2016-03-17 15:49:29 -0700
commit11df39c91611b9ff2d7c87a9a9829251a015bccf (patch)
tree6239f0e399c08457fcabd397adf4024af57cb5a3
parent3b54f63a65ad1ac66ed424efc595598ace99ce9f (diff)
downloaddata-binding-11df39c91611b9ff2d7c87a9a9829251a015bccf.tar.gz
Added simple inverted String conversion.
When binding a primitive to an EditText, a common pattern is to use '@{"" + value}'. This, however, doesn't allow for a two-way data binding expressions. To mitigate the need for conversion functions, a simple inversion for this expression wsa implemented that just converts value from a String when possible. This CL also fixes a bug in which a method matching the first parameter was always chosen, reguardless of the second and further parameters. Change-Id: I36828d9f54d2073965358fceb140b2d5e6328919
-rw-r--r--compilationTests/build.gradle11
-rw-r--r--compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java53
-rw-r--r--compiler/src/main/java/android/databinding/tool/CompilerChef.java27
-rw-r--r--compiler/src/main/java/android/databinding/tool/expr/MathExpr.java50
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/InjectedClass.java (renamed from compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClass.java)41
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/InjectedField.java (renamed from compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassField.java)4
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/InjectedMethod.java (renamed from compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassMethod.java)67
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java32
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java3
-rw-r--r--compiler/src/main/kotlin/android/databinding/tool/writer/DynamicUtilWriter.kt81
-rw-r--r--compiler/src/test/java/android/databinding/tool/reflection/java/JavaAnalyzer.java4
-rw-r--r--extensions/library/src/main/java/android/databinding/ViewDataBinding.java70
-rw-r--r--integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/FindMethodTest.java7
-rw-r--r--integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/TwoWayBindingAdapterTest.java101
-rw-r--r--integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObject.java13
-rw-r--r--integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TwoWayBindingObject.java12
-rw-r--r--integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml10
-rw-r--r--integration-tests/TestApp/app/src/main/res/layout/two_way.xml48
18 files changed, 565 insertions, 69 deletions
diff --git a/compilationTests/build.gradle b/compilationTests/build.gradle
index 0ca1877a..be30dc26 100644
--- a/compilationTests/build.gradle
+++ b/compilationTests/build.gradle
@@ -9,9 +9,18 @@ dependencies {
testCompile 'commons-io:commons-io:2.4'
testCompile 'commons-codec:commons-codec:1.10'
testCompile project(':dataBinding:compilerCommon')
+ testCompile project(':dataBinding:compiler')
}
afterEvaluate {
tasks['test'].systemProperties['useReleaseVersion'] = dataBindingConfig.inReleaseBuild ? 'true' : 'false'
tasks['test'].systemProperties['addRemoteRepos'] = dataBindingConfig.addRemoteRepos ? 'true' : 'false'
-} \ No newline at end of file
+}
+
+sourceSets {
+ test {
+ java {
+ srcDirs += "${project.rootProject.getProjectDir().getAbsolutePath()}/compiler/src/test/java/android/databinding/tool/reflection/java"
+ }
+ }
+}
diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java
index a7aa9d6b..a4c7af9c 100644
--- a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java
+++ b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java
@@ -16,24 +16,41 @@
package android.databinding.compilationTest;
+import android.databinding.tool.CompilerChef;
import android.databinding.tool.processing.ErrorMessages;
import android.databinding.tool.processing.ScopedErrorReport;
import android.databinding.tool.processing.ScopedException;
+import android.databinding.tool.reflection.InjectedClass;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.reflection.java.JavaAnalyzer;
import android.databinding.tool.store.Location;
import com.google.common.base.Joiner;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.PrefixFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -346,4 +363,40 @@ public class SimpleCompilationTest extends BaseCompilationTest {
assertEquals("The attribute android:textAttrChanged is a two-way binding event attribute " +
"and cannot be assigned.", ex.getBareMessage());
}
+
+ @SuppressWarnings("deprecated")
+ @Test
+ public void testDynamicUtilMembers() throws Throwable {
+ prepareProject();
+ CompilationResult result = runGradle("assembleDebug");
+ assertEquals(result.error, 0, result.resultCode);
+ assertTrue("there should not be any errors " + result.error,
+ StringUtils.isEmpty(result.error));
+ assertTrue("Test sanity, should compile fine",
+ result.resultContainsText("BUILD SUCCESSFUL"));
+ File classFile = new File(testFolder,
+ "app/build/intermediates/classes/debug/android/databinding/DynamicUtil.class");
+ assertTrue(classFile.exists());
+
+ File root = new File(testFolder, "app/build/intermediates/classes/debug/");
+ URL[] urls = new URL[] {root.toURL()};
+ JavaAnalyzer.initForTests();
+ JavaAnalyzer analyzer = (JavaAnalyzer) JavaAnalyzer.getInstance();
+ ClassLoader classLoader = new URLClassLoader(urls, analyzer.getClassLoader());
+ Class dynamicUtilClass = classLoader.loadClass("android.databinding.DynamicUtil");
+
+ InjectedClass injectedClass = CompilerChef.pushDynamicUtilToAnalyzer();
+
+ // test methods
+ for (Method method : dynamicUtilClass.getMethods()) {
+ // look for the method in the injected class
+ ArrayList<ModelClass> args = new ArrayList<ModelClass>();
+ for (Class<?> param : method.getParameterTypes()) {
+ args.add(analyzer.findClass(param));
+ }
+ ModelMethod modelMethod = injectedClass.getMethod(
+ method.getName(), args, Modifier.isStatic(method.getModifiers()), false);
+ assertNotNull(modelMethod);
+ }
+ }
}
diff --git a/compiler/src/main/java/android/databinding/tool/CompilerChef.java b/compiler/src/main/java/android/databinding/tool/CompilerChef.java
index 278492ad..611f3b8f 100644
--- a/compiler/src/main/java/android/databinding/tool/CompilerChef.java
+++ b/compiler/src/main/java/android/databinding/tool/CompilerChef.java
@@ -13,6 +13,8 @@
package android.databinding.tool;
+import android.databinding.tool.reflection.InjectedClass;
+import android.databinding.tool.reflection.InjectedMethod;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.store.ResourceBundle;
@@ -71,6 +73,7 @@ public class CompilerChef {
chef.mFileWriter = fileWriter;
chef.mResourceBundle.validateMultiResLayouts();
chef.pushClassesToAnalyzer();
+ chef.pushDynamicUtilToAnalyzer();
return chef;
}
@@ -127,6 +130,30 @@ public class CompilerChef {
}
}
+ public static InjectedClass pushDynamicUtilToAnalyzer() {
+ InjectedClass injectedClass = new InjectedClass("android.databinding.DynamicUtil",
+ "java.lang.Object");
+ injectedClass.addMethod(new InjectedMethod(injectedClass, true, "getColorFromResource",
+ "int", "android.view.View", "int"));
+ injectedClass.addMethod(new InjectedMethod(injectedClass, true, "parse",
+ "boolean", "java.lang.String", "boolean"));
+ injectedClass.addMethod(new InjectedMethod(injectedClass, true, "parse",
+ "short", "java.lang.String", "short"));
+ injectedClass.addMethod(new InjectedMethod(injectedClass, true, "parse",
+ "int", "java.lang.String", "int"));
+ injectedClass.addMethod(new InjectedMethod(injectedClass, true, "parse",
+ "long", "java.lang.String", "long"));
+ injectedClass.addMethod(new InjectedMethod(injectedClass, true, "parse",
+ "float", "java.lang.String", "float"));
+ injectedClass.addMethod(new InjectedMethod(injectedClass, true, "parse",
+ "double", "java.lang.String", "double"));
+ injectedClass.addMethod(new InjectedMethod(injectedClass, true, "parse",
+ "char", "java.lang.String", "char"));
+ ModelAnalyzer analyzer = ModelAnalyzer.getInstance();
+ analyzer.injectClass(injectedClass);
+ return injectedClass;
+ }
+
public void writeDataBinderMapper(int minSdk, BRWriter brWriter) {
ensureDataBinder();
final String pkg = "android.databinding";
diff --git a/compiler/src/main/java/android/databinding/tool/expr/MathExpr.java b/compiler/src/main/java/android/databinding/tool/expr/MathExpr.java
index 63222de3..14b82c47 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/MathExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/MathExpr.java
@@ -21,9 +21,12 @@ import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.util.Preconditions;
import android.databinding.tool.writer.KCode;
+import com.google.common.collect.Lists;
+
import java.util.List;
public class MathExpr extends Expr {
+ static final String DYNAMIC_UTIL = "android.databinding.DynamicUtil";
final String mOp;
MathExpr(Expr left, String op, Expr right) {
@@ -75,16 +78,29 @@ public class MathExpr extends Expr {
public String getInvertibleError() {
if (mOp.equals("%")) {
return "The modulus operator (%) is not supported in two-way binding.";
- } else if (getResolvedType().isString()) {
- return "String concatenation operator (+) is not supported in two-way binding.";
}
- if (!getLeft().isDynamic()) {
- return getRight().getInvertibleError();
- } else if (!getRight().isDynamic()) {
- return getLeft().getInvertibleError();
- } else {
- return "Arithmetic operator " + mOp + " is not supported with two dynamic expressions.";
+
+ final Expr left = getLeft();
+ final Expr right = getRight();
+ if (left.isDynamic() == right.isDynamic()) {
+ return "Two way binding with operator " + mOp +
+ " supports only a single dynamic expressions.";
+ }
+ Expr dyn = left.isDynamic() ? left : right;
+ if (getResolvedType().isString()) {
+ Expr constExpr = left.isDynamic() ? right : left;
+
+ if (!(constExpr instanceof SymbolExpr) ||
+ !"\"\"".equals(((SymbolExpr) constExpr).getText())) {
+ return "Two-way binding with string concatenation operator (+) only supports the" +
+ " empty string constant (`` or \"\")";
+ }
+ if (!dyn.getResolvedType().unbox().isPrimitive()) {
+ return "Two-way binding with string concatenation operator (+) only supports " +
+ "primitives";
+ }
}
+ return dyn.getInvertibleError();
}
@Override
@@ -92,13 +108,19 @@ public class MathExpr extends Expr {
final Expr left = getLeft();
final Expr right = getRight();
Preconditions.check(left.isDynamic() ^ right.isDynamic(), "Two-way binding of a math " +
- "operations requires A signle dynamic expression. Neither or both sides are " +
+ "operations requires A single dynamic expression. Neither or both sides are " +
"dynamic: (%s) %s (%s)", left, mOp, right);
final Expr constExpr = (left.isDynamic() ? right : left).cloneToModel(model);
+ final Expr varExpr = left.isDynamic() ? left : right;
final Expr newValue;
switch (mOp.charAt(0)) {
case '+': // const + x = value => x = value - const
- newValue = model.math(value, "-", constExpr);
+ if (getResolvedType().isString()) {
+ // just convert back to the primitive type
+ newValue = parseInverse(model, value, varExpr);
+ } else {
+ newValue = model.math(value, "-", constExpr);
+ }
break;
case '*': // const * x = value => x = value / const
newValue = model.math(value, "/", constExpr);
@@ -120,10 +142,16 @@ public class MathExpr extends Expr {
default:
throw new IllegalStateException("Invalid math operation is not invertible: " + mOp);
}
- final Expr varExpr = left.isDynamic() ? left : right;
return varExpr.generateInverse(model, newValue, bindingClassName);
}
+ private Expr parseInverse(ExprModel model, Expr value, Expr prev) {
+ IdentifierExpr dynamicUtil = model.staticIdentifier(DYNAMIC_UTIL);
+ dynamicUtil.setUserDefinedType(DYNAMIC_UTIL);
+
+ return model.methodCall(dynamicUtil, "parse", Lists.newArrayList(value, prev));
+ }
+
@Override
public Expr cloneToModel(ExprModel model) {
return model.math(getLeft().cloneToModel(model), mOp, getRight().cloneToModel(model));
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClass.java b/compiler/src/main/java/android/databinding/tool/reflection/InjectedClass.java
index 4759a9a6..45857991 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClass.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/InjectedClass.java
@@ -18,6 +18,7 @@ package android.databinding.tool.reflection;
import android.databinding.tool.util.StringUtils;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -29,18 +30,23 @@ import java.util.Map;
*
* @see ModelAnalyzer#injectViewDataBinding(String, Map, Map)
*/
-public class InjectedBindingClass extends ModelClass {
+public class InjectedClass extends ModelClass {
private final String mClassName;
private final String mSuperClass;
- private final Map<String, String> mVariables;
- private final Map<String, String> mFields;
+ private final List<InjectedMethod> mMethods = new ArrayList<InjectedMethod>();
+ private final List<InjectedField> mFields = new ArrayList<InjectedField>();
- public InjectedBindingClass(String className, String superClass, Map<String, String> variables,
- Map<String, String> fields) {
+ public InjectedClass(String className, String superClass) {
mClassName = className;
mSuperClass = superClass;
- mVariables = variables;
- mFields = fields;
+ }
+
+ public void addField(InjectedField field) {
+ mFields.add(field);
+ }
+
+ public void addMethod(InjectedMethod method) {
+ mMethods.add(method);
}
@Override
@@ -183,12 +189,11 @@ public class InjectedBindingClass extends ModelClass {
protected ModelField[] getDeclaredFields() {
ModelClass superClass = getSuperclass();
final ModelField[] superFields = superClass.getDeclaredFields();
- final int fieldCount = superFields.length + mFields.size();
+ final int initialCount = superFields.length;
+ final int fieldCount = initialCount + mFields.size();
final ModelField[] fields = Arrays.copyOf(superFields, fieldCount);
- int index = superFields.length;
- for (String fieldName : mFields.keySet()) {
- final String fieldType = mFields.get(fieldName);
- fields[index++] = new InjectedBindingClassField(fieldName, fieldType);
+ for (int i = 0; i < mFields.size(); i++) {
+ fields[i + initialCount] = mFields.get(i);
}
return fields;
}
@@ -197,15 +202,11 @@ public class InjectedBindingClass extends ModelClass {
protected ModelMethod[] getDeclaredMethods() {
ModelClass superClass = getSuperclass();
final ModelMethod[] superMethods = superClass.getDeclaredMethods();
- final int methodCount = superMethods.length + (mVariables.size() * 2);
+ final int initialCount = superMethods.length;
+ final int methodCount = initialCount + mMethods.size();
final ModelMethod[] methods = Arrays.copyOf(superMethods, methodCount);
- int index = superMethods.length;
- for (String variableName : mVariables.keySet()) {
- final String variableType = mVariables.get(variableName);
- final String getterName = "get" + StringUtils.capitalize(variableName);
- methods[index++] = new InjectedBindingClassMethod(this, getterName, variableType, null);
- final String setterName = "set" + StringUtils.capitalize(variableName);
- methods[index++] = new InjectedBindingClassMethod(this, setterName, "void", variableType);
+ for (int i = 0; i < mMethods.size(); i++) {
+ methods[i + initialCount] = mMethods.get(i);
}
return methods;
}
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassField.java b/compiler/src/main/java/android/databinding/tool/reflection/InjectedField.java
index d15cc908..85719f1c 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassField.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/InjectedField.java
@@ -24,11 +24,11 @@ import java.util.Map;
*
* @see ModelAnalyzer#injectViewDataBinding(String, Map, Map)
*/
-public class InjectedBindingClassField extends ModelField {
+public class InjectedField extends ModelField {
private final String mType;
private final String mName;
- public InjectedBindingClassField(String name, String type) {
+ public InjectedField(String name, String type) {
mName = name;
mType = type;
}
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassMethod.java b/compiler/src/main/java/android/databinding/tool/reflection/InjectedMethod.java
index 20e24824..f47442b7 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassMethod.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/InjectedMethod.java
@@ -25,18 +25,22 @@ import java.util.Map;
*
* @see ModelAnalyzer#injectViewDataBinding(String, Map, Map)
*/
-public class InjectedBindingClassMethod extends ModelMethod {
- private final InjectedBindingClass mContainingClass;
+public class InjectedMethod extends ModelMethod {
+ private final InjectedClass mContainingClass;
private final String mName;
- private final String mReturnType;
- private final String mParameter;
-
- public InjectedBindingClassMethod(InjectedBindingClass containingClass, String name, String returnType,
- String parameter) {
+ private final String mReturnTypeName;
+ private final String[] mParameterTypeNames;
+ private ModelClass[] mParameterTypes;
+ private ModelClass mReturnType;
+ private boolean mIsStatic;
+
+ public InjectedMethod(InjectedClass containingClass, boolean isStatic, String name,
+ String returnType, String... parameters) {
mContainingClass = containingClass;
mName = name;
- mReturnType = returnType;
- mParameter = parameter;
+ mIsStatic = isStatic;
+ mReturnTypeName = returnType;
+ mParameterTypeNames = parameters;
}
@Override
@@ -46,11 +50,18 @@ public class InjectedBindingClassMethod extends ModelMethod {
@Override
public ModelClass[] getParameterTypes() {
- if (mParameter != null) {
- ModelClass parameterType = ModelAnalyzer.getInstance().findClass(mParameter, null);
- return new ModelClass[] { parameterType };
+ if (mParameterTypes == null) {
+ if (mParameterTypeNames == null) {
+ mParameterTypes = new ModelClass[0];
+ } else {
+ mParameterTypes = new ModelClass[mParameterTypeNames.length];
+ ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
+ for (int i = 0; i < mParameterTypeNames.length; i++) {
+ mParameterTypes[i] = modelAnalyzer.findClass(mParameterTypeNames[i], null);
+ }
+ }
}
- return new ModelClass[0];
+ return mParameterTypes;
}
@Override
@@ -60,8 +71,10 @@ public class InjectedBindingClassMethod extends ModelMethod {
@Override
public ModelClass getReturnType(List<ModelClass> args) {
- ModelClass returnType = ModelAnalyzer.getInstance().findClass(mReturnType, null);
- return returnType;
+ if (mReturnType == null) {
+ mReturnType = ModelAnalyzer.getInstance().findClass(mReturnTypeName, null);
+ }
+ return mReturnType;
}
@Override
@@ -81,7 +94,7 @@ public class InjectedBindingClassMethod extends ModelMethod {
@Override
public boolean isStatic() {
- return false;
+ return mIsStatic;
}
@Override
@@ -108,4 +121,26 @@ public class InjectedBindingClassMethod extends ModelMethod {
public boolean isVarArgs() {
return false;
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("public ");
+ if (mIsStatic) {
+ sb.append("static ");
+ }
+ sb.append(mReturnTypeName)
+ .append(' ')
+ .append(mName)
+ .append("(");
+ if (mParameterTypeNames != null) {
+ for (int i = 0; i < mParameterTypeNames.length; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(mParameterTypeNames[i]);
+ }
+ }
+ sb.append(')');
+ return sb.toString();
+ }
}
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java b/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java
index 8943200b..995ae219 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java
@@ -18,6 +18,7 @@ package android.databinding.tool.reflection;
import android.databinding.tool.reflection.annotation.AnnotationAnalyzer;
import android.databinding.tool.util.L;
import android.databinding.tool.util.Preconditions;
+import android.databinding.tool.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
@@ -83,8 +84,8 @@ public abstract class ModelAnalyzer {
private ModelClass mViewStubType;
private static ModelAnalyzer sAnalyzer;
- private final Map<String, InjectedBindingClass> mInjectedClasses =
- new HashMap<String, InjectedBindingClass>();
+ private final Map<String, InjectedClass> mInjectedClasses =
+ new HashMap<String, InjectedClass>();
protected void setInstance(ModelAnalyzer analyzer) {
sAnalyzer = analyzer;
@@ -234,10 +235,33 @@ public abstract class ModelAnalyzer {
public abstract TypeUtil createTypeUtil();
+ public ModelClass injectClass(InjectedClass injectedClass) {
+ mInjectedClasses.put(injectedClass.getCanonicalName(), injectedClass);
+ return injectedClass;
+ }
+
public ModelClass injectViewDataBinding(String className, Map<String, String> variables,
Map<String, String> fields) {
- InjectedBindingClass injectedClass = new InjectedBindingClass(className,
- ModelAnalyzer.VIEW_DATA_BINDING, variables, fields);
+ InjectedClass injectedClass = new InjectedClass(className,
+ ModelAnalyzer.VIEW_DATA_BINDING);
+
+ if (fields != null) {
+ for (String name : fields.keySet()) {
+ String type = fields.get(name);
+ injectedClass.addField(new InjectedField(name, type));
+ }
+ }
+ if (variables != null) {
+ for (String name : variables.keySet()) {
+ String type = variables.get(name);
+ String capName = StringUtils.capitalize(name);
+ String setName = "set" + capName;
+ String getName = "get" + capName;
+ injectedClass.addMethod(new InjectedMethod(injectedClass, false, getName, type));
+ injectedClass.addMethod(new InjectedMethod(injectedClass, false, setName, "void",
+ type));
+ }
+ }
mInjectedClasses.put(className, injectedClass);
return injectedClass;
}
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java b/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java
index 5bd214e3..3ec7ff8d 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java
@@ -96,6 +96,9 @@ public abstract class ModelMethod {
final ModelClass arg = args.get(i);
final ModelClass thisParameter = getParameter(i, parameterTypes);
final ModelClass thatParameter = other.getParameter(i, otherParameterTypes);
+ if (thisParameter.equals(thatParameter)) {
+ continue;
+ }
final int diff = compareParameter(arg, thisParameter, thatParameter);
if (diff != 0) {
return diff < 0;
diff --git a/compiler/src/main/kotlin/android/databinding/tool/writer/DynamicUtilWriter.kt b/compiler/src/main/kotlin/android/databinding/tool/writer/DynamicUtilWriter.kt
index 34ac043c..8d76b030 100644
--- a/compiler/src/main/kotlin/android/databinding/tool/writer/DynamicUtilWriter.kt
+++ b/compiler/src/main/kotlin/android/databinding/tool/writer/DynamicUtilWriter.kt
@@ -6,18 +6,77 @@ class DynamicUtilWriter() {
nl("import android.os.Build.VERSION;")
nl("import android.os.Build.VERSION_CODES;")
nl("")
- nl("public class DynamicUtil {")
- tab("@SuppressWarnings(\"deprecation\")")
- tab("public static int getColorFromResource(final android.view.View root, final int resourceId) {") {
- if (targetSdk >= 23) {
- tab("if (VERSION.SDK_INT >= VERSION_CODES.M) {") {
- tab("return root.getContext().getColor(resourceId);")
+ block("public class DynamicUtil") {
+ nl("@SuppressWarnings(\"deprecation\")")
+ block("public static int getColorFromResource(final android.view.View root, final int resourceId)") {
+ if (targetSdk >= 23) {
+ block("if (VERSION.SDK_INT >= VERSION_CODES.M)") {
+ nl("return root.getContext().getColor(resourceId);")
+ }
}
- tab("}")
+ nl("return root.getResources().getColor(resourceId);")
+ }
+
+ block("public static boolean parse(String str, boolean fallback)") {
+ block("if (str == null)") {
+ nl("return fallback;");
+ }
+ nl("return Boolean.parseBoolean(str);")
+ }
+ block("public static byte parse(String str, byte fallback)") {
+ block("try") {
+ nl("return Byte.parseByte(str);")
+ }
+ block("catch (NumberFormatException e)") {
+ nl("return fallback;")
+ }
+ }
+ block("public static short parse(String str, short fallback)") {
+ block("try") {
+ nl("return Short.parseShort(str);")
+ }
+ block("catch (NumberFormatException e)") {
+ nl("return fallback;")
+ }
+ }
+ block("public static int parse(String str, int fallback)") {
+ block("try") {
+ nl("return Integer.parseInt(str);")
+ }
+ block("catch (NumberFormatException e)") {
+ nl("return fallback;")
+ }
+ }
+ block("public static long parse(String str, long fallback)") {
+ block("try") {
+ nl("return Long.parseLong(str);")
+ }
+ block("catch (NumberFormatException e)") {
+ nl("return fallback;")
+ }
+ }
+ block("public static float parse(String str, float fallback)") {
+ block("try") {
+ nl("return Float.parseFloat(str);")
+ }
+ block("catch (NumberFormatException e)") {
+ nl("return fallback;")
+ }
+ }
+ block("public static double parse(String str, double fallback)") {
+ block("try") {
+ nl("return Double.parseDouble(str);")
+ }
+ block("catch (NumberFormatException e)") {
+ nl("return fallback;")
+ }
+ }
+ block("public static char parse(String str, char fallback)") {
+ block ("if (str == null || str.isEmpty())") {
+ nl("return fallback;")
+ }
+ nl("return str.charAt(0);")
}
- tab("return root.getResources().getColor(resourceId);")
}
- tab("}")
- nl("}")
- }
+ }
} \ No newline at end of file
diff --git a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaAnalyzer.java b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaAnalyzer.java
index 695e04b8..a79897ca 100644
--- a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaAnalyzer.java
+++ b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaAnalyzer.java
@@ -66,6 +66,10 @@ public class JavaAnalyzer extends ModelAnalyzer {
}
}
+ public ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
+
@Override
protected ModelClass[] getObservableFieldTypes() {
return new ModelClass[0];
diff --git a/extensions/library/src/main/java/android/databinding/ViewDataBinding.java b/extensions/library/src/main/java/android/databinding/ViewDataBinding.java
index 5e760237..7829c0d1 100644
--- a/extensions/library/src/main/java/android/databinding/ViewDataBinding.java
+++ b/extensions/library/src/main/java/android/databinding/ViewDataBinding.java
@@ -570,6 +570,76 @@ public abstract class ViewDataBinding extends BaseObservable {
}
/** @hide */
+ protected static boolean parse(String str, boolean fallback) {
+ if (str == null) {
+ return fallback;
+ }
+ return Boolean.parseBoolean(str);
+ }
+
+ /** @hide */
+ protected static byte parse(String str, byte fallback) {
+ try {
+ return Byte.parseByte(str);
+ } catch (NumberFormatException e) {
+ return fallback;
+ }
+ }
+
+ /** @hide */
+ protected static short parse(String str, short fallback) {
+ try {
+ return Short.parseShort(str);
+ } catch (NumberFormatException e) {
+ return fallback;
+ }
+ }
+
+ /** @hide */
+ protected static int parse(String str, int fallback) {
+ try {
+ return Integer.parseInt(str);
+ } catch (NumberFormatException e) {
+ return fallback;
+ }
+ }
+
+ /** @hide */
+ protected static long parse(String str, long fallback) {
+ try {
+ return Long.parseLong(str);
+ } catch (NumberFormatException e) {
+ return fallback;
+ }
+ }
+
+ /** @hide */
+ protected static float parse(String str, float fallback) {
+ try {
+ return Float.parseFloat(str);
+ } catch (NumberFormatException e) {
+ return fallback;
+ }
+ }
+
+ /** @hide */
+ protected static double parse(String str, double fallback) {
+ try {
+ return Double.parseDouble(str);
+ } catch (NumberFormatException e) {
+ return fallback;
+ }
+ }
+
+ /** @hide */
+ protected static char parse(String str, char fallback) {
+ if (str == null || str.isEmpty()) {
+ return fallback;
+ }
+ return str.charAt(0);
+ }
+
+ /** @hide */
protected int getColorFromResource(int resourceId) {
if (VERSION.SDK_INT >= VERSION_CODES.M) {
return getRoot().getContext().getColor(resourceId);
diff --git a/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/FindMethodTest.java b/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/FindMethodTest.java
index a7799dc4..234ec1d2 100644
--- a/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/FindMethodTest.java
+++ b/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/FindMethodTest.java
@@ -123,4 +123,11 @@ public class FindMethodTest
assertTrue(mBinder.textView27.getTag() instanceof Integer);
assertEquals((Integer)1, mBinder.textView27.getTag());
}
+
+ @UiThreadTest
+ public void testFindMethodBasedOnSecondParam() throws Throwable {
+ mBinder.executePendingBindings();
+ assertEquals("2", mBinder.textView28.getText().toString());
+ assertEquals("10", mBinder.textView29.getText().toString());
+ }
}
diff --git a/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/TwoWayBindingAdapterTest.java b/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/TwoWayBindingAdapterTest.java
index 7506f2b5..0a1b14d2 100644
--- a/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/TwoWayBindingAdapterTest.java
+++ b/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/TwoWayBindingAdapterTest.java
@@ -30,6 +30,7 @@ import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TabHost.TabSpec;
+import java.util.Calendar;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -142,8 +143,8 @@ public class TwoWayBindingAdapterTest extends BaseDataBinderTest<TwoWayBinding>
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- assertNotSame(0, mBindingObject.date.get());
- assertEquals(mBindingObject.date.get(), mBinder.calendarView.getDate());
+ assertTrue(mBindingObject.date.get() != 0);
+ assertDatesMatch(mBindingObject.date.get(), mBinder.calendarView.getDate());
}
});
final long[] date = new long[2];
@@ -169,7 +170,18 @@ public class TwoWayBindingAdapterTest extends BaseDataBinderTest<TwoWayBinding>
Thread.sleep(1);
}
- assertEquals(date[0], mBindingObject.date.get());
+ assertDatesMatch(date[0], mBindingObject.date.get());
+ }
+
+ public void assertDatesMatch(long expectedTimeMillis, long testTimeMillis) {
+ Calendar expected = Calendar.getInstance();
+ expected.setTimeInMillis(expectedTimeMillis);
+ Calendar testValue = Calendar.getInstance();
+ testValue.setTimeInMillis(testTimeMillis);
+ assertEquals(expected.get(Calendar.YEAR), testValue.get(Calendar.YEAR));
+ assertEquals(expected.get(Calendar.MONTH), testValue.get(Calendar.MONTH));
+ assertEquals(expected.get(Calendar.DAY_OF_MONTH),
+ testValue.get(Calendar.DAY_OF_MONTH));
}
public void testCheckBoxChecked() throws Throwable {
@@ -683,6 +695,81 @@ public class TwoWayBindingAdapterTest extends BaseDataBinderTest<TwoWayBinding>
});
}
+ public void testStringConversions() throws Throwable {
+ makeVisible(mBinder.convertBool, mBinder.convertByte, mBinder.convertShort,
+ mBinder.convertInt, mBinder.convertLong, mBinder.convertFloat,
+ mBinder.convertDouble, mBinder.convertChar);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBinder.convertBool.setText("True");
+ mBinder.convertByte.setText("123");
+ mBinder.convertShort.setText("1234");
+ mBinder.convertInt.setText("12345");
+ mBinder.convertLong.setText("123456");
+ mBinder.convertFloat.setText("1.2345");
+ mBinder.convertDouble.setText("1.23456");
+ mBinder.convertChar.setText("a");
+ }
+ });
+
+ final long timeout = SystemClock.uptimeMillis() + 500;
+ while (!mBindingObject.booleanField.get() && SystemClock.uptimeMillis() < timeout) {
+ Thread.sleep(1);
+ }
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mBindingObject.booleanField.get());
+ assertEquals(123, mBindingObject.byteField.get());
+ assertEquals(1234, mBindingObject.shortField.get());
+ assertEquals(12345, mBindingObject.intField.get());
+ assertEquals(123456, mBindingObject.longField.get());
+ assertEquals(1.2345f, mBindingObject.floatField.get(), 0.0001f);
+ assertEquals(1.23456, mBindingObject.doubleField.get(), 0.000001);
+ assertEquals('a', mBindingObject.charField.get());
+ }
+
+ public void testBadStringConversions() throws Throwable {
+ makeVisible(mBinder.convertBool, mBinder.convertByte, mBinder.convertShort,
+ mBinder.convertInt, mBinder.convertLong, mBinder.convertFloat,
+ mBinder.convertDouble, mBinder.convertChar);
+ mBindingObject.booleanField.set(true);
+ mBindingObject.charField.set('1');
+ mBindingObject.byteField.set((byte) 1);
+ mBindingObject.shortField.set((short) 12);
+ mBindingObject.intField.set(123);
+ mBindingObject.longField.set(1234);
+ mBindingObject.floatField.set(1.2345f);
+ mBindingObject.doubleField.set(1.23456);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBinder.executePendingBindings();
+ mBinder.convertBool.setText("foobar");
+ mBinder.convertByte.setText("fred");
+ mBinder.convertShort.setText("wilma");
+ mBinder.convertInt.setText("barney");
+ mBinder.convertLong.setText("betty");
+ mBinder.convertFloat.setText("pebbles");
+ mBinder.convertDouble.setText("bam-bam");
+ mBinder.convertChar.setText("");
+ }
+ });
+
+ final long timeout = SystemClock.uptimeMillis() + 500;
+ while (mBindingObject.booleanField.get() && SystemClock.uptimeMillis() < timeout) {
+ Thread.sleep(1);
+ }
+ getInstrumentation().waitForIdleSync();
+ assertFalse(mBindingObject.booleanField.get());
+ assertEquals(1, mBindingObject.byteField.get());
+ assertEquals(12, mBindingObject.shortField.get());
+ assertEquals(123, mBindingObject.intField.get());
+ assertEquals(1234, mBindingObject.longField.get());
+ assertEquals(1.2345f, mBindingObject.floatField.get(), 0.0001f);
+ assertEquals(1.23456, mBindingObject.doubleField.get(), 0.00001);
+ assertEquals('1', mBindingObject.charField.get());
+ }
+
private void makeVisible(final View... views) throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
@@ -708,6 +795,14 @@ public class TwoWayBindingAdapterTest extends BaseDataBinderTest<TwoWayBinding>
mBinder.editText2.setVisibility(View.GONE);
mBinder.included.editText1.setVisibility(View.GONE);
mBinder.included.editText2.setVisibility(View.GONE);
+ mBinder.convertBool.setVisibility(View.GONE);
+ mBinder.convertByte.setVisibility(View.GONE);
+ mBinder.convertShort.setVisibility(View.GONE);
+ mBinder.convertInt.setVisibility(View.GONE);
+ mBinder.convertLong.setVisibility(View.GONE);
+ mBinder.convertFloat.setVisibility(View.GONE);
+ mBinder.convertDouble.setVisibility(View.GONE);
+ mBinder.convertChar.setVisibility(View.GONE);
for (View view : views) {
view.setVisibility(View.VISIBLE);
}
diff --git a/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObject.java b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObject.java
index 515a3413..452ddcf3 100644
--- a/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObject.java
+++ b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObject.java
@@ -16,7 +16,6 @@
package android.databinding.testapp.vo;
import android.databinding.BaseObservable;
-import android.databinding.Bindable;
import android.databinding.ObservableField;
import android.databinding.testapp.BR;
import android.util.ArrayMap;
@@ -67,6 +66,18 @@ public class FindMethodBindingObject extends FindMethodBindingObjectBase {
return vals;
}
+ public int argsClose(int i, String j) {
+ return i;
+ }
+
+ public float argsClose(int i, short j) {
+ return i;
+ }
+
+ public int argsClose(int i, int j) {
+ return j;
+ }
+
public static class Foo {
public final String bar = "hello world";
public static final String baz = "hello world";
diff --git a/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TwoWayBindingObject.java b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TwoWayBindingObject.java
index 873b1dce..f3adbdd3 100644
--- a/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TwoWayBindingObject.java
+++ b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TwoWayBindingObject.java
@@ -19,10 +19,14 @@ import android.content.Context;
import android.databinding.ObservableArrayList;
import android.databinding.ObservableArrayMap;
import android.databinding.ObservableBoolean;
+import android.databinding.ObservableByte;
+import android.databinding.ObservableChar;
+import android.databinding.ObservableDouble;
import android.databinding.ObservableField;
import android.databinding.ObservableFloat;
import android.databinding.ObservableInt;
import android.databinding.ObservableLong;
+import android.databinding.ObservableShort;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
@@ -50,6 +54,14 @@ public class TwoWayBindingObject {
public final ObservableArrayMap<String, Integer> map = new ObservableArrayMap<>();
public final ObservableField<int[]> array = new ObservableField<>();
public final ObservableField<CharSequence> editText = new ObservableField<>();
+ public final ObservableBoolean booleanField = new ObservableBoolean();
+ public final ObservableByte byteField = new ObservableByte();
+ public final ObservableShort shortField = new ObservableShort();
+ public final ObservableInt intField = new ObservableInt();
+ public final ObservableLong longField = new ObservableLong();
+ public final ObservableFloat floatField = new ObservableFloat();
+ public final ObservableDouble doubleField = new ObservableDouble();
+ public final ObservableChar charField = new ObservableChar();
public int text1Changes;
public int text2Changes;
public CountDownLatch textLatch;
diff --git a/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml b/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml
index 509517f8..cc5a2c63 100644
--- a/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml
+++ b/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml
@@ -139,5 +139,15 @@
android:id="@+id/textView27"
android:layout_width="wrap_content" android:layout_height="wrap_content"
app:tag="@{1}"/>
+ <TextView
+ android:id="@+id/textView28"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{`` + ((Integer)obj.argsClose(1, 2))}"
+ />
+ <TextView
+ android:id="@+id/textView29"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{`` + ((Integer)obj.argsClose(10, `Hello World`))}"
+ />
</LinearLayout>
</layout> \ No newline at end of file
diff --git a/integration-tests/TestApp/app/src/main/res/layout/two_way.xml b/integration-tests/TestApp/app/src/main/res/layout/two_way.xml
index 227278a5..ad12d58c 100644
--- a/integration-tests/TestApp/app/src/main/res/layout/two_way.xml
+++ b/integration-tests/TestApp/app/src/main/res/layout/two_way.xml
@@ -171,5 +171,53 @@
android:onTextChanged="@{obj::textChanged2}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
+ <EditText
+ android:id="@+id/convertBool"
+ android:text="@={`` + obj.booleanField}"
+ android:inputType="number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <EditText
+ android:id="@+id/convertByte"
+ android:text="@={`` + obj.byteField}"
+ android:inputType="number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <EditText
+ android:id="@+id/convertShort"
+ android:text="@={`` + obj.shortField}"
+ android:inputType="number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <EditText
+ android:id="@+id/convertInt"
+ android:text="@={`` + obj.intField}"
+ android:inputType="number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <EditText
+ android:id="@+id/convertLong"
+ android:text="@={`` + obj.longField}"
+ android:inputType="number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <EditText
+ android:id="@+id/convertFloat"
+ android:text="@={`` + obj.floatField}"
+ android:inputType="numberDecimal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <EditText
+ android:id="@+id/convertDouble"
+ android:text="@={`` + obj.doubleField}"
+ android:inputType="numberDecimal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <EditText
+ android:id="@+id/convertChar"
+ android:text="@={`` + obj.charField}"
+ android:inputType="number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
</LinearLayout>
</layout>