diff options
author | George Mount <mount@google.com> | 2016-02-18 14:33:37 -0800 |
---|---|---|
committer | George Mount <mount@google.com> | 2016-03-08 15:38:00 -0800 |
commit | bb4a033fcd5cd20e5be46ef8ead442dc7db2454d (patch) | |
tree | a6eebe0a848983d8dad5662f674959e7e76b2c80 | |
parent | b7eeedbfadec03792551014e9dfa2bd384fc21a3 (diff) | |
download | data-binding-bb4a033fcd5cd20e5be46ef8ead442dc7db2454d.tar.gz |
Have two-way binding use localized variables to prevent NPE.
Bug 26962999
Two-way binding was using the inverted expressions directly
without localizing variables. That meant that if there was
a variable set to null during evaluation, it may get a
NullPointerException even though it checked for null
on the value previously. This CL localizes the variables
so that cannot happen.
Change-Id: Ia55955ce0f1cb750e6a678e72e0cda03f0e3c9b6
53 files changed, 1276 insertions, 382 deletions
diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java index 3525b3a8..68c34245 100644 --- a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java +++ b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java @@ -158,7 +158,7 @@ public class SimpleCompilationTest extends BaseCompilationTest { expectedErrorFile = "/app/src/main/res/layout/broken.xml"; } else if (errorFile.getCanonicalPath().equals(invalidSetter.getCanonicalPath())) { message = String.format(ErrorMessages.CANNOT_FIND_SETTER_CALL, "android:textx", - String.class.getCanonicalName()); + String.class.getCanonicalName(), "android.widget.TextView"); expectedErrorFile = "/app/src/main/res/layout/invalid_setter.xml"; } else { fail("unexpected exception " + exception.getBareMessage()); @@ -208,7 +208,7 @@ public class SimpleCompilationTest extends BaseCompilationTest { ScopedException ex = singleFileErrorTest("/layout/invalid_setter_binding.xml", "/app/src/main/res/layout/invalid_setter.xml", "myVariable", String.format(ErrorMessages.CANNOT_FIND_SETTER_CALL, "android:textx", - String.class.getCanonicalName())); + String.class.getCanonicalName(), "android.widget.TextView")); } @Test @@ -257,7 +257,7 @@ public class SimpleCompilationTest extends BaseCompilationTest { prepareProject(); ScopedException ex = singleFileErrorTest("/layout/invalid_variable_type.xml", "/app/src/main/res/layout/invalid_variable.xml", "myVariable", - String.format(ErrorMessages.CANNOT_RESOLVE_TYPE, "myVariable~")); + String.format(ErrorMessages.CANNOT_RESOLVE_TYPE, "myVariable")); } @Test diff --git a/compiler/src/main/java/android/databinding/tool/Binding.java b/compiler/src/main/java/android/databinding/tool/Binding.java index 4dc059f3..59e86141 100644 --- a/compiler/src/main/java/android/databinding/tool/Binding.java +++ b/compiler/src/main/java/android/databinding/tool/Binding.java @@ -75,7 +75,7 @@ public class Binding implements LocationScopeProvider { LambdaExpr lambdaExpr = (LambdaExpr) mExpr; final ModelClass listener = getListenerParameter(mTarget, mName, mExpr.getModel()); Preconditions.checkNotNull(listener, ErrorMessages.CANNOT_FIND_SETTER_CALL, mName, - "lambda"); + "lambda", getTarget().getInterfaceType()); //noinspection ConstantConditions List<ModelMethod> abstractMethods = listener.getAbstractMethods(); int numberOfAbstractMethods = abstractMethods.size(); @@ -106,7 +106,8 @@ public class Binding implements LocationScopeProvider { Scope.enter(this); resolveSetterCall(); if (mSetterCall == null) { - L.e(ErrorMessages.CANNOT_FIND_SETTER_CALL, mName, mExpr.getResolvedType()); + L.e(ErrorMessages.CANNOT_FIND_SETTER_CALL, mName, mExpr.getResolvedType(), + getTarget().getInterfaceType()); } } finally { Scope.exit(); diff --git a/compiler/src/main/java/android/databinding/tool/BindingTarget.java b/compiler/src/main/java/android/databinding/tool/BindingTarget.java index 74ef86cd..8c59ca75 100644 --- a/compiler/src/main/java/android/databinding/tool/BindingTarget.java +++ b/compiler/src/main/java/android/databinding/tool/BindingTarget.java @@ -58,28 +58,24 @@ public class BindingTarget implements LocationScopeProvider { L.e(ErrorMessages.TWO_WAY_EVENT_ATTRIBUTE, name); } mBindings.add(new Binding(this, name, expr)); - if (expr.isTwoWay()) { - try { - Scope.enter(expr); - expr.assertIsInvertible(); - final InverseBinding inverseBinding = new InverseBinding(this, name, expr); - mInverseBindings.add(inverseBinding); - mBindings.add(new Binding(this, inverseBinding.getEventAttribute(), - mModel.twoWayListenerExpr(inverseBinding), - inverseBinding.getEventSetter())); - } finally { - Scope.exit(); - } - } } public String getInterfaceType() { return mBundle.getInterfaceType() == null ? mBundle.getFullClassName() : mBundle.getInterfaceType(); } + public InverseBinding addInverseBinding(String name, Expr expr, String bindingClass) { + expr.assertIsInvertible(); + final InverseBinding inverseBinding = new InverseBinding(this, name, expr, bindingClass); + mInverseBindings.add(inverseBinding); + mBindings.add(new Binding(this, inverseBinding.getEventAttribute(), + mModel.twoWayListenerExpr(inverseBinding), + inverseBinding.getEventSetter())); + return inverseBinding; + } + public InverseBinding addInverseBinding(String name, BindingGetterCall call) { - final InverseBinding inverseBinding = new InverseBinding(this, name, null); - inverseBinding.setGetterCall(call); + final InverseBinding inverseBinding = new InverseBinding(this, name, call); mInverseBindings.add(inverseBinding); mBindings.add(new Binding(this, inverseBinding.getEventAttribute(), mModel.twoWayListenerExpr(inverseBinding))); @@ -111,7 +107,7 @@ public class BindingTarget implements LocationScopeProvider { if (mResolvedClass == null) { if (mBundle.isBinder()) { mResolvedClass = ModelAnalyzer.getInstance(). - findClass(ModelAnalyzer.VIEW_DATA_BINDING, mModel.getImports()); + findClass(mBundle.getInterfaceType(), mModel.getImports()); } else { mResolvedClass = ModelAnalyzer.getInstance().findClass(mBundle.getFullClassName(), mModel.getImports()); diff --git a/compiler/src/main/java/android/databinding/tool/CompilerChef.java b/compiler/src/main/java/android/databinding/tool/CompilerChef.java index b7456da0..278492ad 100644 --- a/compiler/src/main/java/android/databinding/tool/CompilerChef.java +++ b/compiler/src/main/java/android/databinding/tool/CompilerChef.java @@ -23,6 +23,7 @@ import android.databinding.tool.writer.DataBinderWriter; import android.databinding.tool.writer.DynamicUtilWriter; import android.databinding.tool.writer.JavaFileWriter; +import java.util.HashMap; import java.util.Set; /** @@ -69,6 +70,7 @@ public class CompilerChef { chef.mResourceBundle = bundle; chef.mFileWriter = fileWriter; chef.mResourceBundle.validateMultiResLayouts(); + chef.pushClassesToAnalyzer(); return chef; } @@ -89,6 +91,42 @@ public class CompilerChef { return mResourceBundle != null && mResourceBundle.getLayoutBundles().size() > 0; } + /** + * Injects ViewDataBinding subclasses to the ModelAnalyzer so that they can be + * analyzed prior to creation. This is useful for resolving variable setters and + * View fields during compilation. + */ + private void pushClassesToAnalyzer() { + ModelAnalyzer analyzer = ModelAnalyzer.getInstance(); + for (String layoutName : mResourceBundle.getLayoutBundles().keySet()) { + ResourceBundle.LayoutFileBundle layoutFileBundle = + mResourceBundle.getLayoutBundles().get(layoutName).get(0); + final HashMap<String, String> imports = new HashMap<String, String>(); + for (ResourceBundle.NameTypeLocation imp : layoutFileBundle.getImports()) { + imports.put(imp.name, imp.type); + } + final HashMap<String, String> variables = new HashMap<String, String>(); + for (ResourceBundle.VariableDeclaration variable : layoutFileBundle.getVariables()) { + final String variableName = variable.name; + String type = variable.type; + if (imports.containsKey(type)) { + type = imports.get(type); + } + variables.put(variableName, type); + } + final HashMap<String, String> fields = new HashMap<String, String>(); + for (ResourceBundle.BindingTargetBundle bindingTargetBundle : + layoutFileBundle.getBindingTargetBundles()) { + if (bindingTargetBundle.getId() != null) { + fields.put(bindingTargetBundle.getId(), bindingTargetBundle.getInterfaceType()); + } + } + final String className = layoutFileBundle.getBindingClassPackage() + "." + + layoutFileBundle.getBindingClassName(); + analyzer.injectViewDataBinding(className, variables, fields); + } + } + public void writeDataBinderMapper(int minSdk, BRWriter brWriter) { ensureDataBinder(); final String pkg = "android.databinding"; diff --git a/compiler/src/main/java/android/databinding/tool/InverseBinding.java b/compiler/src/main/java/android/databinding/tool/InverseBinding.java index e04be283..13dd8875 100644 --- a/compiler/src/main/java/android/databinding/tool/InverseBinding.java +++ b/compiler/src/main/java/android/databinding/tool/InverseBinding.java @@ -16,25 +16,23 @@ package android.databinding.tool; +import android.databinding.tool.expr.CallbackArgExpr; +import android.databinding.tool.expr.CallbackExprModel; import android.databinding.tool.expr.Expr; import android.databinding.tool.expr.ExprModel; import android.databinding.tool.expr.FieldAccessExpr; +import android.databinding.tool.expr.IdentifierExpr; import android.databinding.tool.processing.ErrorMessages; import android.databinding.tool.processing.Scope; import android.databinding.tool.processing.scopes.LocationScopeProvider; import android.databinding.tool.reflection.ModelAnalyzer; import android.databinding.tool.reflection.ModelClass; +import android.databinding.tool.solver.ExecutionPath; import android.databinding.tool.store.Location; import android.databinding.tool.store.SetterStore; import android.databinding.tool.store.SetterStore.BindingGetterCall; import android.databinding.tool.store.SetterStore.BindingSetterCall; import android.databinding.tool.util.L; -import android.databinding.tool.util.Preconditions; -import android.databinding.tool.writer.FlagSet; -import android.databinding.tool.writer.KCode; -import android.databinding.tool.writer.LayoutBinderWriterKt; - -import kotlin.jvm.functions.Function2; import java.util.ArrayList; import java.util.List; @@ -46,11 +44,38 @@ public class InverseBinding implements LocationScopeProvider { private final BindingTarget mTarget; private BindingGetterCall mGetterCall; private final ArrayList<FieldAccessExpr> mChainedExpressions = new ArrayList<FieldAccessExpr>(); + private final CallbackExprModel mCallbackExprModel; + private final Expr mInverseExpr; + private final CallbackArgExpr mVariableExpr; + private final ExecutionPath mExecutionPath; + + public InverseBinding(BindingTarget target, String name, Expr expr, String bindingClassName) { + mTarget = target; + mName = name; + mCallbackExprModel = new CallbackExprModel(expr.getModel()); + mExpr = expr.cloneToModel(mCallbackExprModel); + setGetterCall(mExpr); + mVariableExpr = mCallbackExprModel.callbackArg("callbackArg_0"); + ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); + ModelClass type = modelAnalyzer.findClass(getGetterCall().getGetterType(), null); + mVariableExpr.setClassFromCallback(type); + mVariableExpr.setUserDefinedType(getGetterCall().getGetterType()); + mInverseExpr = + mExpr.generateInverse(mCallbackExprModel, mVariableExpr, bindingClassName); + mExecutionPath = ExecutionPath.createRoot(); + mInverseExpr.toExecutionPath(mExecutionPath); + mCallbackExprModel.seal(); + } - public InverseBinding(BindingTarget target, String name, Expr expr) { + public InverseBinding(BindingTarget target, String name, BindingGetterCall getterCall) { mTarget = target; mName = name; - mExpr = expr; + mExpr = null; + mCallbackExprModel = null; + mInverseExpr = null; + mVariableExpr = null; + mExecutionPath = null; + setGetterCall(getterCall); } @Override @@ -62,7 +87,7 @@ public class InverseBinding implements LocationScopeProvider { } } - void setGetterCall(BindingGetterCall getterCall) { + private void setGetterCall(BindingGetterCall getterCall) { mGetterCall = getterCall; } @@ -74,74 +99,56 @@ public class InverseBinding implements LocationScopeProvider { return mTarget.getResolvedType().isViewDataBinding(); } - private SetterStore.BindingGetterCall getGetterCall() { - if (mGetterCall == null) { - if (mExpr != null) { - mExpr.getResolvedType(); // force resolve of ObservableFields - } - try { - Scope.enter(mTarget); - Scope.enter(this); - resolveGetterCall(); - if (mGetterCall == null) { - L.e(ErrorMessages.CANNOT_FIND_GETTER_CALL, mName, - mExpr == null ? "Unknown" : mExpr.getResolvedType(), - mTarget.getResolvedType()); - } - } finally { - Scope.exit(); - Scope.exit(); + private void setGetterCall(Expr expr) { + try { + Scope.enter(mTarget); + Scope.enter(this); + ModelClass viewType = mTarget.getResolvedType(); + final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance()); + final ModelClass resolvedType = expr == null ? null : expr.getResolvedType(); + mGetterCall = setterStore.getGetterCall(mName, viewType, resolvedType, + expr.getModel().getImports()); + if (mGetterCall == null) { + L.e(ErrorMessages.CANNOT_FIND_GETTER_CALL, mName, + expr == null ? "Unknown" : mExpr.getResolvedType(), + mTarget.getResolvedType()); } + } finally { + Scope.exit(); + Scope.exit(); } - return mGetterCall; } - private void resolveGetterCall() { - ModelClass viewType = mTarget.getResolvedType(); - final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance()); - final ModelClass resolvedType = mExpr == null ? null : mExpr.getResolvedType(); - mGetterCall = setterStore.getGetterCall(mName, viewType, resolvedType, - getModel().getImports()); + public SetterStore.BindingGetterCall getGetterCall() { + return mGetterCall; } public BindingTarget getTarget() { return mTarget; } - public KCode toJavaCode(String bindingComponent, final FlagSet flagField) { - final String targetViewName = LayoutBinderWriterKt.getFieldName(getTarget()); - KCode code = new KCode(); - // A chained expression will have substituted its chained value for the expression - // unless the attribute has no expression. Therefore, chaining and expressions are - // mutually exclusive. - Preconditions.check((mExpr == null) != mChainedExpressions.isEmpty(), - "Chained expressions are only against unbound attributes."); - if (mExpr != null) { - code.app("", mExpr.toInverseCode(new KCode(getGetterCall().toJava(bindingComponent, - targetViewName)))); - } else { // !mChainedExpressions.isEmpty()) - final String fieldName = flagField.getLocalName(); - FlagSet flagSet = new FlagSet(); - for (FieldAccessExpr expr : mChainedExpressions) { - flagSet = flagSet.or(new FlagSet(expr.getId())); - } - final FlagSet allFlags = flagSet; - code.nl(new KCode("synchronized(this) {")); - code.tab(LayoutBinderWriterKt - .mapOr(flagField, flagSet, new Function2<String, Integer, KCode>() { - @Override - public KCode invoke(String suffix, Integer index) { - return new KCode(fieldName) - .app(suffix) - .app(" |= ") - .app(LayoutBinderWriterKt.binaryCode(allFlags, index)) - .app(";"); - } - })); - code.nl(new KCode("}")); - code.nl(new KCode("requestRebind()")); - } - return code; + public Expr getExpr() { + return mExpr; + } + + public Expr getInverseExpr() { + return mInverseExpr; + } + + public IdentifierExpr getVariableExpr() { + return mVariableExpr; + } + + public ExecutionPath getExecutionPath() { + return mExecutionPath; + } + + public CallbackExprModel getCallbackExprModel() { + return mCallbackExprModel; + } + + public List<FieldAccessExpr> getChainedExpressions() { + return mChainedExpressions; } public String getBindingAdapterInstanceClass() { diff --git a/compiler/src/main/java/android/databinding/tool/LayoutBinder.java b/compiler/src/main/java/android/databinding/tool/LayoutBinder.java index 7348bd75..7969854a 100644 --- a/compiler/src/main/java/android/databinding/tool/LayoutBinder.java +++ b/compiler/src/main/java/android/databinding/tool/LayoutBinder.java @@ -216,13 +216,18 @@ public class LayoutBinder implements FileScopeProvider { for (BindingTarget bindingTarget : mBindingTargets) { try { Scope.enter(bindingTarget.mBundle); + final String className = getPackage() + "." + getClassName(); for (BindingTargetBundle.BindingBundle bindingBundle : bindingTarget.mBundle .getBindingBundleList()) { try { Scope.enter(bindingBundle.getValueLocation()); - bindingTarget.addBinding(bindingBundle.getName(), - parse(bindingBundle.getExpr(), bindingBundle.isTwoWay(), - bindingBundle.getValueLocation())); + Expr expr = parse(bindingBundle.getExpr(), + bindingBundle.getValueLocation()); + bindingTarget.addBinding(bindingBundle.getName(), expr); + if (bindingBundle.isTwoWay()) { + bindingTarget.addInverseBinding(bindingBundle.getName(), expr, + className); + } } finally { Scope.exit(); } @@ -290,10 +295,9 @@ public class LayoutBinder implements FileScopeProvider { return target; } - public Expr parse(String input, boolean isTwoWay, @Nullable Location locationInFile) { + public Expr parse(String input, @Nullable Location locationInFile) { final Expr parsed = mExpressionParser.parse(input, locationInFile); parsed.setBindingExpression(true); - parsed.setTwoWay(isTwoWay); return parsed; } diff --git a/compiler/src/main/java/android/databinding/tool/expr/ArgListExpr.java b/compiler/src/main/java/android/databinding/tool/expr/ArgListExpr.java index c8f6e2cf..cdb0d14a 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/ArgListExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/ArgListExpr.java @@ -42,12 +42,17 @@ public class ArgListExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { throw new IllegalStateException("should never try to convert an argument expressions" + " into code"); } @Override + public Expr cloneToModel(ExprModel model) { + return model.argListExpr(cloneToModel(model, getChildren())); + } + + @Override protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { return modelAnalyzer.findClass(Void.class); } diff --git a/compiler/src/main/java/android/databinding/tool/expr/BitShiftExpr.java b/compiler/src/main/java/android/databinding/tool/expr/BitShiftExpr.java index cbc895bb..f4da3ec0 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/BitShiftExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/BitShiftExpr.java @@ -57,15 +57,25 @@ public class BitShiftExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { return new KCode() - .app("", getLeft().toCode(expand)) + .app("", getLeft().toCode()) .app(getOp()) - .app("", getRight().toCode(expand)); + .app("", getRight().toCode()); + } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.bitshift(getLeft().cloneToModel(model), mOp, getRight().cloneToModel(model)); } @Override public String getInvertibleError() { return "Bit shift operators cannot be inverted in two-way binding"; } + + @Override + public String toString() { + return getLeft().toString() + ' ' + mOp + ' ' + getRight().toString(); + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java b/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java index 0829ad04..a9a61556 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java @@ -21,6 +21,8 @@ import android.databinding.tool.reflection.ModelClass; import android.databinding.tool.solver.ExecutionPath; import android.databinding.tool.writer.KCode; +import com.google.common.collect.Lists; + import java.util.ArrayList; import java.util.List; @@ -90,7 +92,7 @@ public class BracketExpr extends Expr { protected String computeUniqueKey() { final String targetKey = getTarget().computeUniqueKey(); - return addTwoWay(join(targetKey, "$", getArg().computeUniqueKey(), "$")); + return join(targetKey, "$", getArg().computeUniqueKey(), "$"); } @Override @@ -115,7 +117,7 @@ public class BracketExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { String cast = argCastsInteger() ? "(Integer) " : ""; switch (getAccessor()) { case ARRAY: { @@ -152,12 +154,33 @@ public class BracketExpr extends Expr { } @Override - public KCode toInverseCode(KCode value) { - String cast = argCastsInteger() ? "(Integer) " : ""; - return new KCode(). - app("setTo(", getTarget().toCode(true)). - app(", "). - app(cast, getArg().toCode(true)). - app(", ", value).app(");"); + public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { + Expr arg = getArg().cloneToModel(model); + arg = argCastsInteger() + ? model.castExpr("int", model.castExpr("Integer", arg)) + : arg; + StaticIdentifierExpr viewDataBinding = + model.staticIdentifier(ModelAnalyzer.VIEW_DATA_BINDING); + viewDataBinding.setUserDefinedType(ModelAnalyzer.VIEW_DATA_BINDING); + ModelClass targetType = getTarget().getResolvedType(); + if ((targetType.isList() || targetType.isMap()) && + value.getResolvedType().isPrimitive()) { + ModelClass boxed = value.getResolvedType().box(); + value = model.castExpr(boxed.toJavaCode(), value); + } + List<Expr> args = Lists.newArrayList(getTarget().cloneToModel(model), arg, value); + MethodCallExpr setter = model.methodCall(viewDataBinding, "setTo", args); + setter.setAllowProtected(); + return setter; + } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.bracketExpr(getTarget().cloneToModel(model), getArg().cloneToModel(model)); + } + + @Override + public String toString() { + return getTarget().toString() + '[' + getArg() + ']'; } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/BuiltInVariableExpr.java b/compiler/src/main/java/android/databinding/tool/expr/BuiltInVariableExpr.java index d2fdea13..ff1d1adf 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/BuiltInVariableExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/BuiltInVariableExpr.java @@ -49,7 +49,7 @@ public class BuiltInVariableExpr extends IdentifierExpr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { if (mAccessCode == null) { return new KCode().app(mName); } else { @@ -65,4 +65,9 @@ public class BuiltInVariableExpr extends IdentifierExpr { public String getInvertibleError() { return "Built-in variables may not be the target of two-way binding"; } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.builtInVariable(mName, mUserDefinedType, mAccessCode); + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/CallbackArgExpr.java b/compiler/src/main/java/android/databinding/tool/expr/CallbackArgExpr.java index 2f77f831..34841c93 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/CallbackArgExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/CallbackArgExpr.java @@ -68,7 +68,7 @@ public class CallbackArgExpr extends IdentifierExpr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { return new KCode(CallbackWrapper.ARG_PREFIX + mArgIndex); } @@ -85,4 +85,9 @@ public class CallbackArgExpr extends IdentifierExpr { public String getName() { return mName; } + + @Override + public Expr cloneToModel(ExprModel model) { + return new CallbackArgExpr(mArgIndex, mName); + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/CallbackExprModel.java b/compiler/src/main/java/android/databinding/tool/expr/CallbackExprModel.java index 1e0fc8ac..501125c0 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/CallbackExprModel.java +++ b/compiler/src/main/java/android/databinding/tool/expr/CallbackExprModel.java @@ -18,7 +18,6 @@ package android.databinding.tool.expr; import android.databinding.tool.processing.ErrorMessages; import android.databinding.tool.processing.Scope; -import android.databinding.tool.processing.ScopedException; import android.databinding.tool.store.Location; import android.databinding.tool.util.L; import android.databinding.tool.util.Preconditions; diff --git a/compiler/src/main/java/android/databinding/tool/expr/CastExpr.java b/compiler/src/main/java/android/databinding/tool/expr/CastExpr.java index 9a4a36ae..9a66cd98 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/CastExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/CastExpr.java @@ -46,7 +46,7 @@ public class CastExpr extends Expr { } protected String computeUniqueKey() { - return addTwoWay(join(mType, getCastExpr().computeUniqueKey())); + return join(mType, getCastExpr().computeUniqueKey()); } public Expr getCastExpr() { @@ -58,11 +58,12 @@ public class CastExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { return new KCode() .app("(") .app(getCastType()) - .app(") ", getCastExpr().toCode(expand)); + .app(") (", getCastExpr().toCode()) + .app(")"); } @Override @@ -71,8 +72,20 @@ public class CastExpr extends Expr { } @Override - public KCode toInverseCode(KCode value) { - // assume no need to cast in reverse - return getCastExpr().toInverseCode(value); + public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { + Expr castExpr = getCastExpr(); + ModelClass exprType = castExpr.getResolvedType(); + Expr castValue = model.castExpr(exprType.toJavaCode(), value); + return castExpr.generateInverse(model, castValue, bindingClassName); + } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.castExpr(mType, getCastExpr().cloneToModel(model)); + } + + @Override + public String toString() { + return "(" + mType + ") " + getCastExpr(); } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/ComparisonExpr.java b/compiler/src/main/java/android/databinding/tool/expr/ComparisonExpr.java index 172ea219..6b36bca9 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/ComparisonExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/ComparisonExpr.java @@ -62,14 +62,27 @@ public class ComparisonExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { - return new KCode().app("", getLeft().toCode(expand)) - .app(" ").app(getOp()).app(" ") - .app("", getRight().toCode(expand)); + protected KCode generateCode() { + return new KCode() + .app("(", getLeft().toCode()) + .app(") ") + .app(getOp()) + .app(" (", getRight().toCode()) + .app(")"); + } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.comparison(mOp, getLeft().cloneToModel(model), getRight().cloneToModel(model)); } @Override public String getInvertibleError() { return "Comparison operators are not valid as targets of two-way binding"; } + + @Override + public String toString() { + return getLeft().toString() + ' ' + mOp + ' ' + getRight(); + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/Expr.java b/compiler/src/main/java/android/databinding/tool/expr/Expr.java index 59cc916d..768a59a5 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/Expr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/Expr.java @@ -100,7 +100,6 @@ abstract public class Expr implements VersionProvider, LocationScopeProvider { private boolean mRead; private boolean mIsUsed = false; private boolean mIsUsedInCallback = false; - private boolean mIsTwoWay = false; Expr(Iterable<Expr> children) { for (Expr expr : children) { @@ -212,22 +211,6 @@ abstract public class Expr implements VersionProvider, LocationScopeProvider { mModel = model; } - public void setTwoWay(boolean isTwoWay) { - mIsTwoWay = isTwoWay; - } - - public boolean isTwoWay() { - return mIsTwoWay; - } - - protected String addTwoWay(String uniqueKey) { - if (mIsTwoWay) { - return "twoWay(" + uniqueKey + ")"; - } else { - return "oneWay(" + uniqueKey + ")"; - } - } - private BitSet resolveShouldReadWithConditionals() { // ensure we have invalid flags BitSet bitSet = new BitSet(); @@ -751,26 +734,32 @@ abstract public class Expr implements VersionProvider, LocationScopeProvider { } public KCode toCode() { - return toCode(false); - } - - public KCode toCode(boolean expand) { - if (!expand && isDynamic()) { + if (isDynamic()) { return new KCode(LayoutBinderWriterKt.scopedName(this)); } - return generateCode(expand); + return generateCode(); } public KCode toFullCode() { - return generateCode(false); + return generateCode(); } - protected abstract KCode generateCode(boolean expand); + protected abstract KCode generateCode(); - public KCode toInverseCode(KCode value) { + public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { throw new IllegalStateException("expression does not support two-way binding"); } + public abstract Expr cloneToModel(ExprModel model); + + protected static List<Expr> cloneToModel(ExprModel model, List<Expr> exprs) { + ArrayList<Expr> clones = new ArrayList<Expr>(); + for (Expr expr : exprs) { + clones.add(expr.cloneToModel(model)); + } + return clones; + } + public void assertIsInvertible() { final String errorMessage = getInvertibleError(); if (errorMessage != null) { diff --git a/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java b/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java index f9f2c654..ba32042e 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java +++ b/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java @@ -170,7 +170,7 @@ public class ExprModel { } public FieldAccessExpr observableField(Expr parent, String name) { - return register(new FieldAccessExpr(parent, name, true)); + return register(new ObservableFieldExpr(parent, name)); } public SymbolExpr symbol(String text, Class type) { @@ -402,7 +402,7 @@ public class ExprModel { for (Expr expr : mExprMap.values()) { if (expr instanceof FieldAccessExpr) { FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) expr; - if (fieldAccessExpr.getChild() instanceof ViewFieldExpr) { + if (fieldAccessExpr.getTarget() instanceof ViewFieldExpr) { flagMapping.add(fieldAccessExpr.getUniqueKey()); fieldAccessExpr.setId(counter++); } @@ -682,6 +682,10 @@ public class ExprModel { return register(new ListenerExpr(expression, name, listenerType, listenerMethod)); } + public FieldAssignmentExpr assignment(Expr target, String name, Expr value) { + return register(new FieldAssignmentExpr(target, name, value)); + } + public Map<String, CallbackWrapper> getCallbackWrappers() { return mCallbackWrappers; } @@ -696,7 +700,7 @@ public class ExprModel { return wrapper; } - public Expr lambdaExpr(Expr expr, CallbackExprModel callbackExprModel) { + public LambdaExpr lambdaExpr(Expr expr, CallbackExprModel callbackExprModel) { return register(new LambdaExpr(expr, callbackExprModel)); } diff --git a/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java b/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java index 5d850b5d..bdfc9f84 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java @@ -35,6 +35,8 @@ import android.databinding.tool.util.L; import android.databinding.tool.util.Preconditions; import android.databinding.tool.writer.KCode; +import com.google.common.collect.Lists; + import java.util.ArrayList; import java.util.List; @@ -43,23 +45,15 @@ public class FieldAccessExpr extends Expr { // notification name for the field. Important when we map this to a method w/ different name String mBrName; Callable mGetter; - final boolean mIsObservableField; boolean mIsListener; boolean mIsViewAttributeAccess; FieldAccessExpr(Expr parent, String name) { super(parent); mName = name; - mIsObservableField = false; - } - - FieldAccessExpr(Expr parent, String name, boolean isObservableField) { - super(parent); - mName = name; - mIsObservableField = isObservableField; } - public Expr getChild() { + public Expr getTarget() { return getChildren().get(0); } @@ -72,15 +66,15 @@ public class FieldAccessExpr extends Expr { @Override public List<ExecutionPath> toExecutionPath(List<ExecutionPath> paths) { - final List<ExecutionPath> targetPaths = getChild().toExecutionPath(paths); + final List<ExecutionPath> targetPaths = getTarget().toExecutionPath(paths); // after this, we need a null check. List<ExecutionPath> result = new ArrayList<ExecutionPath>(); - if (getChild() instanceof StaticIdentifierExpr) { - result.addAll(toExecutionPathInOrder(paths, getChild())); + if (getTarget() instanceof StaticIdentifierExpr) { + result.addAll(toExecutionPathInOrder(paths, getTarget())); } else { for (ExecutionPath path : targetPaths) { final ComparisonExpr cmp = getModel() - .comparison("!=", getChild(), getModel().symbol("null", Object.class)); + .comparison("!=", getTarget(), getModel().symbol("null", Object.class)); path.addPath(cmp); final ExecutionPath subPath = path.addBranch(cmp, true); if (subPath != null) { @@ -118,7 +112,7 @@ public class FieldAccessExpr extends Expr { return true; } // if it is static final, gone - if (getChild().isDynamic()) { + if (getTarget().isDynamic()) { // if owner is dynamic, then we can be dynamic unless we are static final return !mGetter.isStatic() || mGetter.isDynamic(); } @@ -137,17 +131,14 @@ public class FieldAccessExpr extends Expr { @Override public Expr resolveListeners(ModelClass listener, Expr parent) { - if (mName == null || mName.isEmpty()) { - return this; // ObservableFields aren't listeners - } - final ModelClass childType = getChild().getResolvedType(); + final ModelClass childType = getTarget().getResolvedType(); if (getGetter() == null) { if (listener == null || !mIsListener) { L.e("Could not resolve %s.%s as an accessor or listener on the attribute.", childType.getCanonicalName(), mName); return this; } - getChild().getParents().remove(this); + getTarget().getParents().remove(this); } else if (listener == null) { return this; // Not a listener, but we have a getter. } @@ -166,14 +157,14 @@ public class FieldAccessExpr extends Expr { // Look for a signature matching the abstract method final ModelMethod listenerMethod = abstractMethods.get(0); final ModelClass[] listenerParameters = listenerMethod.getParameterTypes(); - boolean isStatic = getChild() instanceof StaticIdentifierExpr; + boolean isStatic = getTarget() instanceof StaticIdentifierExpr; List<ModelMethod> methods = childType.findMethods(mName, isStatic); for (ModelMethod method : methods) { if (acceptsParameters(method, listenerParameters) && method.getReturnType(null).equals(listenerMethod.getReturnType(null))) { resetResolvedType(); // replace this with ListenerExpr in parent - Expr listenerExpr = getModel().listenerExpr(getChild(), mName, listener, + Expr listenerExpr = getModel().listenerExpr(getTarget(), mName, listener, listenerMethod); if (parent != null) { int index; @@ -217,7 +208,7 @@ public class FieldAccessExpr extends Expr { protected List<Dependency> constructDependencies() { final List<Dependency> dependencies = constructDynamicChildrenDependencies(); for (Dependency dependency : dependencies) { - if (dependency.getOther() == getChild()) { + if (dependency.getOther() == getTarget()) { dependency.setMandatory(true); } } @@ -226,10 +217,7 @@ public class FieldAccessExpr extends Expr { @Override protected String computeUniqueKey() { - if (mIsObservableField) { - return addTwoWay(join(mName, "..", super.computeUniqueKey())); - } - return addTwoWay(join(mName, ".", super.computeUniqueKey())); + return join(mName, ".", super.computeUniqueKey()); } public String getName() { @@ -266,10 +254,10 @@ public class FieldAccessExpr extends Expr { return modelAnalyzer.findClass(Object.class); } if (mGetter == null) { - Expr child = getChild(); - child.getResolvedType(); - boolean isStatic = child instanceof StaticIdentifierExpr; - ModelClass resolvedType = child.getResolvedType(); + Expr target = getTarget(); + target.getResolvedType(); + boolean isStatic = target instanceof StaticIdentifierExpr; + ModelClass resolvedType = target.getResolvedType(); L.d("resolving %s. Resolved class type: %s", this, resolvedType); mGetter = resolvedType.findGetterOrField(mName, isStatic); @@ -284,25 +272,16 @@ public class FieldAccessExpr extends Expr { if (mGetter.isStatic() && !isStatic) { // found a static method on an instance. register a new one - child.getParents().remove(this); - getChildren().remove(child); - StaticIdentifierExpr staticId = getModel().staticIdentifierFor(resolvedType); - getChildren().add(staticId); - staticId.getParents().add(this); - child = getChild(); // replace the child for the next if stmt + replaceStaticIdentifier(resolvedType); + target = getTarget(); } if (mGetter.resolvedType.isObservableField()) { // Make this the ".get()" and add an extra field access for the observable field - child.getParents().remove(this); - getChildren().remove(child); - - FieldAccessExpr observableField = getModel().observableField(child, mName); - observableField.mGetter = mGetter; - if (hasBindableAnnotations()) { - observableField.mBrName = ExtKt.br(BrNameUtil.brKey(mGetter)); - } + target.getParents().remove(this); + getChildren().remove(target); + FieldAccessExpr observableField = getModel().observableField(target, mName); getChildren().add(observableField); observableField.getParents().add(this); mGetter = mGetter.resolvedType.findGetterOrField("", false); @@ -315,9 +294,17 @@ public class FieldAccessExpr extends Expr { return mGetter.resolvedType; } + protected void replaceStaticIdentifier(ModelClass staticIdentifierType) { + getTarget().getParents().remove(this); + getChildren().remove(getTarget()); + StaticIdentifierExpr staticId = getModel().staticIdentifierFor(staticIdentifierType); + getChildren().add(staticId); + staticId.getParents().add(this); + } + @Override public Expr resolveTwoWayExpressions(Expr parent) { - final Expr child = getChild(); + final Expr child = getTarget(); if (!(child instanceof ViewFieldExpr)) { return this; } @@ -390,25 +377,17 @@ public class FieldAccessExpr extends Expr { @Override protected String asPackage() { - String parentPackage = getChild().asPackage(); + String parentPackage = getTarget().asPackage(); return parentPackage == null ? null : parentPackage + "." + mName; } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { // once we can deprecate using Field.access for callbacks, we can get rid of this since // it will be detected when resolve type is run. Preconditions.checkNotNull(getGetter(), ErrorMessages.CANNOT_RESOLVE_TYPE, this); - KCode code = new KCode(); - if (expand) { - String defaultValue = ModelAnalyzer.getInstance().getDefaultValue( - getResolvedType().toJavaCode()); - code.app("(", getChild().toCode(true)) - .app(" == null) ? ") - .app(defaultValue) - .app(" : "); - } - code.app("", getChild().toCode(expand)).app("."); + KCode code = new KCode() + .app("", getTarget().toCode()).app("."); if (getGetter().type == Callable.Type.FIELD) { return code.app(getGetter().name); } else { @@ -417,25 +396,27 @@ public class FieldAccessExpr extends Expr { } @Override - public KCode toInverseCode(KCode value) { - if (mGetter.setterName == null) { - throw new IllegalStateException("There is no inverse for " + toCode().generate()); - } - KCode castValue = new KCode("(").app(getResolvedType().toJavaCode() + ")(", value).app(")"); - String type = getChild().getResolvedType().toJavaCode(); - KCode code = new KCode("targetObj_."); + public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { + Expr castExpr = model.castExpr(getResolvedType().toJavaCode(), value); + Expr target = getTarget().cloneToModel(model); + Expr result; if (getGetter().type == Callable.Type.FIELD) { - code.app(getGetter().setterName).app(" = ", castValue).app(";"); + result = model.assignment(target, mName, castExpr); } else { - code.app(getGetter().setterName).app("(", castValue).app(")").app(";"); + result = model.methodCall(target, mGetter.setterName, Lists.newArrayList(castExpr)); } - return new KCode() - .app("final ") - .app(type) - .app(" targetObj_ = ", getChild().toCode(true)) - .app(";") - .nl(new KCode("if (targetObj_ != null) {")) - .tab(code) - .nl(new KCode("}")); + return result; + } + + @Override + public Expr cloneToModel(ExprModel model) { + final Expr clonedTarget = getTarget().cloneToModel(model); + return model.field(clonedTarget, mName); + } + + @Override + public String toString() { + String name = mName.isEmpty() ? "get()" : mName; + return getTarget().toString() + '.' + name; } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/FieldAssignmentExpr.java b/compiler/src/main/java/android/databinding/tool/expr/FieldAssignmentExpr.java new file mode 100644 index 00000000..a99df620 --- /dev/null +++ b/compiler/src/main/java/android/databinding/tool/expr/FieldAssignmentExpr.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.databinding.tool.expr; + +import android.databinding.tool.reflection.ModelAnalyzer; +import android.databinding.tool.reflection.ModelClass; +import android.databinding.tool.solver.ExecutionPath; +import android.databinding.tool.writer.KCode; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is used by inverse field access expressions to assign back to the field. + * For example, <code>@={a.b}</code> is inverted to @{code a.b = value;} + */ +public class FieldAssignmentExpr extends Expr { + final String mName; + + public FieldAssignmentExpr(Expr target, String name, Expr value) { + super(target, value); + mName = name; + } + + @Override + protected String computeUniqueKey() { + return join(getTarget().getUniqueKey(), mName, "=", getValueExpr().getUniqueKey()); + } + + public Expr getTarget() { + return (FieldAccessExpr) getChildren().get(0); + } + + public Expr getValueExpr() { + return getChildren().get(1); + } + + @Override + protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { + return modelAnalyzer.findClass(void.class); + } + + @Override + protected List<Dependency> constructDependencies() { + return constructDynamicChildrenDependencies(); + } + + @Override + protected KCode generateCode() { + return new KCode() + .app("", getTarget().toCode()) + .app("." + mName + " = ", getValueExpr().toCode()); + } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.assignment(getTarget().cloneToModel(model), mName, getValueExpr()); + } + + @Override + protected String getInvertibleError() { + return "Assignment expressions are inverses of field access expressions."; + } + + @Override + public List<ExecutionPath> toExecutionPath(List<ExecutionPath> paths) { + Expr child = getTarget(); + List<ExecutionPath> targetPaths = child.toExecutionPath(paths); + + // after this, we need a null check. + List<ExecutionPath> result = new ArrayList<ExecutionPath>(); + if (child instanceof StaticIdentifierExpr) { + result.addAll(toExecutionPathInOrder(paths, child)); + } else { + for (ExecutionPath path : targetPaths) { + final ComparisonExpr cmp = getModel() + .comparison("!=", child, getModel().symbol("null", Object.class)); + path.addPath(cmp); + final ExecutionPath subPath = path.addBranch(cmp, true); + if (subPath != null) { + subPath.addPath(this); + result.add(subPath); + } + } + } + return result; + } + + @Override + public String toString() { + return getTarget().toString() + '.' + mName + " = " + getValueExpr(); + } +} diff --git a/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java b/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java index 9ff4f218..b866218a 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java @@ -23,6 +23,8 @@ import android.databinding.tool.util.Preconditions; import android.databinding.tool.writer.KCode; import android.databinding.tool.writer.LayoutBinderWriterKt; +import com.google.common.collect.Lists; + import java.util.ArrayList; import java.util.List; @@ -78,12 +80,8 @@ public class IdentifierExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { - if (expand) { - return new KCode(LayoutBinderWriterKt.getFieldName(this)); - } else { - return new KCode(LayoutBinderWriterKt.scopedName(this)); - } + protected KCode generateCode() { + return new KCode(LayoutBinderWriterKt.scopedName(this)); } public void setDeclared() { @@ -100,7 +98,20 @@ public class IdentifierExpr extends Expr { } @Override - public KCode toInverseCode(KCode value) { - return new KCode().app(LayoutBinderWriterKt.getSetterName(this)).app("(", value).app(");"); + public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { + String thisType = bindingClassName + ".this"; + Expr target = model.builtInVariable(thisType, bindingClassName, thisType); + return model.methodCall(target, LayoutBinderWriterKt.getSetterName(this), + Lists.newArrayList(value)); + } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.identifier(mName); + } + + @Override + public String toString() { + return mName; } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/InstanceOfExpr.java b/compiler/src/main/java/android/databinding/tool/expr/InstanceOfExpr.java index 980d6356..8783d0e7 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/InstanceOfExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/InstanceOfExpr.java @@ -37,14 +37,19 @@ public class InstanceOfExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { return new KCode() - .app("", getExpr().toCode(expand)) + .app("", getExpr().toCode()) .app(" instanceof ") .app(getType().toJavaCode()); } @Override + public Expr cloneToModel(ExprModel model) { + return model.instanceOfOp(getExpr().cloneToModel(model), mTypeStr); + } + + @Override protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { mType = modelAnalyzer.findClass(mTypeStr, getModel().getImports()); return modelAnalyzer.loadPrimitive("boolean"); @@ -67,4 +72,9 @@ public class InstanceOfExpr extends Expr { public String getInvertibleError() { return "two-way binding can't target a value with the 'instanceof' operator"; } + + @Override + public String toString() { + return getExpr().toString() + " instanceof " + mTypeStr; + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/LambdaExpr.java b/compiler/src/main/java/android/databinding/tool/expr/LambdaExpr.java index e210f277..13c6cb72 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/LambdaExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/LambdaExpr.java @@ -83,7 +83,7 @@ public class LambdaExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { Preconditions .checkNotNull(mCallbackWrapper, "Cannot find the callback method for %s", this); KCode code = new KCode(""); @@ -97,6 +97,11 @@ public class LambdaExpr extends Expr { return code; } + @Override + public Expr cloneToModel(ExprModel model) { + return model.lambdaExpr(getExpr().cloneToModel(model), (CallbackExprModel) model); + } + public String generateConstructor() { return getCallbackWrapper().constructForIdentifier(mCallbackId); } diff --git a/compiler/src/main/java/android/databinding/tool/expr/ListenerExpr.java b/compiler/src/main/java/android/databinding/tool/expr/ListenerExpr.java index 6adf997c..3966f7f6 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/ListenerExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/ListenerExpr.java @@ -81,7 +81,7 @@ public class ListenerExpr extends Expr { } @Override - public KCode generateCode(boolean expand) { + public KCode generateCode() { KCode code = new KCode("("); final int minApi = Math.max(mListenerType.getMinApi(), mMethod.getMinApi()); if (minApi > 1) { @@ -108,7 +108,17 @@ public class ListenerExpr extends Expr { } @Override + public Expr cloneToModel(ExprModel model) { + return model.listenerExpr(getChild().cloneToModel(model), mName, mListenerType, mMethod); + } + + @Override public String getInvertibleError() { return "Listeners cannot be the target of a two-way binding"; } + + @Override + public String toString() { + return getChild().toString() + "::" + mName; + } } 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 99e3257d..63222de3 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/MathExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/MathExpr.java @@ -18,12 +18,14 @@ package android.databinding.tool.expr; import android.databinding.tool.reflection.ModelAnalyzer; import android.databinding.tool.reflection.ModelClass; +import android.databinding.tool.util.Preconditions; import android.databinding.tool.writer.KCode; import java.util.List; public class MathExpr extends Expr { final String mOp; + MathExpr(Expr left, String op, Expr right) { super(left, right); mOp = op; @@ -31,7 +33,7 @@ public class MathExpr extends Expr { @Override protected String computeUniqueKey() { - return addTwoWay(join(getLeft().getUniqueKey(), mOp, getRight().getUniqueKey())); + return join(getLeft().getUniqueKey(), mOp, getRight().getUniqueKey()); } @Override @@ -61,8 +63,12 @@ public class MathExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { - return new KCode().app("", getLeft().toCode(expand)).app(mOp, getRight().toCode(expand)); + protected KCode generateCode() { + return new KCode().app("(", getLeft().toCode()) + .app(") ") + .app(mOp) + .app(" (", getRight().toCode()) + .app(")"); } @Override @@ -81,61 +87,50 @@ public class MathExpr extends Expr { } } - private String inverseCast() { - if (!getLeft().isDynamic()) { - return inverseCast(getRight()); - } else { - return inverseCast(getLeft()); - } - } - - private String inverseCast(Expr expr) { - if (!expr.getResolvedType().isAssignableFrom(getResolvedType())) { - return "(" + getResolvedType() + ")"; - } - return null; - } - @Override - public KCode toInverseCode(KCode value) { - if (!isDynamic()) { - return toCode(); - } + public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { final Expr left = getLeft(); final Expr right = getRight(); - final Expr constExpr = left.isDynamic() ? right : left; - final Expr varExpr = left.isDynamic() ? left : right; - final String cast = inverseCast(); - if (cast != null) { - value = new KCode(cast).app("(", value).app(")"); - } + Preconditions.check(left.isDynamic() ^ right.isDynamic(), "Two-way binding of a math " + + "operations requires A signle 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 newValue; switch (mOp.charAt(0)) { case '+': // const + x = value => x = value - const - return varExpr.toInverseCode(value.app(" - (", constExpr.toCode()).app(")")); + newValue = model.math(value, "-", constExpr); + break; case '*': // const * x = value => x = value / const - return varExpr.toInverseCode(value.app(" / (", constExpr.toCode()).app(")")); + newValue = model.math(value, "/", constExpr); + break; case '-': - if (!left.isDynamic()) { // const - x = value => x = const - value) - return varExpr.toInverseCode(new KCode() - .app("(", constExpr.toCode()) - .app(") - (", value) - .app(")")); + if (!left.isDynamic()) { // const - x = value => x = const - (value) + newValue = model.math(constExpr, "-", value); } else { // x - const = value => x = value + const) - return varExpr.toInverseCode(value.app(" + ", constExpr.toCode())); + newValue = model.math(value, "+", constExpr); } + break; case '/': if (!left.isDynamic()) { // const / x = value => x = const / value - return varExpr.toInverseCode(new KCode("(") - .app("", constExpr.toCode()) - .app(") / (", value) - .app(")")); + newValue = model.math(constExpr, "/", value); } else { // x / const = value => x = value * const - return varExpr.toInverseCode(new KCode("(") - .app("", value) - .app(") * (", constExpr.toCode()) - .app(")")); + newValue = model.math(value, "*", constExpr); } + break; + default: + throw new IllegalStateException("Invalid math operation is not invertible: " + mOp); } - throw new IllegalStateException("Invalid math operation is not invertible: " + mOp); + final Expr varExpr = left.isDynamic() ? left : right; + return varExpr.generateInverse(model, newValue, bindingClassName); + } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.math(getLeft().cloneToModel(model), mOp, getRight().cloneToModel(model)); + } + + @Override + public String toString() { + return "(" + getLeft() + ") " + mOp + " (" + getRight() + ")"; } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java b/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java index 71822fb5..6acbfa2e 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java @@ -36,8 +36,9 @@ import static android.databinding.tool.reflection.Callable.STATIC; public class MethodCallExpr extends Expr { final String mName; - Callable mGetter; + // Allow protected calls -- only used for ViewDataBinding methods. + private boolean mAllowProtected; static List<Expr> concat(Expr e, List<Expr> list) { List<Expr> merged = new ArrayList<Expr>(); @@ -64,18 +65,24 @@ public class MethodCallExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { KCode code = new KCode() - .app("", getTarget().toCode(expand)) + .app("", getTarget().toCode()) .app(".") .app(getGetter().name) .app("("); - appendArgs(code, expand); + appendArgs(code); code.app(")"); return code; } - private void appendArgs(KCode code, boolean expand) { + @Override + public Expr cloneToModel(ExprModel model) { + return model.methodCall(getTarget().cloneToModel(model), mName, + cloneToModel(model, getArgs())); + } + + private void appendArgs(KCode code) { boolean first = true; for (Expr arg : getArgs()) { if (first) { @@ -83,7 +90,7 @@ public class MethodCallExpr extends Expr { } else { code.app(", "); } - code.app("", arg.toCode(expand)); + code.app("", arg.toCode()); } } @@ -122,12 +129,20 @@ public class MethodCallExpr extends Expr { Expr target = getTarget(); boolean isStatic = target instanceof StaticIdentifierExpr; - ModelMethod method = target.getResolvedType().getMethod(mName, args, isStatic); + ModelMethod method = target.getResolvedType().getMethod(mName, args, isStatic, + mAllowProtected); if (method == null) { - String message = "cannot find method '" + mName + "' in class " + + StringBuilder argTypes = new StringBuilder(); + for (ModelClass arg : args) { + if (argTypes.length() != 0) { + argTypes.append(", "); + } + argTypes.append(arg.toJavaCode()); + } + String message = "cannot find method '" + mName + "(" + argTypes + ")' in class " + target.getResolvedType().toJavaCode(); IllegalArgumentException e = new IllegalArgumentException(message); - L.e(e, "cannot find method %s in class %s", mName, + L.e(e, "cannot find method %s(%s) in class %s", mName, argTypes, target.getResolvedType().toJavaCode()); throw e; } @@ -185,8 +200,31 @@ public class MethodCallExpr extends Expr { return mGetter; } + public void setAllowProtected() { + mAllowProtected = true; + } + @Override public String getInvertibleError() { return "Method calls may not be used in two-way expressions"; } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(getTarget()) + .append('.') + .append(mName) + .append('('); + final List<Expr> args = getArgs(); + for (int i = 0; i < args.size(); i++) { + Expr arg = args.get(i); + if (i != 0) { + buf.append(", "); + } + buf.append(arg); + } + buf.append(')'); + return buf.toString(); + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/ObservableFieldExpr.java b/compiler/src/main/java/android/databinding/tool/expr/ObservableFieldExpr.java new file mode 100644 index 00000000..59b4712a --- /dev/null +++ b/compiler/src/main/java/android/databinding/tool/expr/ObservableFieldExpr.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.databinding.tool.expr; + +import android.databinding.tool.ext.ExtKt; +import android.databinding.tool.reflection.ModelAnalyzer; +import android.databinding.tool.reflection.ModelClass; +import android.databinding.tool.util.BrNameUtil; +import android.databinding.tool.util.L; + +public class ObservableFieldExpr extends FieldAccessExpr { + + ObservableFieldExpr(Expr parent, String name) { + super(parent, name); + } + + @Override + public Expr resolveListeners(ModelClass listener, Expr parent) { + return this; // ObservableFields aren't listeners + } + + @Override + protected String computeUniqueKey() { + return join(mName, "..", super.computeUniqueKey()); + } + + @Override + protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { + if (mGetter == null) { + Expr target = getTarget(); + target.getResolvedType(); + boolean isStatic = target instanceof StaticIdentifierExpr; + ModelClass resolvedType = target.getResolvedType(); + L.d("resolving %s. Resolved class type: %s", this, resolvedType); + + mGetter = resolvedType.findGetterOrField(mName, isStatic); + + if (mGetter == null) { + L.e("Could not find accessor %s.%s", resolvedType.getCanonicalName(), mName); + return null; + } + + if (mGetter.isStatic() && !isStatic) { + // found a static method on an instance. register a new one + replaceStaticIdentifier(resolvedType); + } + if (hasBindableAnnotations()) { + mBrName = ExtKt.br(BrNameUtil.brKey(getGetter())); + } else { + mBrName = ExtKt.br(mName); + } + } + return mGetter.resolvedType; + } + + @Override + public Expr cloneToModel(ExprModel model) { + final Expr clonedTarget = getTarget().cloneToModel(model); + return model.observableField(clonedTarget, mName); + } + + @Override + public String toString() { + return getTarget().toString() + '.' + mName; + } +} diff --git a/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java b/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java index 752cb9f8..2ecf5fb0 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java @@ -120,20 +120,22 @@ public class ResourceExpr extends Expr { @Override protected String computeUniqueKey() { - String base; - if (mPackage == null) { - base = "@" + mResourceType + "/" + mResourceId; - } else { - base = "@" + "android:" + mResourceType + "/" + mResourceId; - } + String base = toString(); return join(base, computeChildrenKey()); } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { return new KCode(toJava()); } + @Override + public Expr cloneToModel(ExprModel model) { + String pkg = mPackage.isEmpty() ? "" : "android"; + return model.resourceExpr(pkg, mResourceType, mResourceId, + cloneToModel(model, getChildren())); + } + public String getResourceId() { return mPackage + "R." + getResourceObject() + "." + mResourceId; } @@ -211,4 +213,13 @@ public class ResourceExpr extends Expr { } return rFileObject; } + + @Override + public String toString() { + if (mPackage == null) { + return "@" + mResourceType + "/" + mResourceId; + } else { + return "@" + "android:" + mResourceType + "/" + mResourceId; + } + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/StaticIdentifierExpr.java b/compiler/src/main/java/android/databinding/tool/expr/StaticIdentifierExpr.java index 7618e946..0d36f2a1 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/StaticIdentifierExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/StaticIdentifierExpr.java @@ -41,11 +41,17 @@ public class StaticIdentifierExpr extends IdentifierExpr { } @Override - public KCode toInverseCode(KCode value) { + public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { throw new IllegalStateException("StaticIdentifierExpr is not invertible."); } + @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { return new KCode(getResolvedType().toJavaCode()); } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.staticIdentifier(mName); + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/SymbolExpr.java b/compiler/src/main/java/android/databinding/tool/expr/SymbolExpr.java index ee3a1677..388d2240 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/SymbolExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/SymbolExpr.java @@ -20,7 +20,6 @@ import android.databinding.tool.reflection.ModelAnalyzer; import android.databinding.tool.reflection.ModelClass; import android.databinding.tool.solver.ExecutionPath; import android.databinding.tool.writer.KCode; -import android.databinding.tool.writer.LayoutBinderWriterKt; import java.util.ArrayList; import java.util.List; @@ -55,11 +54,16 @@ public class SymbolExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { return new KCode(getText()); } @Override + public Expr cloneToModel(ExprModel model) { + return model.symbol(mText, mType); + } + + @Override protected List<Dependency> constructDependencies() { return new ArrayList<Dependency>(); } @@ -76,4 +80,9 @@ public class SymbolExpr extends Expr { } return super.toExecutionPath(paths); } + + @Override + public String toString() { + return mText; + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/TernaryExpr.java b/compiler/src/main/java/android/databinding/tool/expr/TernaryExpr.java index a23e6c22..856cab04 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/TernaryExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/TernaryExpr.java @@ -124,26 +124,35 @@ public class TernaryExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { return new KCode() - .app("", getPred().toCode(expand)) - .app(" ? ", getIfTrue().toCode(expand)) - .app(" : ", getIfFalse().toCode(expand)); + .app("(", getPred().toCode()) + .app(") ? (", getIfTrue().toCode()) + .app(") : (", getIfFalse().toCode()) + .app(")"); } @Override - public KCode toInverseCode(KCode variable) { - return new KCode() - .app("if (", getPred().toCode(true)) - .app(") {") - .tab(getIfTrue().toInverseCode(variable)) - .nl(new KCode("} else {")) - .tab(getIfFalse().toInverseCode(variable)) - .nl(new KCode("}")); + public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { + final Expr pred = getPred().cloneToModel(model); + final Expr ifTrue = getIfTrue().generateInverse(model, value, bindingClassName); + final Expr ifFalse = getIfFalse().generateInverse(model, value, bindingClassName); + return model.ternary(pred, ifTrue, ifFalse); + } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.ternary(getPred().cloneToModel(model), getIfTrue().cloneToModel(model), + getIfFalse().cloneToModel(model)); } @Override public boolean isConditional() { return true; } + + @Override + public String toString() { + return getPred().toString() + " ? " + getIfTrue() + " : " + getIfFalse(); + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/TwoWayListenerExpr.java b/compiler/src/main/java/android/databinding/tool/expr/TwoWayListenerExpr.java index 1a656732..c14cdd6c 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/TwoWayListenerExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/TwoWayListenerExpr.java @@ -45,12 +45,17 @@ public class TwoWayListenerExpr extends Expr { } @Override - protected KCode generateCode(boolean expand) { + protected KCode generateCode() { final String fieldName = LayoutBinderWriterKt.getFieldName(mInverseBinding); return new KCode(fieldName); } @Override + public Expr cloneToModel(ExprModel model) { + return model.twoWayListenerExpr(mInverseBinding); + } + + @Override protected String computeUniqueKey() { return "event(" + mInverseBinding.getEventAttribute() + ", " + System.identityHashCode(mInverseBinding) + ")"; diff --git a/compiler/src/main/java/android/databinding/tool/expr/UnaryExpr.java b/compiler/src/main/java/android/databinding/tool/expr/UnaryExpr.java index 881a352c..18e5985f 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/UnaryExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/UnaryExpr.java @@ -36,17 +36,22 @@ public class UnaryExpr extends Expr { @Override protected String computeUniqueKey() { - return addTwoWay(join(getOpStr(), getExpr().getUniqueKey())); + return join(getOpStr(), getExpr().getUniqueKey()); } @Override - public KCode toInverseCode(KCode value) { - return getExpr().toInverseCode(new KCode().app(mOp, value)); + public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { + return model.unary(mOp, getExpr().generateInverse(model, value, bindingClassName)); } @Override - protected KCode generateCode(boolean expand) { - return new KCode().app(getOp(), getExpr().toCode(expand)); + public Expr cloneToModel(ExprModel model) { + return model.unary(mOp, getExpr().cloneToModel(model)); + } + + @Override + protected KCode generateCode() { + return new KCode().app(getOp(), getExpr().toCode()); } @Override @@ -76,4 +81,9 @@ public class UnaryExpr extends Expr { public Expr getExpr() { return getChildren().get(0); } + + @Override + public String toString() { + return mOp + getExpr(); + } } diff --git a/compiler/src/main/java/android/databinding/tool/expr/ViewFieldExpr.java b/compiler/src/main/java/android/databinding/tool/expr/ViewFieldExpr.java index 0a6b15b1..59c0dbeb 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/ViewFieldExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/ViewFieldExpr.java @@ -25,7 +25,7 @@ public class ViewFieldExpr extends BuiltInVariableExpr { private final BindingTarget mBindingTarget; ViewFieldExpr(BindingTarget bindingTarget) { - super(LayoutBinderWriterKt.getFieldName(bindingTarget), initialType(bindingTarget), + super(LayoutBinderWriterKt.getFieldName(bindingTarget), bindingTarget.getInterfaceType(), LayoutBinderWriterKt.getFieldName(bindingTarget)); mBindingTarget = bindingTarget; } @@ -35,12 +35,6 @@ public class ViewFieldExpr extends BuiltInVariableExpr { return "View fields may not be the target of two-way binding"; } - private static String initialType(BindingTarget bindingTarget) { - return bindingTarget.isBinder() - ? "android.databinding.ViewDataBinding" - : bindingTarget.getInterfaceType(); - } - public BindingTarget getBindingTarget() { return mBindingTarget; } @@ -49,8 +43,13 @@ public class ViewFieldExpr extends BuiltInVariableExpr { protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { final ModelClass type = modelAnalyzer.findClass(mBindingTarget.getInterfaceType(), null); if (type == null) { - return modelAnalyzer.findClass("android.databinding.ViewDataBinding", null); + return modelAnalyzer.findClass(ModelAnalyzer.VIEW_DATA_BINDING, null); } return type; } + + @Override + public Expr cloneToModel(ExprModel model) { + return model.viewFieldExpr(mBindingTarget); + } } diff --git a/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClass.java b/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClass.java new file mode 100644 index 00000000..4759a9a6 --- /dev/null +++ b/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClass.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.databinding.tool.reflection; + +import android.databinding.tool.util.StringUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * A class that can be used by ModelAnalyzer without any backing model. This is used + * for ViewDataBinding subclasses that haven't been generated yet, but we still want + * to resolve methods and fields for them. + * + * @see ModelAnalyzer#injectViewDataBinding(String, Map, Map) + */ +public class InjectedBindingClass extends ModelClass { + private final String mClassName; + private final String mSuperClass; + private final Map<String, String> mVariables; + private final Map<String, String> mFields; + + public InjectedBindingClass(String className, String superClass, Map<String, String> variables, + Map<String, String> fields) { + mClassName = className; + mSuperClass = superClass; + mVariables = variables; + mFields = fields; + } + + @Override + public String toJavaCode() { + return mClassName; + } + + @Override + public boolean isArray() { + return false; + } + + @Override + public ModelClass getComponentType() { + return null; + } + + @Override + public boolean isNullable() { + return true; + } + + @Override + public boolean isPrimitive() { + return false; + } + + @Override + public boolean isBoolean() { + return false; + } + + @Override + public boolean isChar() { + return false; + } + + @Override + public boolean isByte() { + return false; + } + + @Override + public boolean isShort() { + return false; + } + + @Override + public boolean isInt() { + return false; + } + + @Override + public boolean isLong() { + return false; + } + + @Override + public boolean isFloat() { + return false; + } + + @Override + public boolean isDouble() { + return false; + } + + @Override + public boolean isGeneric() { + return false; + } + + @Override + public List<ModelClass> getTypeArguments() { + return null; + } + + @Override + public boolean isTypeVar() { + return false; + } + + @Override + public boolean isWildcard() { + return false; + } + + @Override + public boolean isInterface() { + return false; + } + + @Override + public boolean isVoid() { + return false; + } + + @Override + public ModelClass unbox() { + return this; + } + + @Override + public ModelClass box() { + return this; + } + + @Override + public boolean isObservable() { + return getSuperclass().isObservable(); + } + + @Override + public boolean isAssignableFrom(ModelClass that) { + ModelClass superClass = that; + while (superClass != null && !superClass.isObject()) { + if (superClass.toJavaCode().equals(mClassName)) { + return true; + } + } + return false; + } + + @Override + public ModelClass getSuperclass() { + return ModelAnalyzer.getInstance().findClass(mSuperClass, null); + } + + @Override + public ModelClass erasure() { + return this; + } + + @Override + public String getJniDescription() { + return TypeUtil.getInstance().getDescription(this); + } + + @Override + protected ModelField[] getDeclaredFields() { + ModelClass superClass = getSuperclass(); + final ModelField[] superFields = superClass.getDeclaredFields(); + final int fieldCount = superFields.length + 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); + } + return fields; + } + + @Override + protected ModelMethod[] getDeclaredMethods() { + ModelClass superClass = getSuperclass(); + final ModelMethod[] superMethods = superClass.getDeclaredMethods(); + final int methodCount = superMethods.length + (mVariables.size() * 2); + 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); + } + return methods; + } + + @Override + public String toString() { + return "Injected Class: " + mClassName; + } +} diff --git a/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassField.java b/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassField.java new file mode 100644 index 00000000..d15cc908 --- /dev/null +++ b/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassField.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.databinding.tool.reflection; + +import java.util.Map; + +/** + * A class that can be used by ModelAnalyzer without any backing model. This is used + * for fields on ViewDataBinding subclasses that haven't been generated yet. + * + * @see ModelAnalyzer#injectViewDataBinding(String, Map, Map) + */ +public class InjectedBindingClassField extends ModelField { + private final String mType; + private final String mName; + + public InjectedBindingClassField(String name, String type) { + mName = name; + mType = type; + } + + @Override + public boolean isBindable() { + return false; + } + + @Override + public String getName() { + return mName; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public boolean isFinal() { + return true; + } + + @Override + public ModelClass getFieldType() { + return ModelAnalyzer.getInstance().findClass(mType, null); + } +} diff --git a/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassMethod.java b/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassMethod.java new file mode 100644 index 00000000..20e24824 --- /dev/null +++ b/compiler/src/main/java/android/databinding/tool/reflection/InjectedBindingClassMethod.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.databinding.tool.reflection; + +import java.util.List; +import java.util.Map; + +/** + * A class that can be used by ModelAnalyzer without any backing model. This is used + * for methods on ViewDataBinding subclasses that haven't been generated yet. + * + * @see ModelAnalyzer#injectViewDataBinding(String, Map, Map) + */ +public class InjectedBindingClassMethod extends ModelMethod { + private final InjectedBindingClass mContainingClass; + private final String mName; + private final String mReturnType; + private final String mParameter; + + public InjectedBindingClassMethod(InjectedBindingClass containingClass, String name, String returnType, + String parameter) { + mContainingClass = containingClass; + mName = name; + mReturnType = returnType; + mParameter = parameter; + } + + @Override + public ModelClass getDeclaringClass() { + return mContainingClass; + } + + @Override + public ModelClass[] getParameterTypes() { + if (mParameter != null) { + ModelClass parameterType = ModelAnalyzer.getInstance().findClass(mParameter, null); + return new ModelClass[] { parameterType }; + } + return new ModelClass[0]; + } + + @Override + public String getName() { + return mName; + } + + @Override + public ModelClass getReturnType(List<ModelClass> args) { + ModelClass returnType = ModelAnalyzer.getInstance().findClass(mReturnType, null); + return returnType; + } + + @Override + public boolean isVoid() { + return getReturnType().isVoid(); + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public boolean isProtected() { + return false; + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public boolean isAbstract() { + return true; + } + + @Override + public boolean isBindable() { + return false; + } + + @Override + public int getMinApi() { + return 0; + } + + @Override + public String getJniDescription() { + return TypeUtil.getInstance().getDescription(this); + } + + @Override + public boolean isVarArgs() { + return false; + } +} 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 6b4bf876..8943200b 100644 --- a/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java +++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java @@ -19,6 +19,7 @@ import android.databinding.tool.reflection.annotation.AnnotationAnalyzer; import android.databinding.tool.util.L; import android.databinding.tool.util.Preconditions; +import java.util.HashMap; import java.util.Map; import javax.annotation.processing.ProcessingEnvironment; @@ -82,6 +83,8 @@ public abstract class ModelAnalyzer { private ModelClass mViewStubType; private static ModelAnalyzer sAnalyzer; + private final Map<String, InjectedBindingClass> mInjectedClasses = + new HashMap<String, InjectedBindingClass>(); protected void setInstance(ModelAnalyzer analyzer) { sAnalyzer = analyzer; @@ -218,12 +221,27 @@ public abstract class ModelAnalyzer { return "null"; } - public abstract ModelClass findClass(String className, Map<String, String> imports); + public final ModelClass findClass(String className, Map<String, String> imports) { + if (mInjectedClasses.containsKey(className)) { + return mInjectedClasses.get(className); + } + return findClassInternal(className, imports); + } + + public abstract ModelClass findClassInternal(String className, Map<String, String> imports); public abstract ModelClass findClass(Class classType); public abstract TypeUtil createTypeUtil(); + public ModelClass injectViewDataBinding(String className, Map<String, String> variables, + Map<String, String> fields) { + InjectedBindingClass injectedClass = new InjectedBindingClass(className, + ModelAnalyzer.VIEW_DATA_BINDING, variables, fields); + mInjectedClasses.put(className, injectedClass); + return injectedClass; + } + ModelClass[] getListTypes() { if (mListTypes == null) { mListTypes = new ModelClass[LIST_CLASS_NAMES.length]; diff --git a/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java b/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java index 35eaf6fa..243fcdee 100644 --- a/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java +++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java @@ -235,23 +235,27 @@ public abstract class ModelClass { public abstract boolean isAssignableFrom(ModelClass that); /** - * Returns an array containing all public methods on the type represented by this ModelClass - * with the name <code>name</code> and can take the passed-in types as arguments. This will - * also work if the arguments match VarArgs parameter. + * Returns an array containing all public methods (or protected if allowProtected is true) + * on the type represented by this ModelClass with the name <code>name</code> and can + * take the passed-in types as arguments. This will also work if the arguments match + * VarArgs parameter. * * @param name The name of the method to find. * @param args The types that the method should accept. * @param staticOnly Whether only static methods should be returned or both instance methods * and static methods are valid. + * @param allowProtected true if the method can be protected as well as public. * * @return An array containing all public methods with the name <code>name</code> and taking * <code>args</code> parameters. */ - public ModelMethod[] getMethods(String name, List<ModelClass> args, boolean staticOnly) { + public ModelMethod[] getMethods(String name, List<ModelClass> args, boolean staticOnly, + boolean allowProtected) { ModelMethod[] methods = getDeclaredMethods(); ArrayList<ModelMethod> matching = new ArrayList<ModelMethod>(); for (ModelMethod method : methods) { - if (method.isPublic() && (!staticOnly || method.isStatic()) && + if ((method.isPublic() || (allowProtected && method.isProtected())) && + (!staticOnly || method.isStatic()) && name.equals(method.getName()) && method.acceptsArguments(args)) { matching.add(method); } @@ -288,9 +292,11 @@ public abstract class ModelClass { * @param args The arguments that the method should accept * @param staticOnly true if the returned method must be static or false if it does not * matter. + * @param allowProtected true if the method can be protected as well as public. */ - public ModelMethod getMethod(String name, List<ModelClass> args, boolean staticOnly) { - ModelMethod[] methods = getMethods(name, args, staticOnly); + public ModelMethod getMethod(String name, List<ModelClass> args, boolean staticOnly, + boolean allowProtected) { + ModelMethod[] methods = getMethods(name, args, staticOnly, allowProtected); L.d("looking methods for %s. static only ? %s . method count: %d", name, staticOnly, methods.length); for (ModelMethod method : methods) { @@ -397,7 +403,8 @@ public abstract class ModelClass { name }; for (String methodName : methodNames) { - ModelMethod[] methods = getMethods(methodName, new ArrayList<ModelClass>(), staticOnly); + ModelMethod[] methods = + getMethods(methodName, new ArrayList<ModelClass>(), staticOnly, false); for (ModelMethod method : methods) { if (method.isPublic() && (!staticOnly || method.isStatic()) && !method.getReturnType(Arrays.asList(method.getParameterTypes())).isVoid()) { @@ -465,7 +472,8 @@ public abstract class ModelClass { name }; for (String methodName : methodNames) { - ModelMethod[] methods = getMethods(methodName, new ArrayList<ModelClass>(), false); + ModelMethod[] methods = + getMethods(methodName, new ArrayList<ModelClass>(), false, false); for (ModelMethod method : methods) { if (method.isPublic() && !method.isStatic() && !method.getReturnType(Arrays.asList(method.getParameterTypes())).isVoid()) { 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 87ae28d4..5bd214e3 100644 --- a/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java +++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java @@ -32,6 +32,8 @@ public abstract class ModelMethod { public abstract boolean isPublic(); + public abstract boolean isProtected(); + public abstract boolean isStatic(); public abstract boolean isAbstract(); @@ -76,6 +78,9 @@ public abstract class ModelMethod { for (int i = 0; i < args.size(); i++) { ModelClass parameterType = getParameter(i, parameterTypes); ModelClass arg = args.get(i); + if (parameterType.isIncomplete()) { + parameterType = parameterType.erasure(); + } if (!parameterType.isAssignableFrom(arg) && !isImplicitConversion(arg, parameterType)) { parametersMatch = false; break; diff --git a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationAnalyzer.java b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationAnalyzer.java index ef52880b..80664cda 100644 --- a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationAnalyzer.java +++ b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationAnalyzer.java @@ -26,7 +26,6 @@ import java.util.Map; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; @@ -85,7 +84,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer { } @Override - public AnnotationClass findClass(String className, Map<String, String> imports) { + public ModelClass findClassInternal(String className, Map<String, String> imports) { className = className.trim(); int numDimensions = 0; while (className.endsWith("[]")) { @@ -121,7 +120,8 @@ public class AnnotationAnalyzer extends ModelAnalyzer { ArrayList<String> templateParameters = splitTemplateParameters(paramStr); TypeMirror[] typeArgs = new TypeMirror[templateParameters.size()]; for (int i = 0; i < typeArgs.length; i++) { - final AnnotationClass clazz = findClass(templateParameters.get(i), imports); + final AnnotationClass clazz = (AnnotationClass) + findClass(templateParameters.get(i), imports); if (clazz == null) { L.e("cannot find type argument for %s in %s", templateParameters.get(i), baseClassName); diff --git a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java index ce17c4b0..feae09db 100644 --- a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java +++ b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java @@ -286,13 +286,17 @@ class AnnotationClass extends ModelClass { @Override public boolean isAssignableFrom(ModelClass that) { - if (that == null) { + ModelClass other = that; + while (other != null && !(other instanceof AnnotationClass)) { + other = other.getSuperclass(); + } + if (other == null) { return false; } - if (equals(that)) { + if (equals(other)) { return true; } - AnnotationClass thatAnnotationClass = (AnnotationClass) that; + AnnotationClass thatAnnotationClass = (AnnotationClass) other; return getTypeUtils().isAssignable(thatAnnotationClass.mTypeMirror, this.mTypeMirror); } diff --git a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java index d7caa452..66c1dbc5 100644 --- a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java +++ b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java @@ -127,6 +127,11 @@ class AnnotationMethod extends ModelMethod { } @Override + public boolean isProtected() { + return mExecutableElement.getModifiers().contains(Modifier.PROTECTED); + } + + @Override public boolean isStatic() { return mExecutableElement.getModifiers().contains(Modifier.STATIC); } diff --git a/compiler/src/main/java/android/databinding/tool/store/SetterStore.java b/compiler/src/main/java/android/databinding/tool/store/SetterStore.java index 95688100..90f75e2f 100644 --- a/compiler/src/main/java/android/databinding/tool/store/SetterStore.java +++ b/compiler/src/main/java/android/databinding/tool/store/SetterStore.java @@ -714,7 +714,7 @@ public class SetterStore { if (viewType == null) { return null; } else if (viewType.isViewDataBinding()) { - return new ViewDataBindingGetterCall(attribute); + return new ViewDataBindingGetterCall(viewType, attribute); } attribute = stripNamespace(attribute); @@ -758,11 +758,11 @@ public class SetterStore { viewType.getCanonicalName()); } else { bestMethod.call = new AdapterGetter(inverseDescription, - setters.get(0)); + setters.get(0), key.valueType); } } else { bestMethod.call = new AdapterGetter(inverseDescription, - eventCall); + eventCall, key.valueType); } } @@ -1271,6 +1271,7 @@ public class SetterStore { } private static class IntermediateV2 extends IntermediateV1 { + private static final long serialVersionUID = 0xA45C2EB637E35C07L; public final HashMap<String, HashMap<AccessorKey, InverseDescription>> inverseAdapters = new HashMap<String, HashMap<AccessorKey, InverseDescription>>(); public final HashMap<String, HashMap<String, InverseDescription>> inverseMethods = @@ -1635,6 +1636,8 @@ public class SetterStore { public interface BindingGetterCall { String toJava(String componentExpression, String viewExpression); + String getGetterType(); + int getMinApi(); String getBindingAdapterInstanceClass(); @@ -1650,12 +1653,14 @@ public class SetterStore { private final String mGetter; private final BindingSetterCall mEventSetter; private final String mAttribute; + private final ModelClass mBindingClass; - public ViewDataBindingGetterCall(String attribute) { + public ViewDataBindingGetterCall(ModelClass bindingClass, String attribute) { final int colonIndex = attribute.indexOf(':'); mAttribute = attribute.substring(colonIndex + 1); mGetter = "get" + StringUtils.capitalize(mAttribute); mEventSetter = new ViewDataBindingEventSetter(); + mBindingClass = bindingClass; } @Override @@ -1664,6 +1669,11 @@ public class SetterStore { } @Override + public String getGetterType() { + return mBindingClass.findInstanceGetter(mGetter).getReturnType().toJavaCode(); + } + + @Override public int getMinApi() { return 0; } @@ -1716,6 +1726,11 @@ public class SetterStore { } @Override + public String getGetterType() { + return mMethod.getReturnType().toJavaCode(); + } + + @Override public int getMinApi() { return mMethod.getMinApi(); } @@ -1734,10 +1749,18 @@ public class SetterStore { private final InverseDescription mInverseDescription; private String mBindingAdapterCall; private final BindingSetterCall mEventCall; + private final String mGetterType; - public AdapterGetter(InverseDescription description, BindingSetterCall eventCall) { + public AdapterGetter(InverseDescription description, BindingSetterCall eventCall, + String getterType) { mInverseDescription = description; mEventCall = eventCall; + mGetterType = getterType; + } + + @Override + public String getGetterType() { + return mGetterType; } @Override diff --git a/compiler/src/main/kotlin/android/databinding/tool/expr/ExprWriters.kt b/compiler/src/main/kotlin/android/databinding/tool/expr/ExprWriters.kt index 618c1413..84884516 100644 --- a/compiler/src/main/kotlin/android/databinding/tool/expr/ExprWriters.kt +++ b/compiler/src/main/kotlin/android/databinding/tool/expr/ExprWriters.kt @@ -30,7 +30,7 @@ fun Expr.shouldLocalizeInCallbacks() = canBeEvaluatedToAVariable() && !resolvedT fun CallbackExprModel.localizeGlobalVariables(vararg ignore: Expr): KCode = kcode("// localize variables for thread safety") { // puts all variables in this model to local values. mExprMap.values.filter { it.shouldLocalizeInCallbacks() && !ignore.contains(it) }.forEach { - nl("// ${it.uniqueKey}") + nl("// ${it.toString()}") nl("${it.resolvedType.toJavaCode()} ${it.scopedName()} = ${if (it.isVariable()) it.fieldName else it.defaultValue};") } } diff --git a/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt b/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt index 51c1cc95..7809f5b6 100644 --- a/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt +++ b/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt @@ -206,7 +206,6 @@ val Expr.callbackLocalName by lazyProp { expr : Expr -> else expr.toCode().generate() } - val Expr.executePendingLocalName by lazyProp { expr : Expr -> if(expr.needsLocalField) "${expr.model.ext.getUniqueName(expr.readableName, Scope.EXECUTE_PENDING_METHOD, false)}" else expr.toCode().generate() @@ -366,7 +365,12 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { nl(declareVariables()) nl(declareBoundValues()) nl(declareListeners()) - nl(declareInverseBindingImpls()); + try { + Scope.enter(Scope.GLOBAL) + nl(declareInverseBindingImpls()); + } finally { + Scope.exit() + } nl(declareConstructor(minSdk)) nl(declareInvalidateAll()) nl(declareHasPendingBindings()) @@ -853,14 +857,30 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { className = "android.databinding.InverseBindingListener" param = "" } - nl("private $className ${inverseBinding.fieldName} = new $className($param) {") { - tab("@Override") - tab("public void onChange() {") { - tab(inverseBinding.toJavaCode("mBindingComponent", mDirtyFlags)).app(";"); + block("private $className ${inverseBinding.fieldName} = new $className($param)") { + nl("@Override") + block("public void onChange()") { + if (inverseBinding.inverseExpr != null) { + val valueExpr = inverseBinding.variableExpr + val getterCall = inverseBinding.getterCall + nl("// Inverse of ${inverseBinding.expr}") + nl("// is ${inverseBinding.inverseExpr}") + nl("${valueExpr.resolvedType.toJavaCode()} ${valueExpr.name} = ${getterCall.toJava("mBindingComponent", target.fieldName)};") + nl(inverseBinding.callbackExprModel.localizeGlobalVariables(valueExpr)) + nl(inverseBinding.executionPath.toCode()) + } else { + block("synchronized(this)") { + val flagSet = inverseBinding.chainedExpressions.fold(FlagSet(), { initial, expr -> + initial.or(FlagSet(expr.id)) + }) + mDirtyFlags.mapOr(flagSet) { suffix, index -> + tab("${mDirtyFlags.localValue(index)} |= ${flagSet.binaryCode(index)};") + } + } + nl("requestRebind();") + } } - tab("}") - } - nl("};") + }.app(";") } } } @@ -999,7 +1019,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { if (!assignedValues.isEmpty()) { val assignment = kcode("") { assignedValues.forEach { expr: Expr -> - tab("// read ${expr.uniqueKey}") + tab("// read ${expr}") tab("${expr.executePendingLocalName}").app(" = ", expr.toFullCode()).app(";") } } @@ -1018,7 +1038,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { it.value.forEach { expr: Expr -> justRead.add(expr) - L.d("%s / readWithDependants %s", className, expr.uniqueKey); + L.d("%s / readWithDependants %s", className, expr); L.d("flag set:%s . inherited flags: %s. need another if: %s", flagSet, inheritedFlags, needsIfWrapper); // if I am the condition for an expression, set its flag diff --git a/compiler/src/test/java/android/databinding/tool/ExpressionVisitorTest.java b/compiler/src/test/java/android/databinding/tool/ExpressionVisitorTest.java index 97331cfa..84b032af 100644 --- a/compiler/src/test/java/android/databinding/tool/ExpressionVisitorTest.java +++ b/compiler/src/test/java/android/databinding/tool/ExpressionVisitorTest.java @@ -143,8 +143,8 @@ public class ExpressionVisitorTest { @Test public void testInheritedFieldResolution() { final FieldAccessExpr parsed = parse("myStr.length", FieldAccessExpr.class); - assertTrue(parsed.getChild() instanceof IdentifierExpr); - final IdentifierExpr id = (IdentifierExpr) parsed.getChild(); + assertTrue(parsed.getTarget() instanceof IdentifierExpr); + final IdentifierExpr id = (IdentifierExpr) parsed.getTarget(); id.setUserDefinedType("java.lang.String"); assertEquals(new JavaClass(int.class), parsed.getResolvedType()); Callable getter = parsed.getGetter(); @@ -159,8 +159,8 @@ public class ExpressionVisitorTest { @Test public void testGetterResolution() { final FieldAccessExpr parsed = parse("myStr.bytes", FieldAccessExpr.class); - assertTrue(parsed.getChild() instanceof IdentifierExpr); - final IdentifierExpr id = (IdentifierExpr) parsed.getChild(); + assertTrue(parsed.getTarget() instanceof IdentifierExpr); + final IdentifierExpr id = (IdentifierExpr) parsed.getTarget(); id.setUserDefinedType("java.lang.String"); assertEquals(new JavaClass(byte[].class), parsed.getResolvedType()); Callable getter = parsed.getGetter(); diff --git a/compiler/src/test/java/android/databinding/tool/LayoutBinderTest.java b/compiler/src/test/java/android/databinding/tool/LayoutBinderTest.java index 8b1f820d..c7f81ab1 100644 --- a/compiler/src/test/java/android/databinding/tool/LayoutBinderTest.java +++ b/compiler/src/test/java/android/databinding/tool/LayoutBinderTest.java @@ -77,8 +77,8 @@ public class LayoutBinderTest { int originalSize = mExprModel.size(); mLayoutBinder.addVariable("user", "android.databinding.tool2.LayoutBinderTest.TestUser", null); - mLayoutBinder.parse("user.name", false, null); - mLayoutBinder.parse("user.lastName", false, null); + mLayoutBinder.parse("user.name", null); + mLayoutBinder.parse("user.lastName", null); assertEquals(originalSize + 3, mExprModel.size()); final List<Expr> bindingExprs = mExprModel.getBindingExpressions(); assertEquals(2, bindingExprs.size()); @@ -94,7 +94,7 @@ public class LayoutBinderTest { public void testParseWithMethods() { mLayoutBinder.addVariable("user", "android.databinding.tool.LayoutBinderTest.TestUser", null); - mLayoutBinder.parse("user.fullName", false, null); + mLayoutBinder.parse("user.fullName", null); Expr item = mExprModel.getBindingExpressions().get(0); assertTrue(item instanceof FieldAccessExpr); IdentifierExpr id = mExprModel.identifier("user"); @@ -102,7 +102,7 @@ public class LayoutBinderTest { fa.getResolvedType(); final Callable getter = fa.getGetter(); assertTrue(getter.type == Callable.Type.METHOD); - assertSame(id, fa.getChild()); + assertSame(id, fa.getTarget()); assertTrue(fa.isDynamic()); } diff --git a/compiler/src/test/java/android/databinding/tool/expr/ExecutionPathTest.java b/compiler/src/test/java/android/databinding/tool/expr/ExecutionPathTest.java index 4f31ecaa..2bb8832f 100644 --- a/compiler/src/test/java/android/databinding/tool/expr/ExecutionPathTest.java +++ b/compiler/src/test/java/android/databinding/tool/expr/ExecutionPathTest.java @@ -57,7 +57,7 @@ public class ExecutionPathTest { public void simpleExpr() { MockLayoutBinder lb = new MockLayoutBinder(); ExprModel model = lb.getModel(); - Expr parsed = lb.parse(mExpression, false, null); + Expr parsed = lb.parse(mExpression, null); List<ExecutionPath> paths = new ArrayList<ExecutionPath>(); ExecutionPath root = ExecutionPath.createRoot(); paths.add(root); diff --git a/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java b/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java index 6a89cb6a..20d523f6 100644 --- a/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java +++ b/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java @@ -75,11 +75,16 @@ public class ExprModelTest { } @Override - protected KCode generateCode(boolean full) { + protected KCode generateCode() { return new KCode(); } @Override + public Expr cloneToModel(ExprModel model) { + return this; + } + + @Override protected String getInvertibleError() { return "DummyExpr cannot be 2-way."; } @@ -149,7 +154,7 @@ public class ExprModelTest { IdentifierExpr a = lb.addVariable("a", "java.lang.String", null); IdentifierExpr b = lb.addVariable("b", "java.lang.String", null); IdentifierExpr c = lb.addVariable("c", "java.lang.String", null); - lb.parse("a == null ? b : c", false, null); + lb.parse("a == null ? b : c", null); mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class)); lb.getModel().seal(); List<Expr> shouldRead = getShouldRead(); @@ -298,7 +303,7 @@ public class ExprModelTest { IdentifierExpr c = lb.addVariable("c", "java.lang.String", null); IdentifierExpr d = lb.addVariable("d", "java.lang.String", null); IdentifierExpr e = lb.addVariable("e", "java.lang.String", null); - final Expr aTernary = lb.parse("a == null ? b == null ? c : d : e", false, null); + final Expr aTernary = lb.parse("a == null ? b == null ? c : d : e", null); assertTrue(aTernary instanceof TernaryExpr); final Expr bTernary = ((TernaryExpr) aTernary).getIfTrue(); assertTrue(bTernary instanceof TernaryExpr); @@ -796,7 +801,7 @@ public class ExprModelTest { assertFalse(fieldAccess.isDynamic()); mExprModel.seal(); assertEquals(0, getShouldRead().size()); - final Expr child = fieldAccess.getChild(); + final Expr child = fieldAccess.getTarget(); assertTrue(child instanceof StaticIdentifierExpr); StaticIdentifierExpr id = (StaticIdentifierExpr) child; assertEquals(id.getResolvedType().getCanonicalName(), "android.view.View"); @@ -1058,7 +1063,7 @@ public class ExprModelTest { } private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) { - final Expr parsed = binder.parse(input, false, null); + final Expr parsed = binder.parse(input, null); assertTrue(klass.isAssignableFrom(parsed.getClass())); return (T) parsed; } diff --git a/compiler/src/test/java/android/databinding/tool/expr/ExprTest.java b/compiler/src/test/java/android/databinding/tool/expr/ExprTest.java index 61d04cb6..6966dd49 100644 --- a/compiler/src/test/java/android/databinding/tool/expr/ExprTest.java +++ b/compiler/src/test/java/android/databinding/tool/expr/ExprTest.java @@ -56,11 +56,16 @@ public class ExprTest{ } @Override - protected KCode generateCode(boolean full) { + protected KCode generateCode() { return new KCode(); } @Override + public Expr cloneToModel(ExprModel model) { + return this; + } + + @Override protected String getInvertibleError() { return null; } @@ -90,11 +95,16 @@ public class ExprTest{ } @Override - protected KCode generateCode(boolean full) { + protected KCode generateCode() { return new KCode(); } @Override + public Expr cloneToModel(ExprModel model) { + return this; + } + + @Override protected String getInvertibleError() { return null; } 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 1b97cd9f..695e04b8 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 @@ -13,17 +13,17 @@ package android.databinding.tool.reflection.java; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; - -import org.apache.commons.io.FileUtils; - import android.databinding.tool.reflection.ModelAnalyzer; import android.databinding.tool.reflection.ModelClass; import android.databinding.tool.reflection.SdkUtil; import android.databinding.tool.reflection.TypeUtil; import android.databinding.tool.util.L; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; + +import org.apache.commons.io.FileUtils; + import java.io.File; import java.io.IOException; import java.net.MalformedURLException; @@ -72,7 +72,7 @@ public class JavaAnalyzer extends ModelAnalyzer { } @Override - public ModelClass findClass(String className, Map<String, String> imports) { + public ModelClass findClassInternal(String className, Map<String, String> imports) { // TODO handle imports JavaClass loaded = mClassCache.get(className); if (loaded != null) { diff --git a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java index 0d00c574..b7b626c1 100644 --- a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java +++ b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java @@ -68,6 +68,11 @@ public class JavaMethod extends ModelMethod { } @Override + public boolean isProtected() { + return Modifier.isProtected(mMethod.getModifiers()); + } + + @Override public boolean isStatic() { return Modifier.isStatic(mMethod.getModifiers()); } diff --git a/compilerCommon/src/main/java/android/databinding/tool/processing/ErrorMessages.java b/compilerCommon/src/main/java/android/databinding/tool/processing/ErrorMessages.java index fa773745..746729ec 100644 --- a/compilerCommon/src/main/java/android/databinding/tool/processing/ErrorMessages.java +++ b/compilerCommon/src/main/java/android/databinding/tool/processing/ErrorMessages.java @@ -22,7 +22,7 @@ public class ErrorMessages { public static final String UNDEFINED_VARIABLE = "Identifiers must have user defined types from the XML file. %s is missing it"; public static final String CANNOT_FIND_SETTER_CALL = - "Cannot find the setter for attribute '%s' with parameter type %s."; + "Cannot find the setter for attribute '%s' with parameter type %s on %s."; public static final String CANNOT_RESOLVE_TYPE = "Cannot resolve type for %s"; public static final String MULTI_CONFIG_LAYOUT_CLASS_NAME_MISMATCH = diff --git a/databinding.properties b/databinding.properties index 95277f2f..77cf3068 100644 --- a/databinding.properties +++ b/databinding.properties @@ -1,6 +1,6 @@ # global settings for projects kotlinVersion = 1.0.0 -extensionsVersion = 1.0-rc5 +extensionsVersion = 1.1 # we use a public plugin so that it does not need data binding while compiling library androidPublicPluginVersion= 1.5.0 javaTargetCompatibility = 1.6 |