Support static method and field access and improve method finding.

Bug 19425630
Bug 19336295

Change-Id: I4c04db32492edfa093e94c3c15bf7799128b1e03
This commit is contained in:
George Mount
2015-02-18 17:20:03 -08:00
parent 371450b5b1
commit 0e7ca4055e
14 changed files with 375 additions and 73 deletions

View File

@@ -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 com.android.databinding.testapp;
import com.android.databinding.testapp.generated.FindMethodTestBinder;
import com.android.databinding.testapp.vo.FindMethodBindingObject;
import android.widget.TextView;
public class FindMethodTest
extends BindingAdapterTestBase<FindMethodTestBinder, FindMethodBindingObject> {
public FindMethodTest() {
super(FindMethodTestBinder.class, FindMethodBindingObject.class, R.layout.find_method_test);
}
public void testNoArg() throws Throwable {
TextView textView = mBinder.getTextView6();
assertEquals("no arg", textView.getText().toString());
}
public void testIntArg() throws Throwable {
TextView textView = mBinder.getTextView0();
assertEquals("1", textView.getText().toString());
}
public void testFloatArg() throws Throwable {
TextView textView = mBinder.getTextView1();
assertEquals("1.25", textView.getText().toString());
}
public void testStringArg() throws Throwable {
TextView textView = mBinder.getTextView2();
assertEquals("hello", textView.getText().toString());
}
public void testBoxedArg() throws Throwable {
TextView textView = mBinder.getTextView3();
assertEquals("1", textView.getText().toString());
}
public void testInheritedMethod() throws Throwable {
TextView textView = mBinder.getTextView4();
assertEquals("base", textView.getText().toString());
}
public void testInheritedMethodInt() throws Throwable {
TextView textView = mBinder.getTextView5();
assertEquals("base 2", textView.getText().toString());
}
public void testStaticMethod() throws Throwable {
TextView textView = mBinder.getTextView7();
assertEquals("world", textView.getText().toString());
}
public void testStaticField() throws Throwable {
TextView textView = mBinder.getTextView8();
assertEquals("hello world", textView.getText().toString());
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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 com.android.databinding.testapp.vo;
public class FindMethodBindingObject extends FindMethodBindingObjectBase {
public String method() { return "no arg"; }
public String method(int i) { return String.valueOf(i); }
public String method(float f) { return String.valueOf(f); }
public String method(String value) { return value; }
public static String staticMethod() { return "world"; }
public static Foo foo = new Foo();
public static class Foo {
public final String bar = "hello world";
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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 com.android.databinding.testapp.vo;
public class FindMethodBindingObjectBase extends BindingAdapterBindingObject {
public String inheritedMethod() {
return "base";
}
public String inheritedMethod(int i) {
return "base " + i;
}
@Override
public void changeValues() {
}
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<variable name="obj" type="com.android.databinding.testapp.vo.FindMethodBindingObject"/>
<!--
<import name="FMBO" type=""/>
-->
<TextView
android:id="@+id/textView0"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{obj.method(1)}"/>
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{obj.method(1.25f}"/>
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{obj.method(`hello`}"/>
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{obj.method((java.lang.Integer) 1)}"/>
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{obj.inheritedMethod()}"/>
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{obj.inheritedMethod(2)}"/>
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{obj.method()}"/>
<TextView
android:id="@+id/textView7"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{com.android.databinding.testapp.vo.FindMethodBindingObject.staticMethod()}"/>
<TextView
android:id="@+id/textView8"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{com.android.databinding.testapp.vo.FindMethodBindingObject.foo.bar}"/>
</LinearLayout>

View File

@@ -16,11 +16,6 @@
package com.android.databinding.expr;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
@@ -31,6 +26,11 @@ import com.google.common.collect.Lists;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
abstract public class Expr {
public static final int NO_ID = -1;
@@ -570,6 +570,41 @@ abstract public class Expr {
}
public void updateExpr(ModelAnalyzer modelAnalyzer) {
for (Expr child : mChildren) {
child.updateExpr(modelAnalyzer);
}
}
protected void replaceStaticAccess(ModelAnalyzer modelAnalyzer) {
for (int i = 0; i < mChildren.size(); i++) {
Expr child = mChildren.get(i);
String packageName = child.asPackage();
if (packageName != null) {
ModelClass modelClass = modelAnalyzer.findClass(packageName);
if (modelClass != null) {
Expr staticAccessExpr = getModel().staticAccessExpr(modelClass);
staticAccessExpr.getParents().add(this);
mChildren.set(i, staticAccessExpr);
child.removeParentAndUnregisterIfOrphan(this);
}
}
}
}
private void removeParentAndUnregisterIfOrphan(Expr parent) {
while (mParents.remove(parent)) {
}
if (getParents().isEmpty()) {
getModel().unregister(this);
for (Expr expr : mChildren) {
expr.removeParentAndUnregisterIfOrphan(this);
}
mChildren.clear();
}
}
protected String asPackage() {
return null;
}
static class Node {

View File

@@ -22,6 +22,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.util.L;
import com.android.databinding.writer.FlagSet;
@@ -85,6 +86,10 @@ public class ExprModel {
return expr;
}
public void unregister(Expr expr) {
mExprMap.remove(expr.getUniqueKey());
}
public Map<String, Expr> getExprMap() {
return mExprMap;
}
@@ -141,6 +146,10 @@ public class ExprModel {
return register(new BracketExpr(variableExpr, argExpr));
}
public Expr staticAccessExpr(ModelClass modelClass) {
return register(new StaticAccessExpr(modelClass));
}
public Expr castExpr(String type, Expr expr) {
return register(new CastExpr(type, expr));
}
@@ -197,17 +206,10 @@ public class ExprModel {
//ensure class analyzer. We need to know observables at this point
final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
ArrayList<Expr> processedExprs = new ArrayList<>();
ArrayList<Expr> exprs = new ArrayList<>();
do {
exprs.clear();
exprs.addAll(mExprMap.values());
exprs.removeAll(processedExprs);
for (Expr expr: exprs) {
expr.updateExpr(modelAnalyzer);
}
processedExprs.addAll(exprs);
} while (!exprs.isEmpty());
ArrayList<Expr> exprs = new ArrayList<>(mBindingExpressions);
for (Expr expr: exprs) {
expr.updateExpr(modelAnalyzer);
}
int counter = 0;
final Iterable<Expr> observables = filterObservables(modelAnalyzer);

View File

@@ -87,11 +87,19 @@ public class FieldAccessExpr extends Expr {
@Override
public void updateExpr(ModelAnalyzer modelAnalyzer) {
resolveType(modelAnalyzer);
super.updateExpr(modelAnalyzer);
}
@Override
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
if (mGetter == null) {
mGetter = modelAnalyzer.findMethodOrField(mChildren.get(0).getResolvedType(), mName);
replaceStaticAccess(modelAnalyzer);
Expr parent = getParent();
boolean isStatic = parent instanceof StaticAccessExpr;
mGetter = modelAnalyzer.findMethodOrField(parent.getResolvedType(), mName, isStatic);
if (modelAnalyzer.isObservableField(mGetter.resolvedType)) {
// Make this the ".get()" and add an extra field access for the observable field
Expr parent = getParent();
parent.getParents().remove(this);
getChildren().remove(parent);
@@ -100,17 +108,16 @@ public class FieldAccessExpr extends Expr {
getChildren().add(observableField);
observableField.getParents().add(this);
mGetter = modelAnalyzer.findMethodOrField(mGetter.resolvedType, "get");
mGetter = modelAnalyzer.findMethodOrField(mGetter.resolvedType, "get", false);
mName = "";
}
}
return mGetter.resolvedType;
}
@Override
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
if (mGetter == null) {
mGetter = modelAnalyzer.findMethodOrField(mChildren.get(0).getResolvedType(), mName);
}
return mGetter.resolvedType;
protected String asPackage() {
String parentPackage = getParent().asPackage();
return parentPackage == null ? null : parentPackage + "." + mName;
}
}

View File

@@ -68,4 +68,9 @@ public class IdentifierExpr extends Expr {
protected List<Dependency> constructDependencies() {
return Lists.newArrayList();
}
@Override
protected String asPackage() {
return mUserDefinedType == null ? mName : null;
}
}

View File

@@ -36,14 +36,24 @@ public class MethodCallExpr extends Expr {
mName = name;
}
@Override
public void updateExpr(ModelAnalyzer modelAnalyzer) {
resolveType(modelAnalyzer);
super.updateExpr(modelAnalyzer);
}
@Override
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
if (mGetter == null) {
replaceStaticAccess(modelAnalyzer);
List<ModelClass> args = new ArrayList<>();
for (Expr expr : getArgs()) {
args.add(expr.getResolvedType());
}
mGetter = modelAnalyzer.findMethod(getTarget().getResolvedType(), mName, args);
Expr target = getTarget();
boolean isStatic = target instanceof StaticAccessExpr;
mGetter = modelAnalyzer.findMethod(target.getResolvedType(), mName, args, isStatic);
}
return mGetter.resolvedType;
}

View File

@@ -0,0 +1,45 @@
/*
* 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 com.android.databinding.expr;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import java.util.ArrayList;
import java.util.List;
public class StaticAccessExpr extends Expr {
private ModelClass mStaticClass;
public StaticAccessExpr(ModelClass modelClass) {
mStaticClass = modelClass;
}
@Override
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
return mStaticClass;
}
@Override
protected List<Dependency> constructDependencies() {
return new ArrayList<>();
}
protected String computeUniqueKey() {
return mStaticClass.toJavaCode();
}
}

View File

@@ -124,31 +124,33 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
@Override
public Callable findMethod(ModelClass modelClass, String name,
List<ModelClass> args) {
List<ModelClass> args, boolean staticAccess) {
AnnotationClass clazz = (AnnotationClass)modelClass;
// TODO implement properly
for (String methodName : new String[]{"set" + StringUtils.capitalize(name), name}) {
for (ModelMethod method : clazz.getMethods(methodName, args.size())) {
ModelClass[] parameters = method.getParameterTypes();
boolean parametersMatch = true;
boolean isVarArgs = ((AnnotationMethod)method).mMethod.isVarArgs();
for (int i = 0; i < parameters.length; i++) {
if (isVarArgs && i == parameters.length - 1) {
ModelClass component = parameters[i].getComponentType();
for (int j = i; j < args.size(); j++) {
if (!component.isAssignableFrom(args.get(j))) {
parametersMatch = false;
break;
if (method.isStatic() == staticAccess) {
ModelClass[] parameters = method.getParameterTypes();
boolean parametersMatch = true;
boolean isVarArgs = ((AnnotationMethod) method).mMethod.isVarArgs();
for (int i = 0; i < parameters.length; i++) {
if (isVarArgs && i == parameters.length - 1) {
ModelClass component = parameters[i].getComponentType();
for (int j = i; j < args.size(); j++) {
if (!component.isAssignableFrom(args.get(j))) {
parametersMatch = false;
break;
}
}
} else if (!parameters[i].isAssignableFrom(args.get(i))) {
parametersMatch = false;
break;
}
} else if (!parameters[i].isAssignableFrom(args.get(i))) {
parametersMatch = false;
break;
}
}
if (parametersMatch) {
return new Callable(Callable.Type.METHOD, methodName,
method.getReturnType(args), true, false);
if (parametersMatch) {
return new Callable(Callable.Type.METHOD, methodName,
method.getReturnType(args), true, false);
}
}
}
}
@@ -190,7 +192,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
}
@Override
public Callable findMethodOrField(ModelClass modelClass, String name) {
public Callable findMethodOrField(ModelClass modelClass, String name, boolean staticAccess) {
AnnotationClass annotationClass = (AnnotationClass)modelClass;
for (String methodName :
new String[]{"get" + StringUtils.capitalize(name),
@@ -198,7 +200,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
ModelMethod[] methods = modelClass.getMethods(methodName, 0);
for (ModelMethod modelMethod : methods) {
AnnotationMethod method = (AnnotationMethod) modelMethod;
if (method.isPublic()) {
if (method.isPublic() && method.isStatic() == staticAccess) {
final AnnotationField backingField = findField(annotationClass, name, true);
final Callable result = new Callable(Callable.Type.METHOD, methodName,
method.getReturnType(null), true, isBindable(method) ||
@@ -210,7 +212,8 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
}
AnnotationField field = findField(annotationClass, name, false);
if (field != null && field.mField.getModifiers().contains(Modifier.PUBLIC)) {
if (field != null && field.mField.getModifiers().contains(Modifier.PUBLIC) &&
field.mField.getModifiers().contains(Modifier.STATIC) == staticAccess) {
AnnotationClass fieldType = new AnnotationClass(field.mField.asType());
return new Callable(Callable.Type.FIELD, name, fieldType,
!field.mField.getModifiers().contains(Modifier.FINAL)
@@ -288,15 +291,17 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
}
int templateOpenIndex = className.indexOf('<');
DeclaredType declaredType;
Elements elementUtils = getElementUtils();
if (templateOpenIndex < 0) {
Elements elementUtils = getElementUtils();
TypeElement typeElement = elementUtils.getTypeElement(className);
if (typeElement == null) {
return null;
}
declaredType = (DeclaredType) typeElement.asType();
} else {
int templateCloseIndex = className.lastIndexOf('>');
String paramStr = className.substring(templateOpenIndex + 1, templateCloseIndex);
Elements elementUtils = getElementUtils();
String baseClassName = className.substring(0, templateOpenIndex);
TypeElement typeElement = elementUtils.getTypeElement(baseClassName);

View File

@@ -105,20 +105,17 @@ public class AnnotationClass implements ModelClass {
", but it isn't a declared type: " + foundInterface);
return null;
}
System.err.println("found interface: " + foundInterface);
return (DeclaredType) foundInterface;
}
@Override
public boolean isList() {
AnnotationAnalyzer analyzer = AnnotationAnalyzer.instance;
Types typeUtil = getTypeUtils();
return typeUtil.isAssignable(typeUtil.erasure(mTypeMirror), getListType());
}
@Override
public boolean isMap() {
AnnotationAnalyzer analyzer = AnnotationAnalyzer.instance;
Types typeUtil = getTypeUtils();
return typeUtil.isAssignable(typeUtil.erasure(mTypeMirror), getMapType());
}
@@ -221,7 +218,7 @@ public class AnnotationClass implements ModelClass {
}
@Override
public ModelClass box() {
public AnnotationClass box() {
if (!isPrimitive()) {
return this;
}
@@ -233,8 +230,10 @@ public class AnnotationClass implements ModelClass {
if (that == null) {
return false;
}
TypeMirror thatType = ((AnnotationClass)that).mTypeMirror;
return getTypeUtils().isAssignable(thatType, mTypeMirror);
AnnotationClass thisBoxed = box();
AnnotationClass thatBoxed = (AnnotationClass) that.box();
final TypeMirror thatType = thatBoxed.mTypeMirror;
return getTypeUtils().isAssignable(thatType, thisBoxed.mTypeMirror);
}
@Override

View File

@@ -28,7 +28,7 @@ public abstract class ModelAnalyzer {
public abstract boolean isDataBinder(ModelClass modelClass);
public abstract Callable findMethod(ModelClass modelClass, String name,
List<ModelClass> args);
List<ModelClass> args, boolean staticAccess);
public abstract boolean isObservable(ModelClass modelClass);
@@ -38,7 +38,8 @@ public abstract class ModelAnalyzer {
public abstract boolean isBindable(ModelMethod method);
public abstract Callable findMethodOrField(ModelClass modelClass, String name);
public abstract Callable findMethodOrField(ModelClass modelClass, String name,
boolean staticAccess);
public ModelClass findCommonParentOf(ModelClass modelClass1,
ModelClass modelClass2) {

View File

@@ -46,6 +46,7 @@ import com.android.databinding.expr.ResourceExpr
import com.android.databinding.expr.BracketExpr
import com.android.databinding.reflection.Callable
import com.android.databinding.expr.CastExpr
import com.android.databinding.expr.StaticAccessExpr
fun String.stripNonJava() = this.split("[^a-zA-Z0-9]").map{ it.trim() }.joinToCamelCaseAsVar()
@@ -221,6 +222,9 @@ fun Expr.toCode(full : Boolean = false) : KCode {
app("(", it.getCastType())
app(") ", it.getCastExpr().toCode())
}
is StaticAccessExpr -> kcode("") {
app("", it.getResolvedType().toJavaCode())
}
else -> kcode("//NOT IMPLEMENTED YET")
}
@@ -422,24 +426,26 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
nl("}")
}
usedVariables.forEach {
nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}) {") {
if (it.isObservable()) {
tab("updateRegistration(${it.getId()}, ${it.readableUniqueName});");
if (it.getUserDefinedType() != null) {
nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}) {") {
if (it.isObservable()) {
tab("updateRegistration(${it.getId()}, ${it.readableUniqueName});");
}
tab("this.${it.fieldName} = ${it.readableUniqueName};")
// set dirty flags!
val flagSet = it.invalidateFlagSet
mDirtyFlags.mapOr(flagSet) { suffix, index ->
tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
}
tab("super.requestRebind();")
}
tab("this.${it.fieldName} = ${it.readableUniqueName};")
// set dirty flags!
val flagSet = it.invalidateFlagSet
mDirtyFlags.mapOr(flagSet) { suffix, index ->
tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
nl("}")
nl("")
nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
tab("return ${it.fieldName};")
}
tab("super.requestRebind();")
nl("}")
}
nl("}")
nl("")
nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
tab("return ${it.fieldName};")
}
nl("}")
}
}
@@ -667,8 +673,10 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
nl("public interface ${interfaceName} extends IViewDataBinder {")
variables.forEach {
tab("@Bindable")
tab("public void ${it.setterName}(${it.getUserDefinedType()} ${it.readableUniqueName});")
if (it.getUserDefinedType() != null) {
tab("@Bindable")
tab("public void ${it.setterName}(${it.getUserDefinedType()} ${it.readableUniqueName});")
}
}
layoutBinder.getBindingTargets().forEach {
tab("public ${it.getInterfaceType()} ${it.getterName}();")