diff options
author | George Mount <mount@google.com> | 2016-03-15 16:35:50 -0700 |
---|---|---|
committer | George Mount <mount@google.com> | 2016-03-17 15:49:29 -0700 |
commit | 11df39c91611b9ff2d7c87a9a9829251a015bccf (patch) | |
tree | 6239f0e399c08457fcabd397adf4024af57cb5a3 | |
parent | 3b54f63a65ad1ac66ed424efc595598ace99ce9f (diff) | |
download | data-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
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> |