summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYigit Boyar <yboyar@google.com>2015-04-29 19:49:41 -0700
committerYigit Boyar <yboyar@google.com>2015-04-30 15:32:34 -0700
commite9b33bac04bb1ce1444d7f1744fcec1ecd3a57da (patch)
treedac2454bf8fae14152c532d36300407dfbf330f1
parentcffffe30fe53455856d3d41724b9d5dd21aebf9a (diff)
downloaddata-binding-e9b33bac04bb1ce1444d7f1744fcec1ecd3a57da.tar.gz
Support multi-param adapters in code generation
Bug: 19800022 Change-Id: I40c4ac72f24f965db12fd1c7dec6591184160ae5
-rw-r--r--compiler/src/main/java/android/databinding/tool/Binding.java17
-rw-r--r--compiler/src/main/java/android/databinding/tool/BindingTarget.java71
-rw-r--r--compiler/src/main/java/android/databinding/tool/LayoutBinder.java3
-rw-r--r--compiler/src/main/java/android/databinding/tool/MergedBinding.java84
-rw-r--r--compiler/src/main/java/android/databinding/tool/expr/ArgListExpr.java57
-rw-r--r--compiler/src/main/java/android/databinding/tool/expr/Expr.java7
-rw-r--r--compiler/src/main/java/android/databinding/tool/expr/ExprModel.java12
-rw-r--r--compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java2
-rw-r--r--compiler/src/main/java/android/databinding/tool/store/SetterStore.java39
-rw-r--r--compiler/src/main/kotlin/android/databinding/tool/writer/CodeGenUtil.kt113
-rw-r--r--compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt104
-rw-r--r--integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MultiArgAdapterEvaluationTest.java61
-rw-r--r--integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MultiArgAdapterTest.java74
-rw-r--r--integration-tests/TestApp/app/src/main/java/android/databinding/testapp/adapter/MultiArgTestAdapter.java103
-rw-r--r--integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/MultiAdapterSetterObj.java26
-rw-r--r--integration-tests/TestApp/app/src/main/res/layout/multi_arg_adapter_evaluation_test.xml50
-rw-r--r--integration-tests/TestApp/app/src/main/res/layout/multi_arg_adapter_test.xml66
17 files changed, 774 insertions, 115 deletions
diff --git a/compiler/src/main/java/android/databinding/tool/Binding.java b/compiler/src/main/java/android/databinding/tool/Binding.java
index f2bc96fe..657cd7bc 100644
--- a/compiler/src/main/java/android/databinding/tool/Binding.java
+++ b/compiler/src/main/java/android/databinding/tool/Binding.java
@@ -21,6 +21,7 @@ import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.store.SetterStore;
import android.databinding.tool.store.SetterStore.SetterCall;
+import android.databinding.tool.writer.CodeGenUtil;
public class Binding {
@@ -35,7 +36,7 @@ public class Binding {
mExpr = expr;
}
- private SetterStore.SetterCall getSetterCall() {
+ private SetterStore.BindingSetterCall getSetterCall() {
if (mSetterCall == null) {
ModelClass viewType = mTarget.getResolvedType();
if (viewType != null && viewType.extendsViewStub()) {
@@ -56,8 +57,9 @@ public class Binding {
return mTarget;
}
- public String toJavaCode(String targetViewName, String expressionCode) {
- return getSetterCall().toJava(targetViewName, expressionCode);
+ public String toJavaCode(String targetViewName) {
+ String argCode = CodeGenUtil.Companion.toCode(getExpr(), false).generate();
+ return getSetterCall().toJava(targetViewName, argCode);
}
/**
@@ -70,15 +72,6 @@ public class Binding {
return getSetterCall().getMinApi();
}
-// private String resolveJavaCode(ModelAnalyzer modelAnalyzer) {
-//
-// }
-//// return modelAnalyzer.findMethod(mTarget.getResolvedType(), mName,
-//// Arrays.asList(mExpr.getResolvedType()));
-// //}
-//
-
-
public String getName() {
return mName;
}
diff --git a/compiler/src/main/java/android/databinding/tool/BindingTarget.java b/compiler/src/main/java/android/databinding/tool/BindingTarget.java
index bc52a2a1..da2c5036 100644
--- a/compiler/src/main/java/android/databinding/tool/BindingTarget.java
+++ b/compiler/src/main/java/android/databinding/tool/BindingTarget.java
@@ -16,15 +16,24 @@
package android.databinding.tool;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
import android.databinding.tool.expr.Expr;
import android.databinding.tool.expr.ExprModel;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.store.ResourceBundle;
import android.databinding.tool.store.SetterStore;
+import android.databinding.tool.util.L;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class BindingTarget {
List<Binding> mBindings = new ArrayList<Binding>();
@@ -99,4 +108,66 @@ public class BindingTarget {
public void setModel(ExprModel model) {
mModel = model;
}
+
+ /**
+ * Called after BindingTarget is finalized.
+ * <p>
+ * We traverse all bindings and ask SetterStore to figure out if any can be combined.
+ * When N bindings are combined, they are demoted from being a binding expression and a new
+ * ArgList expression is added as the new binding expression that depends on others.
+ */
+ public void resolveMultiSetters() {
+ L.d("resolving multi setters for %s", getId());
+ final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance());
+ final String[] attributes = new String[mBindings.size()];
+ final ModelClass[] types = new ModelClass[mBindings.size()];
+ for (int i = 0; i < mBindings.size(); i ++) {
+ Binding binding = mBindings.get(i);
+ attributes[i] = binding.getName();
+ types[i] = binding.getExpr().getResolvedType();
+ }
+ final List<SetterStore.MultiAttributeSetter> multiAttributeSetterCalls = setterStore
+ .getMultiAttributeSetterCalls(attributes, getResolvedType(), types);
+ if (multiAttributeSetterCalls.isEmpty()) {
+ return;
+ }
+ final Map<String, Binding> lookup = new HashMap<String, Binding>();
+ for (Binding binding : mBindings) {
+ String name = binding.getName();
+ if (name.startsWith("android:")) {
+ lookup.put(name, binding);
+ } else {
+ int ind = name.indexOf(":");
+ if (ind == -1) {
+ lookup.put(name, binding);
+ } else {
+ lookup.put(name.substring(ind + 1), binding);
+ }
+ }
+ }
+ List<MergedBinding> mergeBindings = new ArrayList<MergedBinding>();
+ for (final SetterStore.MultiAttributeSetter setter : multiAttributeSetterCalls) {
+ L.d("resolved %s", setter);
+ final Binding[] mergedBindings = Iterables.toArray(
+ Iterables.transform(Arrays.asList(setter.attributes),
+ new Function<String, Binding>() {
+ @Override
+ public Binding apply(final String attribute) {
+ L.d("looking for binding for attribute %s", attribute);
+ return lookup.get(attribute);
+ }
+ }), Binding.class) ;
+ Preconditions.checkArgument(mergedBindings.length == setter.attributes.length);
+ for (Binding binding : mergedBindings) {
+ binding.getExpr().setBindingExpression(false);
+ mBindings.remove(binding);
+ }
+ MergedBinding mergedBinding = new MergedBinding(getModel(), setter, this,
+ Arrays.asList(mergedBindings));
+ mergeBindings.add(mergedBinding);
+ }
+ for (MergedBinding binding : mergeBindings) {
+ mBindings.add(binding);
+ }
+ }
}
diff --git a/compiler/src/main/java/android/databinding/tool/LayoutBinder.java b/compiler/src/main/java/android/databinding/tool/LayoutBinder.java
index d8669271..669ed96e 100644
--- a/compiler/src/main/java/android/databinding/tool/LayoutBinder.java
+++ b/compiler/src/main/java/android/databinding/tool/LayoutBinder.java
@@ -22,8 +22,10 @@ import android.databinding.tool.expr.Dependency;
import android.databinding.tool.expr.Expr;
import android.databinding.tool.expr.ExprModel;
import android.databinding.tool.expr.IdentifierExpr;
+import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.store.ResourceBundle;
import android.databinding.tool.store.ResourceBundle.BindingTargetBundle;
+import android.databinding.tool.store.SetterStore;
import android.databinding.tool.util.ParserHelper;
import android.databinding.tool.writer.LayoutBinderWriter;
import android.databinding.tool.writer.WriterPackage;
@@ -81,6 +83,7 @@ public class LayoutBinder {
.getBindingBundleList()) {
bindingTarget.addBinding(bindingBundle.getName(), parse(bindingBundle.getExpr()));
}
+ bindingTarget.resolveMultiSetters();
}
mSortedBindingTargets = new ArrayList<BindingTarget>(mBindingTargets);
Collections.sort(mSortedBindingTargets, COMPARE_FIELD_NAME);
diff --git a/compiler/src/main/java/android/databinding/tool/MergedBinding.java b/compiler/src/main/java/android/databinding/tool/MergedBinding.java
new file mode 100644
index 00000000..174f89da
--- /dev/null
+++ b/compiler/src/main/java/android/databinding/tool/MergedBinding.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.databinding.tool.expr.ArgListExpr;
+import android.databinding.tool.expr.Expr;
+import android.databinding.tool.expr.ExprModel;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.store.SetterStore;
+import android.databinding.tool.util.L;
+import android.databinding.tool.writer.CodeGenUtil;
+
+import java.util.List;
+
+/**
+ * Multiple binding expressions can be evaluated using a single adapter. In those cases,
+ * we replace the Binding with a MergedBinding.
+ */
+public class MergedBinding extends Binding {
+ private final SetterStore.MultiAttributeSetter mMultiAttributeSetter;
+ public MergedBinding(ExprModel model, SetterStore.MultiAttributeSetter multiAttributeSetter,
+ BindingTarget target, Iterable<Binding> bindings) {
+ super(target, createMergedName(bindings), createArgListExpr(model, bindings));
+ mMultiAttributeSetter = multiAttributeSetter;
+ }
+
+ private static Expr createArgListExpr(ExprModel model, final Iterable<Binding> bindings) {
+ Expr expr = model.argListExpr(Iterables.transform(bindings, new Function<Binding, Expr>() {
+ @Override
+ public Expr apply(Binding input) {
+ return input.getExpr();
+ }
+ }));
+ expr.setBindingExpression(true);
+ return expr;
+ }
+
+ private static String createMergedName(Iterable<Binding> bindings) {
+ return Iterables.toString(Iterables.transform(bindings, new Function<Binding, String>() {
+ @Override
+ public String apply(Binding input) {
+ return input.getName();
+ }
+ }));
+ }
+
+ @Override
+ public int getMinApi() {
+ return 1;
+ }
+
+ @Override
+ public String toJavaCode(String targetViewName) {
+ ArgListExpr args = (ArgListExpr) getExpr();
+ final String[] expressions = Iterables
+ .toArray(Iterables.transform(args.getChildren(), new Function<Expr, String>() {
+ @Override
+ public String apply(Expr input) {
+ return CodeGenUtil.Companion.toCode(input, false).generate();
+ }
+ }), String.class);
+ L.d("merged binding arg: %s", args.getUniqueKey());
+ return mMultiAttributeSetter.toJava(targetViewName, expressions);
+ }
+}
diff --git a/compiler/src/main/java/android/databinding/tool/expr/ArgListExpr.java b/compiler/src/main/java/android/databinding/tool/expr/ArgListExpr.java
new file mode 100644
index 00000000..91ae6495
--- /dev/null
+++ b/compiler/src/main/java/android/databinding/tool/expr/ArgListExpr.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.List;
+
+/**
+ * This is a special expression that is created when we have an adapter that has multiple
+ * parameters.
+ * <p>
+ * When it is detected, we create a new binding with this argument list expression and merge N
+ * bindings into a new one so that rest of the code generation logic works as expected.
+ */
+public class ArgListExpr extends Expr {
+ private int mId;
+ public ArgListExpr(int id, Iterable<Expr> children) {
+ super(children);
+ mId = id;
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ return "ArgList[" + mId + "]" + super.computeUniqueKey();
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ return modelAnalyzer.findClass(Void.class);
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return super.constructDynamicChildrenDependencies();
+ }
+
+ @Override
+ public boolean canBeEvaluatedToAVariable() {
+ return false;
+ }
+}
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 84f16404..7e3849c0 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/Expr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/Expr.java
@@ -25,6 +25,7 @@ import com.google.common.collect.Lists;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.writer.KCode;
import java.util.ArrayList;
import java.util.BitSet;
@@ -144,6 +145,10 @@ abstract public class Expr {
return mIsBindingExpression;
}
+ public boolean canBeEvaluatedToAVariable() {
+ return true; // anything except arg expr can be evaluated to a variable
+ }
+
public boolean isObservable() {
return getResolvedType().isObservable();
}
@@ -199,7 +204,7 @@ abstract public class Expr {
for (Dependency dependency : getDependants()) {
final boolean isElevated = unreadElevatedCheck.apply(dependency);
if (dependency.isConditional()) {
- continue; // TODO
+ continue; // will be resolved later when conditional is elevated
}
if (isElevated) {
// if i already have all flags that will require my dependant's predicate to
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 37455452..0847e287 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java
@@ -42,6 +42,9 @@ public class ExprModel {
private int mRequirementIdCount = 0;
+ // each arg list receives a unique id even if it is the same arguments and method.
+ private int mArgListIdCounter = 0;
+
private static final String TRUE_KEY_SUFFIX = "== true";
private static final String FALSE_KEY_SUFFIX = "== false";
@@ -67,6 +70,8 @@ public class ExprModel {
private List<Expr> mObservables;
+ private boolean mSealed = false;
+
private Map<String, String> mImports = new HashMap<String, String>();
/**
@@ -77,6 +82,7 @@ public class ExprModel {
* @return The expression itself or another one if the same thing was parsed before
*/
public <T extends Expr> T register(T expr) {
+ Preconditions.checkState(!mSealed, "Cannot add expressions to a model after it is sealed");
T existing = (T) mExprMap.get(expr.getUniqueKey());
if (existing != null) {
Preconditions.checkState(expr.getParents().isEmpty(),
@@ -371,7 +377,7 @@ public class ExprModel {
expr.getResolvedType();
}
-
+ mSealed = true;
}
public int getFlagBucketCount() {
@@ -529,4 +535,8 @@ public class ExprModel {
public BitSet getInvalidateAnyBitSet() {
return mInvalidateAnyFlags;
}
+
+ public Expr argListExpr(Iterable<Expr> expressions) {
+ return register(new ArgListExpr(mArgListIdCounter ++, expressions));
+ }
}
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 ad0d948a..24e62323 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
@@ -105,7 +105,7 @@ public class FieldAccessExpr extends Expr {
child.resolveType(modelAnalyzer);
boolean isStatic = child instanceof StaticIdentifierExpr;
ModelClass resolvedType = child.getResolvedType();
- L.d("resolving %s. Resolved type: %s", this, resolvedType);
+ L.d("resolving %s. Resolved class type: %s", this, resolvedType);
mGetter = resolvedType.findGetterOrField(mName, isStatic);
if (mGetter.resolvedType.isObservableField()) {
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 0066ca15..2b2442e5 100644
--- a/compiler/src/main/java/android/databinding/tool/store/SetterStore.java
+++ b/compiler/src/main/java/android/databinding/tool/store/SetterStore.java
@@ -857,7 +857,13 @@ public class SetterStore {
}
}
- public static abstract class SetterCall {
+ public static interface BindingSetterCall {
+ String toJava(String viewExpression, String... valueExpressions);
+
+ int getMinApi();
+ }
+
+ public static abstract class SetterCall implements BindingSetterCall {
private MethodDescription mConverter;
protected String mCastString = "";
@@ -870,8 +876,10 @@ public class SetterStore {
protected abstract String toJavaInternal(String viewExpression, String converted);
- public final String toJava(String viewExpression, String valueExpression) {
- return toJavaInternal(viewExpression, convertValue(valueExpression));
+ @Override
+ public final String toJava(String viewExpression, String... valueExpression) {
+ Preconditions.checkArgument(valueExpression.length == 1);
+ return toJavaInternal(viewExpression, convertValue(valueExpression[0]));
}
protected String convertValue(String valueExpression) {
@@ -886,7 +894,7 @@ public class SetterStore {
}
}
- public static class MultiAttributeSetter {
+ public static class MultiAttributeSetter implements BindingSetterCall {
public final String[] attributes;
private final MethodDescription mAdapter;
private final MethodDescription[] mConverters;
@@ -905,8 +913,11 @@ public class SetterStore {
this.mKey = key;
}
- public String toJava(String viewExpression, String[] valueExpressions) {
- Preconditions.checkArgument(valueExpressions.length == attributes.length);
+ @Override
+ public final String toJava(String viewExpression, String[] valueExpressions) {
+ Preconditions.checkArgument(valueExpressions.length == attributes.length,
+ "MultiAttributeSetter needs %s items, received %s",
+ Arrays.toString(attributes), Arrays.toString(valueExpressions));
StringBuilder sb = new StringBuilder();
sb.append(mAdapter.type)
.append('.')
@@ -935,5 +946,21 @@ public class SetterStore {
sb.append(')');
return sb.toString();
}
+
+ @Override
+ public int getMinApi() {
+ return 1;
+ }
+
+ @Override
+ public String toString() {
+ return "MultiAttributeSetter{" +
+ "attributes=" + Arrays.toString(attributes) +
+ ", mAdapter=" + mAdapter +
+ ", mConverters=" + Arrays.toString(mConverters) +
+ ", mCasts=" + Arrays.toString(mCasts) +
+ ", mKey=" + mKey +
+ '}';
+ }
}
}
diff --git a/compiler/src/main/kotlin/android/databinding/tool/writer/CodeGenUtil.kt b/compiler/src/main/kotlin/android/databinding/tool/writer/CodeGenUtil.kt
new file mode 100644
index 00000000..4ddfe88a
--- /dev/null
+++ b/compiler/src/main/kotlin/android/databinding/tool/writer/CodeGenUtil.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.writer
+
+import android.databinding.tool.expr.*
+import android.databinding.tool.reflection.Callable
+import android.databinding.tool.writer.KCode
+import android.databinding.tool.writer.kcode
+
+class CodeGenUtil {
+ companion object {
+ fun toCode(it : Expr, full : Boolean) : KCode {
+ if (it.isDynamic() && !full) {
+ return kcode(it.executePendingLocalName)
+ }
+ return when (it) {
+ is ComparisonExpr -> kcode("") {
+ app("", it.getLeft().toCode())
+ app(it.getOp())
+ app("", it.getRight().toCode())
+ }
+ is InstanceOfExpr -> kcode("") {
+ app("", it.getExpr().toCode())
+ app(" instanceof ")
+ app("", it.getType().toJavaCode())
+ }
+ is FieldAccessExpr -> kcode("") {
+ app("", it.getChild().toCode())
+ if (it.getGetter().type == Callable.Type.FIELD) {
+ app(".", it.getGetter().name)
+ } else {
+ app(".", it.getGetter().name).app("()")
+ }
+ }
+ is GroupExpr -> kcode("(").app("", it.getWrapped().toCode()).app(")")
+ is StaticIdentifierExpr -> kcode(it.getResolvedType().toJavaCode())
+ is IdentifierExpr -> kcode(it.executePendingLocalName)
+ is MathExpr -> kcode("") {
+ app("", it.getLeft().toCode())
+ app(it.getOp())
+ app("", it.getRight().toCode())
+ }
+ is UnaryExpr -> kcode("") {
+ app(it.getOp(), it.getExpr().toCode())
+ }
+ is BitShiftExpr -> kcode("") {
+ app("", it.getLeft().toCode())
+ app(it.getOp())
+ app("", it.getRight().toCode())
+ }
+ is MethodCallExpr -> kcode("") {
+ app("", it.getTarget().toCode())
+ app(".", it.getGetter().name)
+ app("(")
+ var first = true
+ it.getArgs().forEach {
+ apps(if (first) "" else ",", it.toCode())
+ first = false
+ }
+ app(")")
+ }
+ is SymbolExpr -> kcode(it.getText()) // TODO
+ is TernaryExpr -> kcode("") {
+ app("", it.getPred().toCode())
+ app("?", it.getIfTrue().toCode())
+ app(":", it.getIfFalse().toCode())
+ }
+ is ResourceExpr -> kcode("") {
+ app("", it.toJava())
+ }
+ is BracketExpr -> kcode("") {
+ app("", it.getTarget().toCode())
+ val bracketType = it.getAccessor();
+ when (bracketType) {
+ BracketExpr.BracketAccessor.ARRAY -> {
+ app("[", it.getArg().toCode())
+ app("]")
+ }
+ BracketExpr.BracketAccessor.LIST -> {
+ app(".get(")
+ if (it.argCastsInteger()) {
+ app("(Integer)")
+ }
+ app("", it.getArg().toCode())
+ app(")")
+ }
+ BracketExpr.BracketAccessor.MAP -> {
+ app(".get(", it.getArg().toCode())
+ app(")")
+ }
+ }
+ }
+ is CastExpr -> kcode("") {
+ app("(", it.getCastType())
+ app(") ", it.getCastExpr().toCode())
+ }
+ is ArgListExpr -> throw IllegalStateException("should never try to convert an argument expressions into code");
+ else -> kcode("//NOT IMPLEMENTED YET")
+ }
+ }
+ }
+} \ No newline at end of file
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 77605c3f..01d5f277 100644
--- a/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
+++ b/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
@@ -14,6 +14,8 @@
package android.databinding.tool.writer
import android.databinding.tool.LayoutBinder
+import kotlin.properties.Delegates
+import android.databinding.tool.BindingTarget
import android.databinding.tool.expr.Expr
import kotlin.properties.Delegates
import android.databinding.tool.ext.joinToCamelCaseAsVar
@@ -39,8 +41,11 @@ import android.databinding.tool.ext.lazy
import android.databinding.tool.ext.br
import android.databinding.tool.expr.ResourceExpr
import android.databinding.tool.expr.BracketExpr
+import android.databinding.tool.ext;
+import android.databinding.tool.util.Log
+import java.util.BitSet
+import java.util.Arrays
import android.databinding.tool.reflection.Callable
-import android.databinding.tool.expr.CastExpr
import android.databinding.tool.reflection.ModelAnalyzer
import java.util.ArrayList
import java.util.HashMap
@@ -167,96 +172,7 @@ val Expr.dirtyFlagName by Delegates.lazy { expr : Expr ->
}
-fun Expr.toCode(full : Boolean = false) : KCode {
- val it = this
- if (isDynamic() && !full) {
- return kcode(executePendingLocalName)
- }
- return when (it) {
- is ComparisonExpr -> kcode("") {
- app("", it.getLeft().toCode())
- app(it.getOp())
- app("", it.getRight().toCode())
- }
- is InstanceOfExpr -> kcode("") {
- app("", it.getExpr().toCode())
- app(" instanceof ")
- app("", it.getType().toJavaCode())
- }
- is FieldAccessExpr -> kcode("") {
- app("", it.getChild().toCode())
- if (it.getGetter().type == Callable.Type.FIELD) {
- app(".", it.getGetter().name)
- } else {
- app(".", it.getGetter().name).app("()")
- }
- }
- is GroupExpr -> kcode("(").app("", it.getWrapped().toCode()).app(")")
- is StaticIdentifierExpr -> kcode(it.getResolvedType().toJavaCode())
- is IdentifierExpr -> kcode(it.executePendingLocalName)
- is MathExpr -> kcode("") {
- app("", it.getLeft().toCode())
- app(it.getOp())
- app("", it.getRight().toCode())
- }
- is UnaryExpr -> kcode("") {
- app(it.getOp(), it.getExpr().toCode())
- }
- is BitShiftExpr -> kcode("") {
- app("", it.getLeft().toCode())
- app(it.getOp())
- app("", it.getRight().toCode())
- }
- is MethodCallExpr -> kcode("") {
- app("", it.getTarget().toCode())
- app(".", it.getGetter().name)
- app("(")
- var first = true
- it.getArgs().forEach {
- apps(if (first) "" else ",", it.toCode())
- first = false
- }
- app(")")
- }
- is SymbolExpr -> kcode(it.getText()) // TODO
- is TernaryExpr -> kcode("") {
- app("", it.getPred().toCode())
- app("?", it.getIfTrue().toCode())
- app(":", it.getIfFalse().toCode())
- }
- is ResourceExpr -> kcode("") {
- app("", it.toJava())
- }
- is BracketExpr -> kcode("") {
- app("", it.getTarget().toCode())
- val bracketType = it.getAccessor();
- when (bracketType) {
- BracketExpr.BracketAccessor.ARRAY -> {
- app("[", it.getArg().toCode())
- app("]")
- }
- BracketExpr.BracketAccessor.LIST -> {
- app(".get(")
- if (it.argCastsInteger()) {
- app("(Integer)")
- }
- app("", it.getArg().toCode())
- app(")")
- }
- BracketExpr.BracketAccessor.MAP -> {
- app(".get(", it.getArg().toCode())
- app(")")
- }
- }
- }
- is CastExpr -> kcode("") {
- app("(", it.getCastType())
- app(") ", it.getCastExpr().toCode())
- }
- else -> kcode("//NOT IMPLEMENTED YET")
- }
-
-}
+fun Expr.toCode(full : Boolean = false) : KCode = CodeGenUtil.toCode(this, full)
fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic()
@@ -730,7 +646,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
tab("${mDirtyFlags.localValue(i)} = 0;")
}
} tab("}")
- model.getPendingExpressions().filterNot {it.isVariable()}.forEach {
+ model.getPendingExpressions().filterNot {!it.canBeEvaluatedToAVariable() || it.isVariable()}.forEach {
tab("${it.getResolvedType().toJavaCode()} ${it.executePendingLocalName} = ${it.getDefaultValue()};")
}
Log.d {"writing executePendingBindings for $className"}
@@ -772,7 +688,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
} else {
fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
}
- val bindingCode = binding.toJavaCode(fieldName, binding.getExpr().toCode().generate())
+ val bindingCode = binding.toJavaCode(fieldName)
if (binding.getMinApi() > 1) {
tab("if(getBuildSdkInt() >= ${binding.getMinApi()}) {") {
tab("$bindingCode;")
@@ -813,7 +729,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
})"
val readCode = kcode("") {
- if (!expr.isVariable()) {
+ if (expr.canBeEvaluatedToAVariable() && !expr.isVariable()) {
// it is not a variable read it.
tab("// read ${expr.getUniqueKey()}")
// create an if case for all dependencies that might be null
diff --git a/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MultiArgAdapterEvaluationTest.java b/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MultiArgAdapterEvaluationTest.java
new file mode 100644
index 00000000..ac0a8ffe
--- /dev/null
+++ b/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MultiArgAdapterEvaluationTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.BR;
+import android.databinding.testapp.databinding.MultiArgAdapterEvaluationTestBinding;
+import android.databinding.testapp.databinding.MultiArgAdapterTestBinding;
+import android.test.UiThreadTest;
+
+import static android.databinding.testapp.adapter.MultiArgTestAdapter.MultiBindingClass1;
+import static android.databinding.testapp.adapter.MultiArgTestAdapter.MultiBindingClass2;
+import static android.databinding.testapp.adapter.MultiArgTestAdapter.join;
+
+public class MultiArgAdapterEvaluationTest extends BaseDataBinderTest<MultiArgAdapterEvaluationTestBinding> {
+
+ public MultiArgAdapterEvaluationTest() {
+ super(MultiArgAdapterEvaluationTestBinding.class);
+ }
+
+ @UiThreadTest
+ public void testMultiArgIsCalled() {
+ MultiBindingClass1 obj1 = new MultiBindingClass1();
+ MultiBindingClass2 obj2 = new MultiBindingClass2();
+ obj1.setValue("a", false);
+ obj2.setValue("b", false);
+ mBinder.setObj1(obj1);
+ mBinder.setObj2(obj2);
+ mBinder.executePendingBindings();
+
+ assertEquals(mBinder.merged.getText().toString(), join(obj1.getValue(), obj2.getValue()));
+ assertEquals(mBinder.view2.getText().toString(), join(obj2.getValue()));
+ assertEquals(mBinder.view2text.getText().toString(), obj2.getValue());
+
+ String prev2 = mBinder.view2.getText().toString();
+ String prevValue = mBinder.merged.getText().toString();
+ obj1.setValue("o", false);
+ mBinder.executePendingBindings();
+ assertEquals(prevValue, mBinder.merged.getText().toString());
+ obj2.setValue("p", false);
+ mBinder.executePendingBindings();
+ assertEquals(prevValue, mBinder.merged.getText().toString());
+ // now invalidate obj1 only, obj2 should be evaluated as well
+ obj1.setValue("o2", true);
+ mBinder.executePendingBindings();
+ assertEquals(join(obj1, obj2), mBinder.merged.getText().toString());
+ assertEquals("obj2 should not be re-evaluated", prev2, mBinder.view2.getText().toString());
+ assertEquals("obj2 should not be re-evaluated", prev2,
+ mBinder.view2text.getText().toString());
+ }
+}
diff --git a/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MultiArgAdapterTest.java b/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MultiArgAdapterTest.java
new file mode 100644
index 00000000..d117b9a4
--- /dev/null
+++ b/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MultiArgAdapterTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.adapter.MultiArgTestAdapter;
+import android.databinding.testapp.databinding.MultiArgAdapterTestBinding;
+import android.test.UiThreadTest;
+import android.view.View;
+import android.databinding.testapp.BR;
+import static android.databinding.testapp.adapter.MultiArgTestAdapter.*;
+
+public class MultiArgAdapterTest extends BaseDataBinderTest<MultiArgAdapterTestBinding> {
+
+ public MultiArgAdapterTest() {
+ super(MultiArgAdapterTestBinding.class);
+ }
+
+ @UiThreadTest
+ public void testMultiArgIsCalled() {
+ MultiBindingClass1 obj1 = new MultiBindingClass1();
+ MultiBindingClass2 obj2 = new MultiBindingClass2();
+ MultiBindingClass1 obj3 = new MultiBindingClass1();
+ MultiBindingClass2 obj4 = new MultiBindingClass2();
+ obj1.setValue("a", false);
+ obj2.setValue("b", false);
+ obj3.setValue("c", false);
+ obj4.setValue("d", false);
+ mBinder.setObj1(obj1);
+ mBinder.setObj2(obj2);
+ mBinder.setObj3(obj3);
+ mBinder.setObj4(obj4);
+ mBinder.executePendingBindings();
+
+ assertEquals(mBinder.merged.getText().toString(), join(obj1, obj2));
+ assertEquals(mBinder.view2.getText().toString(), join(obj2));
+ assertEquals(mBinder.view3.getText().toString(), join(obj3));
+ assertEquals(mBinder.view4.getText().toString(), join(obj4));
+ String prev2 = mBinder.view2.getText().toString();
+ String prevValue = mBinder.merged.getText().toString();
+ obj1.setValue("o", false);
+ mBinder.executePendingBindings();
+ assertEquals(prevValue, mBinder.merged.getText().toString());
+ obj2.setValue("p", false);
+ mBinder.executePendingBindings();
+ assertEquals(prevValue, mBinder.merged.getText().toString());
+ // now invalidate obj1 only, obj2 should be evaluated as well
+ obj1.notifyPropertyChanged(BR._all);
+ String prev3 = mBinder.view3.getText().toString();
+ String prev4 = mBinder.view4.getText().toString();
+ obj3.setValue("q", false);
+ obj4.setValue("r", false);
+ mBinder.executePendingBindings();
+ assertEquals(join(obj1, obj2), mBinder.merged.getText().toString());
+ assertEquals("obj2 should not be re-evaluated", prev2, mBinder.view2.getText().toString());
+ // make sure 3 and 4 are not invalidated
+ assertEquals("obj3 should not be re-evaluated", prev3, mBinder.view3.getText().toString());
+ assertEquals("obj4 should not be re-evaluated", prev4, mBinder.view4.getText().toString());
+
+
+ }
+
+
+}
diff --git a/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/adapter/MultiArgTestAdapter.java b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/adapter/MultiArgTestAdapter.java
new file mode 100644
index 00000000..a2702798
--- /dev/null
+++ b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/adapter/MultiArgTestAdapter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp.adapter;
+
+
+import android.databinding.BaseObservable;
+import android.databinding.Bindable;
+import android.databinding.BindingAdapter;
+import android.view.View;
+import android.databinding.testapp.BR;
+import android.widget.TextView;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class MultiArgTestAdapter {
+
+ public static String join(BaseMultiBindingClass... classes) {
+ StringBuilder sb = new StringBuilder();
+ for(BaseMultiBindingClass instance : classes) {
+ sb.append(instance == null ? "??" : instance.getValue());
+ }
+ return sb.toString();
+ }
+
+ public static String join(String... strings) {
+ StringBuilder sb = new StringBuilder();
+ for(String str : strings) {
+ sb.append(str == null ? "??" : str);
+ }
+ return sb.toString();
+
+ }
+
+ @BindingAdapter(attributes={"android:class1", "android:class2"})
+ public static void setBoth(TextView view, MultiBindingClass1 class1,
+ MultiBindingClass2 class2) {
+ view.setText(join(class1, class2));
+ }
+
+ @BindingAdapter(attributes={"android:class1str", "android:class2str"})
+ public static void setBoth(TextView view, String str1,
+ String str2) {
+ view.setText(join(str1, str2));
+ }
+
+ @BindingAdapter(attributes={"android:class1"})
+ public static void setClass1(TextView view, MultiBindingClass1 class1) {
+ view.setText(class1.getValue());
+ }
+
+ @BindingAdapter(attributes={"android:classStr"})
+ public static void setClassStr(TextView view, String str) {
+ view.setText(str);
+ }
+
+ @BindingAdapter(attributes={"android:class2"})
+ public static void setClass2(TextView view, MultiBindingClass2 class2) {
+ view.setText(class2.getValue());
+ }
+
+ public static class MultiBindingClass1 extends BaseMultiBindingClass {
+
+ }
+
+ public static class MultiBindingClass2 extends BaseMultiBindingClass {
+
+ }
+
+ public static class BaseMultiBindingClass extends BaseObservable {
+ View mSetOn;
+ @Bindable
+ String mValue;
+ public View getSetOn() {
+ return mSetOn;
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+
+ public void setValue(String value, boolean notify) {
+ mValue = value;
+ if (notify) {
+ notifyPropertyChanged(BR.value);
+ }
+ }
+
+ public void clear() {
+ mSetOn = null;
+ }
+ }
+}
diff --git a/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/MultiAdapterSetterObj.java b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/MultiAdapterSetterObj.java
new file mode 100644
index 00000000..c23a41a8
--- /dev/null
+++ b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/MultiAdapterSetterObj.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp.vo;
+
+import android.view.View;
+
+public class MultiAdapterSetterObj {
+ public boolean isClickable;
+ public View.OnClickListener onClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+
+ }
+ };
+}
diff --git a/integration-tests/TestApp/app/src/main/res/layout/multi_arg_adapter_evaluation_test.xml b/integration-tests/TestApp/app/src/main/res/layout/multi_arg_adapter_evaluation_test.xml
new file mode 100644
index 00000000..2fbe6a73
--- /dev/null
+++ b/integration-tests/TestApp/app/src/main/res/layout/multi_arg_adapter_evaluation_test.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res-auto">
+
+ <data>
+
+ <variable
+ name="obj1"
+ type="android.databinding.testapp.adapter.MultiArgTestAdapter.MultiBindingClass1" />
+
+ <variable
+ name="obj2"
+ type="android.databinding.testapp.adapter.MultiArgTestAdapter.MultiBindingClass2" />
+ </data>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/merged"
+ android:class1str="@{obj1.value}"
+ android:class2str="@{obj2.value}" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/view2"
+ android:text="@{obj2.value}" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/view2text"
+ android:classStr="@{obj2.value}" />
+ </LinearLayout>
+</layout> \ No newline at end of file
diff --git a/integration-tests/TestApp/app/src/main/res/layout/multi_arg_adapter_test.xml b/integration-tests/TestApp/app/src/main/res/layout/multi_arg_adapter_test.xml
new file mode 100644
index 00000000..fd8d4411
--- /dev/null
+++ b/integration-tests/TestApp/app/src/main/res/layout/multi_arg_adapter_test.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2015 The Android Open Source Project
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res-auto">
+
+ <data>
+
+ <variable
+ name="obj1"
+ type="android.databinding.testapp.adapter.MultiArgTestAdapter.MultiBindingClass1" />
+
+ <variable
+ name="obj2"
+ type="android.databinding.testapp.adapter.MultiArgTestAdapter.MultiBindingClass2" />
+
+ <variable
+ name="obj3"
+ type="android.databinding.testapp.adapter.MultiArgTestAdapter.MultiBindingClass1" />
+
+ <variable
+ name="obj4"
+ type="android.databinding.testapp.adapter.MultiArgTestAdapter.MultiBindingClass2" />
+ </data>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/merged"
+ android:class1="@{obj1}"
+ android:class2="@{obj2}" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/view3"
+ android:class1="@{obj3}" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/view4"
+ android:class2="@{obj4}" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/view2"
+ android:class2="@{obj2}" />
+ </LinearLayout>
+</layout> \ No newline at end of file