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 /compiler/src/main | |
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
Diffstat (limited to 'compiler/src/main')
8 files changed, 241 insertions, 64 deletions
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 |