Check API version for methods called via binding

In data binding, setting an attribute actually means calling a method, which might be
an issue if the method is added after a certain API.

This CL introduces a change which will check called methods per api and add necessary
API check code to avoid calling those methods in older platforms.

This CL also resurrects the Java Model Analyzer (in testing) and also fixes compiler tests.

Bug: 19593398
Change-Id: I0da4194625231cf43125e1b43338069e7d191eb9
This commit is contained in:
Yigit Boyar
2015-03-03 18:58:24 -08:00
parent eed3f1fee4
commit ac5dc9a4e1
40 changed files with 1781 additions and 257 deletions

View File

@@ -1,9 +1,12 @@
/*
* 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.
@@ -11,11 +14,13 @@
* limitations under the License.
*/
package com.android.databinding;
package com.android.databinding.library;
/**
* Mock class for Observable interface, used for testing.
* This helper is used to toggle DataBinder's package private values to change behavior for testing
*/
public class MockObservable {
public class DataBinderTrojan {
public static void setBuildSdkInt(int level) {
DataBinder.SDK_INT = level;
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.library.DataBinderTrojan;
import com.android.databinding.testapp.generated.NewApiLayoutBinder;
import android.content.Context;
import android.os.Build;
import android.test.UiThreadTest;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
public class NewApiTest extends BaseDataBinderTest<NewApiLayoutBinder> {
public NewApiTest() {
super(NewApiLayoutBinder.class, R.layout.new_api_layout);
}
@UiThreadTest
public void testSetElevation() {
mBinder.setElevation(3);
mBinder.setName("foo");
mBinder.setChildren(new ArrayList<View>());
mBinder.rebindDirty();
assertEquals("foo", mBinder.getTextView().getText().toString());
assertEquals(3f, mBinder.getTextView().getElevation());
}
@UiThreadTest
public void testSetElevationOlderAPI() {
DataBinderTrojan.setBuildSdkInt(1);
try {
TextView textView = mBinder.getTextView();
float originalElevation = textView.getElevation();
mBinder.setElevation(3);
mBinder.setName("foo2");
mBinder.rebindDirty();
assertEquals("foo2", textView.getText().toString());
assertEquals(originalElevation, textView.getElevation());
} finally {
DataBinderTrojan.setBuildSdkInt(Build.VERSION.SDK_INT);
}
}
@UiThreadTest
public void testGeneric() {
ArrayList<View> views = new ArrayList<>();
mBinder.setChildren(views);
mBinder.rebindDirty();
assertEquals(1, views.size());
assertSame(mBinder.getTextView(), views.get(0));
}
@UiThreadTest
public void testGenericOlderApi() {
DataBinderTrojan.setBuildSdkInt(1);
try {
ArrayList<View> views = new ArrayList<>();
mBinder.setChildren(views);
mBinder.rebindDirty();
// we should not call the api on older platforms.
assertEquals(0, views.size());
} finally {
DataBinderTrojan.setBuildSdkInt(Build.VERSION.SDK_INT);
}
}
}

View File

@@ -1,9 +1,12 @@
/*
* 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.

View File

@@ -0,0 +1,27 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/myContainer"
android:addChildrenForAccessibility="@{children}"
android:layout_width="match_parent"
android:layout_height="match_parent">
<variable name="elevation" type="float"/>
<variable name="name" type="java.lang.String"/>
<variable name="children" type="java.util.ArrayList&lt;android.view.View>"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:text="@{name}" android:elevation="@{elevation}"/>
</LinearLayout>

View File

@@ -1,7 +1,25 @@
/*
* 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.annotationprocessor;
import com.android.databinding.CompilerChef;
import com.android.databinding.reflection.SdkUtil;
import com.android.databinding.store.ResourceBundle;
import com.android.databinding.util.L;
import com.android.databinding.writer.AnnotationJavaFileWriter;
import org.apache.commons.codec.binary.Base64;
@@ -11,6 +29,7 @@ import android.binding.BinderBundle;
import android.binding.BindingAppInfo;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
@@ -37,12 +56,12 @@ public class ProcessExpressions extends AbstractProcessor {
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
ResourceBundle resourceBundle = null;
for (Element element : roundEnv.getElementsAnnotatedWith(BindingAppInfo.class)) {
final BindingAppInfo appInfo = element.getAnnotation(BindingAppInfo.class);
if (appInfo == null) {
continue; // It gets confused between BindingAppInfo and BinderBundle
}
SdkUtil.initialize(appInfo.minSdk(), new File(appInfo.sdkRoot()));
if (element.getKind() != ElementKind.CLASS) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"BindingAppInfo associated with wrong type. Should be a class.", element);

View File

@@ -21,5 +21,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) // this is necessary for java analyzer to work
public @interface Bindable {
}

View File

@@ -25,4 +25,6 @@ import java.lang.annotation.Target;
public @interface BindingAppInfo {
String buildId();
String applicationPackage();
String sdkRoot();
int minSdk();
}

View File

@@ -40,6 +40,7 @@ dependencies {
compile project(":baseLibrary")
compile project(":grammarBuilder")
compile project(":xmlGrammar")
testCompile "com.android.databinding:library:$version@jar"
}
uploadArchives {
repositories {

View File

@@ -18,6 +18,7 @@ package com.android.databinding;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.SdkUtil;
import com.android.databinding.store.SetterStore;
import com.android.databinding.expr.Expr;
@@ -26,7 +27,7 @@ public class Binding {
private final String mName;
private final Expr mExpr;
private final BindingTarget mTarget;
private String mJavaCode = null;
private SetterStore.SetterCall mSetterCall;
public Binding(BindingTarget target, String name, Expr expr) {
mTarget = target;
@@ -34,15 +35,31 @@ public class Binding {
mExpr = expr;
}
private SetterStore.SetterCall getSetterCall() {
if (mSetterCall == null) {
ModelClass viewType = mTarget.getResolvedType();
mSetterCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName,
viewType, mExpr.getResolvedType(), mExpr.getModel().getImports());
}
return mSetterCall;
}
public BindingTarget getTarget() {
return mTarget;
}
public String toJavaCode(String targetViewName, String expressionCode) {
ModelClass viewType = mTarget.getResolvedType();
return SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName, viewType,
mExpr.getResolvedType(), targetViewName, expressionCode,
mExpr.getModel().getImports());
return getSetterCall().toJava(targetViewName, expressionCode);
}
/**
* The min api level in which this binding should be executed.
* <p>
* This should be the minimum value among the dependencies of this binding. For now, we only
* check the setter.
*/
public int getMinApi() {
return getSetterCall().getMinApi();
}
// private String resolveJavaCode(ModelAnalyzer modelAnalyzer) {

View File

@@ -62,6 +62,7 @@ public class DataBinder {
public void writeBinders() {
for (LayoutBinder layoutBinder : mLayoutBinders) {
L.d("writing data binder %s", layoutBinder.getClassName());
mFileWriter.writeToFile(layoutBinder.getPackage() + "." + layoutBinder.getClassName(),
layoutBinder.writeViewBinder());
}

View File

@@ -21,6 +21,7 @@ import com.android.databinding.store.ResourceBundle;
import com.android.databinding.writer.JavaFileWriter;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringEscapeUtils;
import org.xml.sax.SAXException;
import java.io.File;
@@ -48,16 +49,19 @@ public class LayoutXmlProcessor {
public static final String APPLICATION_INFO_CLASS = "ApplicationBindingInfo";
private final JavaFileWriter mFileWriter;
private final ResourceBundle mResourceBundle;
private final int mMinSdk;
private boolean mProcessingComplete;
private boolean mWritten;
private final String mBuildId = UUID.randomUUID().toString();
private final List<File> mResourceFolders;
public LayoutXmlProcessor(String applicationPackage, List<File> resourceFolders,
JavaFileWriter fileWriter) {
JavaFileWriter fileWriter, int minSdk) {
mFileWriter = fileWriter;
mResourceBundle = new ResourceBundle(applicationPackage);
mResourceFolders = resourceFolders;
mMinSdk = minSdk;
}
public boolean processResources()
@@ -84,13 +88,13 @@ public class LayoutXmlProcessor {
return true;
}
public void writeIntermediateFile() throws JAXBException {
public void writeIntermediateFile(File sdkDir) throws JAXBException {
if (mWritten) {
return;
}
JAXBContext context = JAXBContext.newInstance(ResourceBundle.LayoutFileBundle.class);
Marshaller marshaller = context.createMarshaller();
writeAppInfo(marshaller);
writeAppInfo(marshaller, sdkDir);
for (List<ResourceBundle.LayoutFileBundle> layouts : mResourceBundle.getLayoutBundles()
.values()) {
for (ResourceBundle.LayoutFileBundle layout : layouts) {
@@ -124,10 +128,13 @@ public class LayoutXmlProcessor {
mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + className, classString);
}
private void writeAppInfo(Marshaller marshaller) {
private void writeAppInfo(Marshaller marshaller, File sdkDir) {
final String sdkPath = StringEscapeUtils.escapeJava(sdkDir.getAbsolutePath());
String classString = "import android.binding.BindingAppInfo;\n\n" +
"@BindingAppInfo(buildId=\"" + mBuildId + "\", applicationPackage=\"" +
mResourceBundle.getAppPackage() + "\")\n" +
"@BindingAppInfo(buildId=\"" + mBuildId + "\", " +
"applicationPackage=\"" + mResourceBundle.getAppPackage() + "\", " +
"sdkRoot=\"" + sdkPath + "\", " +
"minSdk=" + mMinSdk + ")\n" +
"public class " + APPLICATION_INFO_CLASS + " {}\n";
mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + APPLICATION_INFO_CLASS, classString);
}

View File

@@ -16,16 +16,61 @@
package com.android.databinding.reflection;
import com.google.common.base.Preconditions;
import com.android.databinding.reflection.annotation.AnnotationAnalyzer;
import com.android.databinding.util.L;
import java.net.URL;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.ProcessingEnvironment;
public abstract class ModelAnalyzer {
public static final String[] LIST_CLASS_NAMES = {
"java.util.List",
"android.util.SparseArray",
"android.util.SparseBooleanArray",
"android.util.SparseIntArray",
"android.util.SparseLongArray",
"android.util.LongSparseArray",
"android.support.v4.util.LongSparseArray",
};
public static final String MAP_CLASS_NAME = "java.util.Map";
public static final String STRING_CLASS_NAME = "java.lang.String";
public static final String OBJECT_CLASS_NAME = "java.lang.Object";
static ModelAnalyzer instance;
public static final String OBSERVABLE_CLASS_NAME = "android.binding.Observable";
public static final String OBSERVABLE_LIST_CLASS_NAME = "android.binding.ObservableList";
public static final String OBSERVABLE_MAP_CLASS_NAME = "android.binding.ObservableMap";
public static final String[] OBSERVABLE_FIELDS = {
"com.android.databinding.library.ObservableBoolean",
"com.android.databinding.library.ObservableByte",
"com.android.databinding.library.ObservableChar",
"com.android.databinding.library.ObservableShort",
"com.android.databinding.library.ObservableInt",
"com.android.databinding.library.ObservableLong",
"com.android.databinding.library.ObservableFloat",
"com.android.databinding.library.ObservableDouble",
"com.android.databinding.library.ObservableField",
};
public static final String I_VIEW_DATA_BINDER
= "com.android.databinding.library.IViewDataBinder";
private static ModelAnalyzer sAnalyzer;
protected void setInstance(ModelAnalyzer analyzer) {
sAnalyzer = analyzer;
}
public abstract boolean isDataBinder(ModelClass modelClass);
public abstract Callable findMethod(ModelClass modelClass, String name,
@@ -68,27 +113,32 @@ public abstract class ModelAnalyzer {
}
public static void setProcessingEnvironment(ProcessingEnvironment processingEnvironment) {
if (sAnalyzer != null) {
throw new IllegalStateException("processing env is already created, you cannot "
+ "change class loader after that");
}
L.d("setting processing env to %s", processingEnvironment);
AnnotationAnalyzer annotationAnalyzer = new AnnotationAnalyzer(processingEnvironment);
sAnalyzer = annotationAnalyzer;
}
public String getDefaultValue(String className) {
if("int".equals(className)) {
if ("int".equals(className)) {
return "0";
}
if("short".equals(className)) {
if ("short".equals(className)) {
return "0";
}
if("long".equals(className)) {
if ("long".equals(className)) {
return "0L";
}
if("float".equals(className)) {
if ("float".equals(className)) {
return "0f";
}
if("double".equals(className)) {
if ("double".equals(className)) {
return "0.0";
}
if("boolean".equals(className)) {
if ("boolean".equals(className)) {
return "false";
}
if ("char".equals(className)) {
@@ -105,4 +155,6 @@ public abstract class ModelAnalyzer {
public abstract List<URL> getResources(String name);
public abstract ModelClass findClass(Class classType);
public abstract TypeUtil createTypeUtil();
}

View File

@@ -62,4 +62,21 @@ public interface ModelClass {
ModelMethod[] getMethods(String name, int numParameters);
ModelClass getSuperclass();
String getCanonicalName();
/**
* Since when this class is available. Important for Binding expressions so that we don't
* call non-existing APIs when setting UI.
*
* @return The SDK_INT where this method was added. If it is not a framework method, should
* return 1.
*/
int getMinApi();
/**
* Returns the JNI description of the method which can be used to lookup it in SDK.
* @see com.android.databinding.reflection.TypeUtil
*/
String getJniDescription();
}

View File

@@ -29,4 +29,19 @@ public interface ModelMethod {
boolean isPublic();
boolean isStatic();
/**
* Since when this method is available. Important for Binding expressions so that we don't
* call non-existing APIs when setting UI.
*
* @return The SDK_INT where this method was added. If it is not a framework method, should
* return 1.
*/
int getMinApi();
/**
* Returns the JNI description of the method which can be used to lookup it in SDK.
* @see com.android.databinding.reflection.TypeUtil
*/
String getJniDescription();
}

View File

@@ -0,0 +1,157 @@
/*
* 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.reflection;
import com.google.common.base.Preconditions;
import com.android.databinding.util.L;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
/**
* Class that is used for SDK related stuff.
* <p>
* Must be initialized with the sdk location to work properly
*/
public class SdkUtil {
static File mSdkPath;
static ApiChecker mApiChecker;
static int mMinSdk;
public static void initialize(int minSdk, File sdkPath) {
mSdkPath = sdkPath;
mMinSdk = minSdk;
mApiChecker = new ApiChecker(new File(sdkPath.getAbsolutePath()
+ "/platform-tools/api/api-versions.xml"));
L.d("SdkUtil init, minSdk: %s", minSdk);
}
public static int getMinApi(ModelClass modelClass) {
return mApiChecker.getMinApi(modelClass.getJniDescription(), null);
}
public static int getMinApi(ModelMethod modelMethod) {
ModelClass declaringClass = modelMethod.getDeclaringClass();
Preconditions.checkNotNull(mApiChecker, "should've initialized api checker");
while (declaringClass != null) {
String classDesc = declaringClass.getJniDescription();
String methodDesc = modelMethod.getJniDescription();
int result = mApiChecker.getMinApi(classDesc, methodDesc);
L.d("checking method api for %s, class:%s method:%s. result: %d", modelMethod.getName(),
classDesc, methodDesc, result);
if (result > 1) {
return result;
}
declaringClass = declaringClass.getSuperclass();
}
return 1;
}
private static class ApiChecker {
private Map<String, Integer> mFullLookup = new HashMap<>();
private Document mDoc;
private XPath mXPath;
public ApiChecker(File apiFile) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
mDoc = builder.parse(apiFile);
XPathFactory xPathFactory = XPathFactory.newInstance();
mXPath = xPathFactory.newXPath();
buildFullLookup();
} catch (Throwable t) {
L.e(t, "cannot load api descriptions from %s", apiFile);
}
}
private void buildFullLookup() throws XPathExpressionException {
NodeList allClasses = mDoc.getChildNodes().item(0).getChildNodes();
for (int j = 0; j < allClasses.getLength(); j++) {
Node node = allClasses.item(j);
if (node.getNodeType() != Node.ELEMENT_NODE || !"class"
.equals(node.getNodeName())) {
continue;
}
//L.d("checking node %s", node.getAttributes().getNamedItem("name").getNodeValue());
int classSince = getSince(node);
String classDesc = node.getAttributes().getNamedItem("name").getNodeValue();
final NodeList childNodes = node.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE || !"method"
.equals(child.getNodeName())) {
continue;
}
int methodSince = getSince(child);
int since = Math.max(classSince, methodSince);
if (since > SdkUtil.mMinSdk) {
String methodDesc = child.getAttributes().getNamedItem("name")
.getNodeValue();
String key = cacheKey(classDesc, methodDesc);
L.d("adding method lookup %s as %s", key, since);
mFullLookup.put(key, since);
}
}
}
}
public int getMinApi(String classDesc, String methodOrFieldDesc) {
if (mDoc == null || mXPath == null) {
return 1;
}
if (classDesc == null || classDesc.isEmpty()) {
return 1;
}
final String key = cacheKey(classDesc, methodOrFieldDesc);
Integer since = mFullLookup.get(key);
return since == null ? 1 : since;
}
private static String cacheKey(String classDesc, String methodOrFieldDesc) {
return classDesc + "~" + methodOrFieldDesc;
}
private static int getSince(Node node) {
final Node since = node.getAttributes().getNamedItem("since");
if (since != null) {
final String nodeValue = since.getNodeValue();
if (nodeValue != null && !nodeValue.isEmpty()) {
try {
return Integer.parseInt(nodeValue);
} catch (Throwable t) {
}
}
}
return 1;
}
}
}

View File

@@ -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 com.android.databinding.reflection;
public abstract class TypeUtil {
public static final String BYTE = "B";
public static final String CHAR = "C";
public static final String DOUBLE = "D";
public static final String FLOAT = "F";
public static final String INT = "I";
public static final String LONG = "J";
public static final String SHORT = "S";
public static final String VOID = "V";
public static final String BOOLEAN = "Z";
public static final String ARRAY = "[";
public static final String CLASS_PREFIX = "L";
public static final String CLASS_SUFFIX = ";";
private static TypeUtil sInstance;
abstract public String getDescription(ModelClass modelClass);
abstract public String getDescription(ModelMethod modelMethod);
public static TypeUtil getInstance() {
if (sInstance == null) {
sInstance = ModelAnalyzer.getInstance().createTypeUtil();
}
return sInstance;
}
}

View File

@@ -13,10 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.databinding.reflection;
package com.android.databinding.reflection.annotation;
import com.google.common.collect.ImmutableMap;
import com.android.databinding.reflection.Callable;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelField;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.reflection.TypeUtil;
import com.android.databinding.util.L;
import org.apache.commons.lang3.StringUtils;
@@ -45,39 +51,7 @@ import javax.tools.Diagnostic;
public class AnnotationAnalyzer extends ModelAnalyzer {
public static final String[] LIST_CLASS_NAMES = {
"java.util.List",
"android.util.SparseArray",
"android.util.SparseBooleanArray",
"android.util.SparseIntArray",
"android.util.SparseLongArray",
"android.util.LongSparseArray",
"android.support.v4.util.LongSparseArray",
};
public static final String MAP_CLASS_NAME = "java.util.Map";
public static final String STRING_CLASS_NAME = "java.lang.String";
public static final String OBJECT_CLASS_NAME = "java.lang.Object";
static AnnotationAnalyzer instance;
private static final String OBSERVABLE_CLASS_NAME = "android.binding.Observable";
private static final String OBSERVABLE_LIST_CLASS_NAME = "android.binding.ObservableList";
private static final String OBSERVABLE_MAP_CLASS_NAME = "android.binding.ObservableMap";
private static final String[] OBSERVABLE_FIELDS = {
"com.android.databinding.library.ObservableBoolean",
"com.android.databinding.library.ObservableByte",
"com.android.databinding.library.ObservableChar",
"com.android.databinding.library.ObservableShort",
"com.android.databinding.library.ObservableInt",
"com.android.databinding.library.ObservableLong",
"com.android.databinding.library.ObservableFloat",
"com.android.databinding.library.ObservableDouble",
"com.android.databinding.library.ObservableField",
};
private static final String I_VIEW_DATA_BINDER = "com.android.databinding.library.IViewDataBinder";
private static final Map<String, TypeKind> PRIMITIVE_TYPES =
public static final Map<String, TypeKind> PRIMITIVE_TYPES =
new ImmutableMap.Builder<String, TypeKind>()
.put("boolean", TypeKind.BOOLEAN)
.put("byte", TypeKind.BYTE)
@@ -89,7 +63,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
.put("double", TypeKind.DOUBLE)
.build();
public final ProcessingEnvironment processingEnv;
public final ProcessingEnvironment mProcessingEnv;
private AnnotationClass[] mListTypes;
private AnnotationClass mMapType;
@@ -102,8 +76,8 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
private AnnotationClass mIViewDataBinderType;
public AnnotationAnalyzer(ProcessingEnvironment processingEnvironment) {
processingEnv = processingEnvironment;
instance = this;
mProcessingEnv = processingEnvironment;
setInstance(this);
}
public AnnotationClass[] getListTypes() {
@@ -120,6 +94,10 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
return mListTypes;
}
public static AnnotationAnalyzer get() {
return (AnnotationAnalyzer) getInstance();
}
public AnnotationClass getMapType() {
if (mMapType == null) {
mMapType = loadClassErasure(MAP_CLASS_NAME);
@@ -154,7 +132,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
}
return mObservableListType;
}
private AnnotationClass getObservableMapType() {
if (mObservableMapType == null) {
mObservableMapType = loadClassErasure(OBSERVABLE_MAP_CLASS_NAME);
@@ -185,7 +163,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
}
private TypeElement findType(String type) {
return processingEnv.getElementUtils().getTypeElement(type);
return mProcessingEnv.getElementUtils().getTypeElement(type);
}
@Override
@@ -196,9 +174,9 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
@Override
public Callable findMethod(ModelClass modelClass, String name,
List<ModelClass> args, boolean staticAccess) {
AnnotationClass clazz = (AnnotationClass)modelClass;
AnnotationClass clazz = (AnnotationClass) modelClass;
// TODO implement properly
for (String methodName : new String[]{"set" + StringUtils.capitalize(name), name}) {
for (String methodName : new String[]{"set" + StringUtils.capitalize(name), name}) {
for (ModelMethod method : clazz.getMethods(methodName, args.size())) {
if (method.isStatic() == staticAccess) {
ModelClass[] parameters = method.getParameterTypes();
@@ -226,13 +204,14 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
}
}
String message = "cannot find method '" + name + "' in class " + clazz.toJavaCode();
printMessage(Diagnostic.Kind.ERROR, message);
throw new IllegalArgumentException(message);
IllegalArgumentException e = new IllegalArgumentException(message);
L.e(e, "cannot find method %s in class %s", name, clazz.toJavaCode());
throw e;
}
@Override
public boolean isObservable(ModelClass modelClass) {
AnnotationClass annotationClass = (AnnotationClass)modelClass;
AnnotationClass annotationClass = (AnnotationClass) modelClass;
return getObservableType().isAssignableFrom(annotationClass) ||
getObservableListType().isAssignableFrom(annotationClass) ||
getObservableMapType().isAssignableFrom(annotationClass);
@@ -240,8 +219,9 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
@Override
public boolean isObservableField(ModelClass modelClass) {
AnnotationClass annotationClass = (AnnotationClass)modelClass;
AnnotationClass erasure = new AnnotationClass(getTypeUtils().erasure(annotationClass.mTypeMirror));
AnnotationClass annotationClass = (AnnotationClass) modelClass;
AnnotationClass erasure = new AnnotationClass(
getTypeUtils().erasure(annotationClass.mTypeMirror));
for (AnnotationClass observableField : getObservableFieldTypes()) {
if (observableField.isAssignableFrom(erasure)) {
return true;
@@ -264,7 +244,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
@Override
public Callable findMethodOrField(ModelClass modelClass, String name, boolean staticAccess) {
AnnotationClass annotationClass = (AnnotationClass)modelClass;
AnnotationClass annotationClass = (AnnotationClass) modelClass;
for (String methodName :
new String[]{"get" + StringUtils.capitalize(name),
"is" + StringUtils.capitalize(name), name}) {
@@ -375,13 +355,21 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
String baseClassName = className.substring(0, templateOpenIndex);
TypeElement typeElement = getTypeElement(baseClassName, imports);
if (typeElement == null) {
L.e("cannot find type element for %s", baseClassName);
return null;
}
ArrayList<String> templateParameters = splitTemplateParameters(paramStr);
TypeMirror[] typeArgs = new TypeMirror[templateParameters.size()];
for (int i = 0; i < typeArgs.length; i++) {
typeArgs[i] = findClass(templateParameters.get(i), imports).mTypeMirror;
if (typeArgs[i] == null) {
L.e("cannot find type argument for %s in %s", templateParameters.get(i),
baseClassName);
return null;
}
}
Types typeUtils = getTypeUtils();
declaredType = typeUtils.getDeclaredType(typeElement, typeArgs);
}
@@ -457,8 +445,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
urls.add(resources.nextElement());
}
} catch (IOException e) {
printMessage(Diagnostic.Kind.ERROR, "IOException while getting resources: " +
e.getLocalizedMessage());
L.e(e, "IOException while getting resources:");
}
return urls;
@@ -470,14 +457,19 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
}
public Types getTypeUtils() {
return processingEnv.getTypeUtils();
return mProcessingEnv.getTypeUtils();
}
public Elements getElementUtils() {
return processingEnv.getElementUtils();
return mProcessingEnv.getElementUtils();
}
public void printMessage(Diagnostic.Kind kind, String message) {
processingEnv.getMessager().printMessage(kind, message);
public ProcessingEnvironment getProcessingEnv() {
return mProcessingEnv;
}
@Override
public TypeUtil createTypeUtil() {
return new AnnotationTypeUtil(this);
}
}

View File

@@ -13,9 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.databinding.reflection;
package com.android.databinding.reflection.annotation;
import com.android.databinding.store.SetterStore;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.reflection.SdkUtil;
import com.android.databinding.reflection.TypeUtil;
import com.android.databinding.util.L;
import java.util.ArrayList;
import java.util.List;
@@ -33,7 +37,7 @@ import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
public class AnnotationClass implements ModelClass {
class AnnotationClass implements ModelClass {
final TypeMirror mTypeMirror;
@@ -100,15 +104,13 @@ public class AnnotationClass implements ModelClass {
}
}
if (foundInterface == null) {
printMessage(Diagnostic.Kind.ERROR,
"Detected " + interfaceType + " type for " + mTypeMirror +
L.e("Detected " + interfaceType + " type for " + mTypeMirror +
", but not able to find the implemented interface.");
return null;
}
}
if (foundInterface.getKind() != TypeKind.DECLARED) {
printMessage(Diagnostic.Kind.ERROR,
"Found " + interfaceType + " type for " + mTypeMirror +
L.e("Found " + interfaceType + " type for " + mTypeMirror +
", but it isn't a declared type: " + foundInterface);
return null;
}
@@ -285,6 +287,21 @@ public class AnnotationClass implements ModelClass {
return null;
}
@Override
public String getCanonicalName() {
return getTypeUtils().erasure(mTypeMirror).toString();
}
@Override
public int getMinApi() {
return SdkUtil.getMinApi(this);
}
@Override
public String getJniDescription() {
return TypeUtil.getInstance().getDescription(this);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AnnotationClass) {
@@ -300,31 +317,27 @@ public class AnnotationClass implements ModelClass {
}
private static Types getTypeUtils() {
return AnnotationAnalyzer.instance.processingEnv.getTypeUtils();
return AnnotationAnalyzer.get().mProcessingEnv.getTypeUtils();
}
private static Elements getElementUtils() {
return AnnotationAnalyzer.instance.processingEnv.getElementUtils();
return AnnotationAnalyzer.get().mProcessingEnv.getElementUtils();
}
private static AnnotationClass[] getListTypes() {
return AnnotationAnalyzer.instance.getListTypes();
return AnnotationAnalyzer.get().getListTypes();
}
private static AnnotationClass getMapType() {
return AnnotationAnalyzer.instance.getMapType();
return AnnotationAnalyzer.get().getMapType();
}
private static AnnotationClass getStringType() {
return AnnotationAnalyzer.instance.getStringType();
return AnnotationAnalyzer.get().getStringType();
}
private static AnnotationClass getObjectType() {
return AnnotationAnalyzer.instance.getObjectType();
}
private static void printMessage(Diagnostic.Kind kind, String message) {
AnnotationAnalyzer.instance.printMessage(kind, message);
return AnnotationAnalyzer.get().getObjectType();
}
@Override

View File

@@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.databinding.reflection;
package com.android.databinding.reflection.annotation;
import com.android.databinding.reflection.ModelField;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
public class AnnotationField implements ModelField {
class AnnotationField implements ModelField {
final VariableElement mField;
@@ -38,7 +40,7 @@ public class AnnotationField implements ModelField {
public boolean equals(Object obj) {
if (obj instanceof AnnotationField) {
AnnotationField that = (AnnotationField) obj;
return mDeclaredClass.equals(that.mDeclaredClass) && AnnotationAnalyzer.instance
return mDeclaredClass.equals(that.mDeclaredClass) && AnnotationAnalyzer.get()
.getTypeUtils().isSameType(mField.asType(), that.mField.asType());
} else {
return false;

View File

@@ -13,27 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.databinding.reflection;
package com.android.databinding.reflection.annotation;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.reflection.SdkUtil;
import com.android.databinding.reflection.TypeUtil;
import java.util.List;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
public class AnnotationMethod implements ModelMethod {
class AnnotationMethod implements ModelMethod {
final ExecutableType mMethod;
final DeclaredType mDeclaringType;
final ExecutableElement mExecutableElement;
int mApiLevel = -1; // calculated on demand
public AnnotationMethod(DeclaredType declaringType, ExecutableElement executableElement) {
mDeclaringType = declaringType;
mExecutableElement = executableElement;
Types typeUtils = AnnotationAnalyzer.instance.getTypeUtils();
Types typeUtils = AnnotationAnalyzer.get().getTypeUtils();
mMethod = (ExecutableType) typeUtils.asMemberOf(declaringType, executableElement);
}
@@ -74,4 +79,27 @@ public class AnnotationMethod implements ModelMethod {
public boolean isStatic() {
return mExecutableElement.getModifiers().contains(Modifier.STATIC);
}
@Override
public int getMinApi() {
if (mApiLevel == -1) {
mApiLevel = SdkUtil.getMinApi(this);
}
return mApiLevel;
}
@Override
public String getJniDescription() {
return TypeUtil.getInstance().getDescription(this);
}
@Override
public String toString() {
return "AnnotationMethod{" +
"mMethod=" + mMethod +
", mDeclaringType=" + mDeclaringType +
", mExecutableElement=" + mExecutableElement +
", mApiLevel=" + mApiLevel +
'}';
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.reflection.annotation;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.reflection.TypeUtil;
import java.util.List;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
public class AnnotationTypeUtil extends TypeUtil {
javax.lang.model.util.Types mTypes;
public AnnotationTypeUtil(
AnnotationAnalyzer annotationAnalyzer) {
mTypes = annotationAnalyzer.getTypeUtils();
}
@Override
public String getDescription(ModelClass modelClass) {
// TODO use interface
return modelClass.getCanonicalName().replace('.', '/');
}
@Override
public String getDescription(ModelMethod modelMethod) {
// TODO use interface
return modelMethod.getName() + getDescription(
((AnnotationMethod) modelMethod).mExecutableElement.asType());
}
private String getDescription(TypeMirror typeMirror) {
if (typeMirror == null) {
throw new UnsupportedOperationException();
}
switch (typeMirror.getKind()) {
case BOOLEAN:
return BOOLEAN;
case BYTE:
return BYTE;
case SHORT:
return SHORT;
case INT:
return INT;
case LONG:
return LONG;
case CHAR:
return CHAR;
case FLOAT:
return FLOAT;
case DOUBLE:
return DOUBLE;
case DECLARED:
return CLASS_PREFIX + mTypes.erasure(typeMirror).toString().replace('.', '/') + CLASS_SUFFIX;
case VOID:
return VOID;
case ARRAY:
final ArrayType arrayType = (ArrayType) typeMirror;
final String componentType = getDescription(arrayType.getComponentType());
return ARRAY + componentType;
case TYPEVAR:
final TypeVariable typeVariable = (TypeVariable) typeMirror;
final String name = typeVariable.toString();
return CLASS_PREFIX + name.replace('.', '/') + CLASS_SUFFIX;
case EXECUTABLE:
final ExecutableType executableType = (ExecutableType) typeMirror;
final int argStart = mTypes.erasure(executableType).toString().indexOf('(');
final String methodName = executableType.toString().substring(0, argStart);
final String args = joinArgs(executableType.getParameterTypes());
// TODO detect constructor?
return methodName + "(" + args + ")" + getDescription(
executableType.getReturnType());
default:
throw new UnsupportedOperationException("cannot understand type "
+ typeMirror.toString() + ", kind:" + typeMirror.getKind().name());
}
}
private String joinArgs(List<? extends TypeMirror> mirrorList) {
StringBuilder result = new StringBuilder();
for (TypeMirror mirror : mirrorList) {
result.append(getDescription(mirror));
}
return result.toString();
}
}

View File

@@ -15,10 +15,12 @@
*/
package com.android.databinding.store;
import com.android.databinding.reflection.AnnotationAnalyzer;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.util.L;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
@@ -43,7 +45,6 @@ import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
@@ -111,13 +112,9 @@ public class SetterStore {
}
return new SetterStore(modelAnalyzer, store);
} catch (IOException e) {
printMessage(Diagnostic.Kind.ERROR, "Could not read SetterStore intermediate file: " +
e.getLocalizedMessage());
e.printStackTrace();
L.e(e, "Could not read SetterStore intermediate file");
} catch (ClassNotFoundException e) {
printMessage(Diagnostic.Kind.ERROR, "Could not read SetterStore intermediate file: " +
e.getLocalizedMessage());
e.printStackTrace();
L.e(e, "Could not read SetterStore intermediate file");
}
return new SetterStore(modelAnalyzer, store);
}
@@ -255,8 +252,7 @@ public class SetterStore {
Filer filer = processingEnvironment.getFiler();
FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT,
SetterStore.class.getPackage().getName(), "setter_store.bin");
printMessage(Diagnostic.Kind.NOTE, "============= Writing intermediate file: " +
resource.getName());
L.d("============= Writing intermediate file: %s", resource.getName());
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(resource.openOutputStream());
@@ -269,17 +265,17 @@ public class SetterStore {
}
}
public String getSetterCall(String attribute, ModelClass viewType,
ModelClass valueType, String viewExpression, String valueExpression,
Map<String, String> imports) {
public SetterCall getSetterCall(String attribute, ModelClass viewType,
ModelClass valueType, Map<String, String> imports) {
if (!attribute.startsWith("android:")) {
int colon = attribute.indexOf(':');
if (colon >= 0) {
attribute = attribute.substring(colon + 1);
}
}
SetterCall setterCall = null;
MethodDescription adapter = null;
String setterName = null;
MethodDescription conversionMethod = null;
if (viewType != null) {
HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports);
@@ -288,7 +284,7 @@ public class SetterStore {
if (bestSetterMethod != null) {
bestViewType = bestSetterMethod.getDeclaringClass();
bestValueType = bestSetterMethod.getParameterTypes()[0];
setterName = bestSetterMethod.getName();
setterCall = new ModelMethodSetter(bestSetterMethod);
}
if (adapters != null) {
@@ -299,8 +295,7 @@ public class SetterStore {
if (adapterViewType.isAssignableFrom(viewType)) {
try {
ModelClass adapterValueType = mClassAnalyzer
.findClass(key.valueType,
imports);
.findClass(key.valueType, imports);
boolean isBetterView = bestViewType == null ||
bestValueType.isAssignableFrom(adapterValueType);
if (isBetterParameter(valueType, adapterValueType, bestValueType,
@@ -308,35 +303,29 @@ public class SetterStore {
bestViewType = adapterViewType;
bestValueType = adapterValueType;
adapter = adapters.get(key);
setterCall = new AdapterSetter(adapter);
}
} catch (Exception e) {
printMessage(Diagnostic.Kind.NOTE,
"Unknown class: " + key.valueType);
L.e(e, "Unknown class: %s", key.valueType);
}
}
} catch (Exception e) {
printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + key.viewType);
L.e(e, "Unknown class: %s", key.viewType);
}
}
}
MethodDescription conversionMethod = getConversionMethod(valueType, bestValueType,
imports);
if (conversionMethod != null) {
valueExpression = conversionMethod.type + "." + conversionMethod.method + "(" +
valueExpression + ")";
}
conversionMethod = getConversionMethod(valueType, bestValueType, imports);
}
if (adapter == null) {
if (setterName == null) {
setterName = getDefaultSetter(attribute);
}
return viewExpression + "." + setterName + "(" + valueExpression + ")";
} else {
return adapter.type + "." + adapter.method + "(" + viewExpression + ", " +
valueExpression + ")";
if (setterCall == null) {
setterCall = new DummySetter(getDefaultSetter(attribute));
// might be an include tag etc. just note it and continue.
L.d("Cannot find the setter for attribute " + attribute + ". might be an include file,"
+ " moving on.");
}
setterCall.setConverter(conversionMethod);
return setterCall;
}
public boolean isUntaggable(String viewType) {
@@ -345,15 +334,14 @@ public class SetterStore {
private ModelMethod getBestSetter(ModelClass viewType, ModelClass argumentType,
String attribute, Map<String, String> imports) {
String setterName = null;
List<String> setterCandidates = new ArrayList<>();
HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
if (renamed != null) {
for (String className : renamed.keySet()) {
try {
ModelClass renamedViewType = mClassAnalyzer.findClass(className, imports);
if (renamedViewType.isAssignableFrom(viewType)) {
setterName = renamed.get(className).method;
setterCandidates.add(renamed.get(className).method);
break;
}
} catch (Exception e) {
@@ -361,34 +349,40 @@ public class SetterStore {
}
}
}
if (setterName == null) {
setterName = getDefaultSetter(attribute);
}
ModelMethod[] methods = viewType.getMethods(setterName, 1);
setterCandidates.add(getDefaultSetter(attribute));
setterCandidates.add(trimAttributeNamespace(attribute));
ModelClass bestParameterType = null;
ModelMethod bestMethod = null;
List<ModelClass> args = new ArrayList<>();
args.add(argumentType);
for (ModelMethod method : methods) {
ModelClass[] parameterTypes = method.getParameterTypes();
if (method.getReturnType(args).isVoid() && !method.isStatic() && method.isPublic()) {
ModelClass param = parameterTypes[0];
if (isBetterParameter(argumentType, param, bestParameterType, true, imports)) {
bestParameterType = param;
bestMethod = method;
for (String name : setterCandidates) {
ModelMethod[] methods = viewType.getMethods(name, 1);
ModelClass bestParameterType = null;
List<ModelClass> args = new ArrayList<>();
args.add(argumentType);
for (ModelMethod method : methods) {
ModelClass[] parameterTypes = method.getParameterTypes();
if (method.getReturnType(args).isVoid() && !method.isStatic() && method
.isPublic()) {
ModelClass param = parameterTypes[0];
if (isBetterParameter(argumentType, param, bestParameterType, true, imports)) {
bestParameterType = param;
bestMethod = method;
}
}
}
}
return bestMethod;
}
private static String trimAttributeNamespace(String attribute) {
final int colonIndex = attribute.indexOf(':');
return colonIndex == -1 ? attribute : attribute.substring(colonIndex + 1);
}
private static String getDefaultSetter(String attribute) {
int colonIndex = attribute.indexOf(':');
String propertyName;
propertyName = Character.toUpperCase(attribute.charAt(colonIndex + 1)) +
attribute.substring(colonIndex + 2);
return "set" + propertyName;
return "set" + StringUtils.capitalize(trimAttributeNamespace(attribute));
}
private boolean isBetterParameter(ModelClass argument, ModelClass parameter,
@@ -463,12 +457,12 @@ public class SetterStore {
return conversion.get(toClassName);
}
} catch (Exception e) {
printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + toClassName);
L.d(e, "Unknown class: %s", toClassName);
}
}
}
} catch (Exception e) {
printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + fromClassName);
L.d(e, "Unknown class: %s", fromClassName);
}
}
}
@@ -549,15 +543,6 @@ public class SetterStore {
}
}
private static void printMessage(Diagnostic.Kind kind, String message) {
ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
if (modelAnalyzer instanceof AnnotationAnalyzer) {
((AnnotationAnalyzer) modelAnalyzer).printMessage(kind, message);
} else {
System.out.println(message);
}
}
private static class MethodDescription implements Serializable {
private static final long serialVersionUID = 1;
@@ -569,12 +554,14 @@ public class SetterStore {
public MethodDescription(String type, String method) {
this.type = type;
this.method = method;
L.d("BINARY created method desc 1 %s %s", type, method );
}
public MethodDescription(ExecutableElement method) {
TypeElement enclosingClass = (TypeElement) method.getEnclosingElement();
this.type = enclosingClass.getQualifiedName().toString();
this.method = method.getSimpleName().toString();
L.d("BINARY created method desc 2 %s %s, %s", type, this.method, method);
}
@Override
@@ -654,4 +641,85 @@ public class SetterStore {
return this;
}
}
public static class DummySetter extends SetterCall {
private String mMethodName;
public DummySetter(String methodName) {
mMethodName = methodName;
}
@Override
public String toJavaInternal(String viewExpression, String valueExpression) {
return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
}
@Override
public int getMinApi() {
return 1;
}
}
public static class AdapterSetter extends SetterCall {
final MethodDescription mAdapter;
public AdapterSetter(MethodDescription adapter) {
mAdapter = adapter;
}
@Override
public String toJavaInternal(String viewExpression, String valueExpression) {
return mAdapter.type + "." + mAdapter.method + "(" + viewExpression + ", " +
valueExpression + ")";
}
@Override
public int getMinApi() {
return 1;
}
}
public static class ModelMethodSetter extends SetterCall {
final ModelMethod mModelMethod;
public ModelMethodSetter(ModelMethod modelMethod) {
mModelMethod = modelMethod;
}
@Override
public String toJavaInternal(String viewExpression, String valueExpression) {
return viewExpression + "." + mModelMethod.getName() + "(" + valueExpression + ")";
}
@Override
public int getMinApi() {
return mModelMethod.getMinApi();
}
}
public static abstract class SetterCall {
private MethodDescription mConverter;
public SetterCall() {
}
public void setConverter(MethodDescription converter) {
mConverter = converter;
}
protected abstract String toJavaInternal(String viewExpression, String converted);
public final String toJava(String viewExpression, String valueExpression) {
return toJavaInternal(viewExpression, convertValue(valueExpression));
}
protected String convertValue(String valueExpression) {
return mConverter == null ? valueExpression :
mConverter.type + "." + mConverter.method + "(" + valueExpression + ")";
}
abstract public int getMinApi();
}
}

View File

@@ -16,19 +16,48 @@
package com.android.databinding.util;
import com.android.databinding.reflection.annotation.AnnotationAnalyzer;
import com.android.databinding.reflection.ModelAnalyzer;
import org.apache.commons.lang3.exception.ExceptionUtils;
import javax.tools.Diagnostic;
public class L {
public static void d(String msg, Object... args) {
System.out.println("[LDEBUG] " + String.format(msg, args));
printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
}
public static void d(Throwable t, String msg, Object... args) {
printMessage(Diagnostic.Kind.NOTE,
String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
}
public static void e(String msg, Object... args) {
System.out.println("[LERROR] " + String.format(msg, args));
printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));
}
public static void e(Throwable t, String msg, Object... args) {
System.out
.println("[LERROR]" + String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
printMessage(Diagnostic.Kind.ERROR,
String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
}
private static void printMessage(Diagnostic.Kind kind, String message) {
ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
System.out.println("[" + kind.name() + "]: " + message);
if (modelAnalyzer instanceof AnnotationAnalyzer) {
((AnnotationAnalyzer) modelAnalyzer).getProcessingEnv().getMessager()
.printMessage(kind, message);
if (kind == Diagnostic.Kind.ERROR) {
throw new RuntimeException("failure, see logs for details.\n" + message);
}
} else {
if (kind == Diagnostic.Kind.ERROR) {
throw new RuntimeException(message);
}
}
}
}

View File

@@ -620,7 +620,16 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
}.joinToString(" || ")
}) {") {
it.value.forEach { binding ->
tab("${binding.toJavaCode(binding.getTarget().fieldName, binding.getExpr().toCode().generate())};")
tab("// api target ${binding.getMinApi()}")
val bindingCode = binding.toJavaCode(binding.getTarget().fieldName, binding.getExpr().toCode().generate())
if (binding.getMinApi() > 1) {
tab("if(com.android.databinding.library.DataBinder.getBuildSdkInt() >= ${binding.getMinApi()}) {") {
tab("$bindingCode;")
}
tab("}")
} else {
tab("$bindingCode;")
}
}
}
tab("}")

View File

@@ -27,6 +27,8 @@ import com.android.databinding.expr.SymbolExpr;
import com.android.databinding.expr.TernaryExpr;
import com.android.databinding.reflection.Callable;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.java.JavaAnalyzer;
import com.android.databinding.reflection.java.JavaClass;
import org.junit.Before;
import org.junit.Test;
@@ -43,7 +45,7 @@ public class ExpressionVisitorTest {
@Before
public void setUp() throws Exception {
ModelAnalyzer.initForTests();
JavaAnalyzer.initForTests();
}
private <T extends Expr> T parse(String input, Class<T> klass) {
@@ -57,7 +59,7 @@ public class ExpressionVisitorTest {
final SymbolExpr res = parse("null", SymbolExpr.class);
assertEquals(1, mParser.getModel().size());
assertEquals("null", res.getText());
assertSame(res.getResolvedType(), Object.class);
assertEquals(new JavaClass(Object.class),res.getResolvedType());
assertEquals(0, res.getDependencies().size());
}
@@ -69,7 +71,7 @@ public class ExpressionVisitorTest {
@Before
public void setUp() throws Exception {
ModelAnalyzer.initForTests();
JavaAnalyzer.initForTests();
}
@Parameterized.Parameters
@@ -115,7 +117,7 @@ public class ExpressionVisitorTest {
assertEquals(1, mParser.mModel.size());
assertEquals("myStr", id.getName());
id.setUserDefinedType("java.lang.String");
assertSame(String.class, id.getResolvedType());
assertEquals(new JavaClass(String.class), id.getResolvedType());
}
@Test
@@ -142,7 +144,7 @@ public class ExpressionVisitorTest {
assertTrue(parsed.getChild() instanceof IdentifierExpr);
final IdentifierExpr id = (IdentifierExpr) parsed.getChild();
id.setUserDefinedType("java.lang.String");
assertSame(int.class, parsed.getResolvedType());
assertEquals(new JavaClass(int.class), parsed.getResolvedType());
Callable getter = parsed.getGetter();
assertEquals(Callable.Type.METHOD, getter.type);
assertEquals("length", getter.name);
@@ -158,7 +160,7 @@ public class ExpressionVisitorTest {
assertTrue(parsed.getChild() instanceof IdentifierExpr);
final IdentifierExpr id = (IdentifierExpr) parsed.getChild();
id.setUserDefinedType("java.lang.String");
assertSame(byte[].class, parsed.getResolvedType());
assertEquals(new JavaClass(byte[].class), parsed.getResolvedType());
Callable getter = parsed.getGetter();
assertEquals(Callable.Type.METHOD, getter.type);
assertEquals("getBytes", getter.name);

View File

@@ -21,6 +21,7 @@ import com.android.databinding.expr.IdentifierExpr;
import com.android.databinding.expr.StaticIdentifierExpr;
import com.android.databinding.reflection.Callable;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.java.JavaClass;
import org.junit.Before;
import org.junit.Test;
@@ -35,8 +36,7 @@ public class LayoutBinderTest {
ExprModel mExprModel;
@Before
public void setUp() throws Exception {
ModelAnalyzer.initForTests();
mLayoutBinder = new LayoutBinder(null);
mLayoutBinder = new MockLayoutBinder();
mExprModel = mLayoutBinder.getModel();
}
@@ -49,7 +49,7 @@ public class LayoutBinderTest {
assertEquals(value.getClass(), IdentifierExpr.class);
final IdentifierExpr id = (IdentifierExpr) value;
assertEquals("test", id.getName());
assertEquals(String.class, id.getResolvedType());
assertEquals(new JavaClass(String.class), id.getResolvedType());
assertTrue(id.isDynamic());
}
@@ -62,7 +62,7 @@ public class LayoutBinderTest {
assertEquals(value.getClass(), StaticIdentifierExpr.class);
final IdentifierExpr id = (IdentifierExpr) value;
assertEquals("test", id.getName());
assertEquals(String.class, id.getResolvedType());
assertEquals(new JavaClass(String.class), id.getResolvedType());
assertFalse(id.isDynamic());
}

View File

@@ -1,18 +0,0 @@
/*
* 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;
public interface MockIViewDataBinder {
}

View File

@@ -13,6 +13,12 @@
package com.android.databinding;
public class MockObservableLsit {
import com.android.databinding.store.ResourceBundle;
public class MockLayoutBinder extends LayoutBinder {
public MockLayoutBinder() {
super(new ResourceBundle("com.test"),
new ResourceBundle.LayoutFileBundle("blah.xml", 1, "."));
}
}

View File

@@ -1,18 +0,0 @@
/*
* 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;
public class MockObservableMap {
}

View File

@@ -0,0 +1,49 @@
/*
* 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;
import com.android.databinding.expr.ExprModel;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.reflection.SdkUtil;
import com.android.databinding.reflection.java.JavaAnalyzer;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class SdkVersionTest {
@Before
public void setUp() throws Exception {
JavaAnalyzer.initForTests();
}
@Test
public void testNewApiMethod() {
ModelClass view = ModelAnalyzer.getInstance().findClass("android.view.View", null);
ModelMethod setElevation = view.getMethods("setElevation", 1)[0];
assertEquals(21, SdkUtil.getMinApi(setElevation));
}
@Test
public void testCustomCode() {
ModelClass view = ModelAnalyzer.getInstance()
.findClass("com.android.databinding.SdkVersionTest", null);
ModelMethod setElevation = view.getMethods("testCustomCode", 0)[0];
assertEquals(1, SdkUtil.getMinApi(setElevation));
}
}

View File

@@ -19,8 +19,12 @@ package com.android.databinding.expr;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.android.databinding.MockLayoutBinder;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.LayoutBinder;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.java.JavaAnalyzer;
import com.android.databinding.store.ResourceBundle;
import com.android.databinding.util.L;
import org.apache.commons.lang3.ArrayUtils;
@@ -31,6 +35,7 @@ import org.junit.Test;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import java.io.File;
import java.util.BitSet;
import java.util.List;
@@ -45,8 +50,8 @@ public class ExprModelTest {
}
@Override
protected Class resolveType(ModelAnalyzer modelAnalyzer) {
return Integer.class;
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
return modelAnalyzer.findClass(Integer.class);
}
@Override
@@ -77,7 +82,7 @@ public class ExprModelTest {
@Before
public void setUp() throws Exception {
ModelAnalyzer.initForTests();
JavaAnalyzer.initForTests();
mExprModel = new ExprModel();
}
@@ -118,7 +123,7 @@ public class ExprModelTest {
@Test
public void testShouldRead() {
LayoutBinder lb = new LayoutBinder(null);
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", "java.lang.String");
IdentifierExpr b = lb.addVariable("b", "java.lang.String");
@@ -140,7 +145,7 @@ public class ExprModelTest {
@Test
public void testTernaryInsideTernary() {
LayoutBinder lb = new LayoutBinder(null);
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr cond1 = lb.addVariable("cond1", "boolean");
IdentifierExpr cond2 = lb.addVariable("cond2", "boolean");
@@ -185,7 +190,7 @@ public class ExprModelTest {
@Test
public void testRequirementFlags() {
LayoutBinder lb = new LayoutBinder(null);
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", "java.lang.String");
IdentifierExpr b = lb.addVariable("b", "java.lang.String");
@@ -256,7 +261,7 @@ public class ExprModelTest {
@Test
public void testPostConditionalDependencies() {
LayoutBinder lb = new LayoutBinder(null);
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
@@ -273,7 +278,7 @@ public class ExprModelTest {
Expr bcCmp = bcTernary.getPred();
Expr u1GetCondD = ((TernaryExpr) bcTernary.getIfTrue()).getPred();
final MathExpr xxPlusU2getCondE = (MathExpr) bcTernary.getIfFalse();
Expr u2GetCondE = xxPlusU2getCondE.mRight;
Expr u2GetCondE = xxPlusU2getCondE.getRight();
Expr u1Name = abTernary.getIfTrue();
Expr u2Name = abTernary.getIfFalse();
Expr u1LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfTrue();
@@ -338,7 +343,7 @@ public class ExprModelTest {
@Test
public void testCircularDependency() {
LayoutBinder lb = new LayoutBinder(null);
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
@@ -354,7 +359,7 @@ public class ExprModelTest {
@Test
public void testNestedCircularDependency() {
LayoutBinder lb = new LayoutBinder(null);
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
@@ -374,7 +379,7 @@ public class ExprModelTest {
@Test
public void testNoFlagsForNonBindingStatic() {
LayoutBinder lb = new LayoutBinder(null);
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
lb.addVariable("a", int.class.getCanonicalName());
final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
@@ -386,7 +391,7 @@ public class ExprModelTest {
@Test
public void testFlagsForBindingStatic() {
LayoutBinder lb = new LayoutBinder(null);
LayoutBinder lb = new MockLayoutBinder();
mExprModel = lb.getModel();
lb.addVariable("a", int.class.getCanonicalName());
final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class);
@@ -401,7 +406,6 @@ public class ExprModelTest {
@Test
public void testPartialNeededRead() {
throw new NotImplementedException("create a test that has a variable which can be read for "
+ "some flags and also may be read for some condition. Try both must match and"
+ " partial match and none-match in conditionals");

View File

@@ -17,6 +17,8 @@
package com.android.databinding.expr;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.java.JavaAnalyzer;
import org.junit.Before;
import org.junit.Test;
@@ -37,8 +39,8 @@ public class ExprTest{
}
@Override
protected Class resolveType(ModelAnalyzer modelAnalyzer) {
return Integer.class;
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
return modelAnalyzer.findClass(Integer.class);
}
@Override
@@ -59,15 +61,15 @@ public class ExprTest{
@Before
public void setUp() throws Exception {
ModelAnalyzer.initForTests();
JavaAnalyzer.initForTests();
}
@Test(expected=IllegalStateException.class)
public void testBadExpr() {
Expr expr = new Expr() {
@Override
protected Class resolveType(ModelAnalyzer modelAnalyzer) {
return Integer.class;
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
return modelAnalyzer.findClass(Integer.class);
}
@Override

View File

@@ -0,0 +1,361 @@
/*
* 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.reflection.java;
import com.google.common.collect.ImmutableMap;
import com.android.databinding.reflection.Callable;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelField;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.reflection.SdkUtil;
import com.android.databinding.reflection.TypeUtil;
import com.android.databinding.util.L;
import org.apache.commons.lang3.StringUtils;
import android.binding.Bindable;
import android.binding.Observable;
import android.binding.ObservableList;
import android.binding.ObservableMap;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JavaAnalyzer extends ModelAnalyzer {
public static final Map<String, Class> PRIMITIVE_TYPES =
new ImmutableMap.Builder<String, Class>()
.put("boolean", boolean.class)
.put("byte", byte.class)
.put("short", short.class)
.put("char", char.class)
.put("int", int.class)
.put("long", long.class)
.put("float", float.class)
.put("double", double.class)
.build();
private static final String BINDABLE_ANNOTATION_NAME = "android.binding.Bindable";
private HashMap<String, JavaClass> mClassCache = new HashMap<>();
private final ClassLoader mClassLoader;
private final Class mObservable;
private final Class mObservableList;
private final Class mObservableMap;
private final Class[] mObservableFields;
private final Class mBindable;
private final boolean mTestMode;
private final Class mIViewDataBinder;
public JavaAnalyzer(ClassLoader classLoader, boolean testMode) {
setInstance(this);
mClassLoader = classLoader;
mTestMode = testMode;
try {
mIViewDataBinder = classLoader.loadClass(I_VIEW_DATA_BINDER);
mObservable = Observable.class;
mObservableList = ObservableList.class;
mObservableMap = ObservableMap.class;
mBindable = Bindable.class;
mObservableFields = new Class[OBSERVABLE_FIELDS.length];
for (int i = 0; i < OBSERVABLE_FIELDS.length; i++) {
mObservableFields[i] = classLoader.loadClass(getClassName(OBSERVABLE_FIELDS[i]));
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private String getClassName(String name) {
return name;
}
@Override
public boolean isDataBinder(ModelClass reflectionClass) {
JavaClass javaClass = (JavaClass) reflectionClass;
return mIViewDataBinder.isAssignableFrom(javaClass.mClass);
}
@Override
public Callable findMethod(ModelClass modelClass, String name, List<ModelClass> argClasses,
boolean staticAccess) {
Class klass = ((JavaClass) modelClass).mClass;
ArrayList<Class> args = new ArrayList<>(argClasses.size());
for (int i = 0; i < argClasses.size(); i++) {
args.add(((JavaClass) argClasses.get(i)).mClass);
}
// TODO implement properly
for (String methodName : new String[]{"set" + StringUtils.capitalize(name), name}) {
for (Method method : klass.getMethods()) {
if (methodName.equals(method.getName()) && args.size() == method
.getParameterTypes().length) {
return new Callable(Callable.Type.METHOD, methodName,
new JavaClass(method.getReturnType()), true, false);
}
}
}
L.e(new Exception(), "cannot find method %s in %s", name, klass);
throw new IllegalArgumentException(
"cannot find method " + name + " at class " + klass.getSimpleName());
}
@Override
public boolean isObservable(ModelClass modelClass) {
Class klass = ((JavaClass) modelClass).mClass;
return isObservable(klass);
}
private boolean isObservable(Class klass) {
return mObservable.isAssignableFrom(klass) || mObservableList.isAssignableFrom(klass) ||
mObservableMap.isAssignableFrom(klass);
}
@Override
public boolean isObservableField(ModelClass reflectionClass) {
Class klass = ((JavaClass) reflectionClass).mClass;
for (Class observableField : mObservableFields) {
if (observableField.isAssignableFrom(klass)) {
return true;
}
}
return false;
}
@Override
public boolean isBindable(ModelField reflectionField) {
Field field = ((JavaField) reflectionField).mField;
return isBindable(field);
}
@Override
public boolean isBindable(ModelMethod reflectionMethod) {
Method method = ((JavaMethod) reflectionMethod).mMethod;
return isBindable(method);
}
private boolean isBindable(Field field) {
return field.getAnnotation(mBindable) != null;
}
private boolean isBindable(Method method) {
return method.getAnnotation(mBindable) != null;
}
@Override
public Callable findMethodOrField(ModelClass modelClass, String name, boolean staticAccess) {
final Class klass = ((JavaClass) modelClass).mClass;
for (String methodName :
new String[]{"get" + StringUtils.capitalize(name),
"is" + StringUtils.capitalize(name), name}) {
try {
Method method = klass.getMethod(methodName);
Field backingField = findField(klass, name, true);
if (Modifier.isPublic(method.getModifiers())) {
final Callable result = new Callable(Callable.Type.METHOD, methodName,
new JavaClass(method.getReturnType()), true,
isBindable(method) || (backingField != null && isBindable(backingField)) );
L.d("backing field for %s is %s", result, backingField);
return result;
}
} catch (Throwable t) {
}
}
try {
Field field = findField(klass, name, false);
if (Modifier.isPublic(field.getModifiers())) {
return new Callable(Callable.Type.FIELD, name, new JavaClass(field.getType()),
!Modifier.isFinal(field.getModifiers())
|| isObservable(field.getType()), isBindable(field));
}
} catch (Throwable t) {
}
throw new IllegalArgumentException(
"cannot find " + name + " in " + klass.getCanonicalName());
}
private Field findField(Class klass, String name, boolean allowNonPublic) {
try {
return getField(klass, name, allowNonPublic);
} catch (NoSuchFieldException e) {
}
String capitalizedName = StringUtils.capitalize(name);
try {
return getField(klass, "m" + capitalizedName, allowNonPublic);
} catch (Throwable t){}
try {
return getField(klass, "_" + name, allowNonPublic);
} catch (Throwable t){}
try {
return getField(klass, "_" + capitalizedName, allowNonPublic);
} catch (Throwable t){}
try {
return getField(klass, "m_" + name, allowNonPublic);
} catch (Throwable t){}
try {
return getField(klass, "m_" + capitalizedName, allowNonPublic);
} catch (Throwable t){}
return null;
}
private Field getField(Class klass, String exactName, boolean allowNonPublic)
throws NoSuchFieldException {
try {
return klass.getField(exactName);
} catch (NoSuchFieldException e) {
if (allowNonPublic) {
return klass.getDeclaredField(exactName);
} else {
throw e;
}
}
}
@Override
public JavaClass loadPrimitive(String className) {
Class clazz = PRIMITIVE_TYPES.get(className);
if (clazz == null) {
return null;
} else {
return new JavaClass(clazz);
}
}
@Override
public ModelClass findClass(String className, Map<String, String> imports) {
// TODO handle imports
JavaClass loaded = mClassCache.get(className);
if (loaded != null) {
return loaded;
}
L.d("trying to load class %s from %s", className, mClassLoader.toString());
loaded = loadPrimitive(className);
if (loaded == null) {
try {
if (className.startsWith("[") && className.contains("L")) {
int indexOfL = className.indexOf('L');
JavaClass baseClass = (JavaClass) findClass(
className.substring(indexOfL + 1, className.length() - 1), null);
String realClassName = className.substring(0, indexOfL + 1) +
baseClass.mClass.getCanonicalName() + ';';
loaded = new JavaClass(Class.forName(realClassName, false, mClassLoader));
mClassCache.put(className, loaded);
} else {
loaded = loadRecursively(className);
mClassCache.put(className, loaded);
}
} catch (Throwable t) {
// L.e(t, "cannot load class " + className);
}
}
// expr visitor may call this to resolve statics. Sometimes, it is OK not to find a class.
if (loaded == null) {
return null;
}
L.d("loaded class %s", loaded.mClass.getCanonicalName());
return loaded;
}
@Override
public ModelClass findClass(Class classType) {
return new JavaClass(classType);
}
@Override
public TypeUtil createTypeUtil() {
return new JavaTypeUtil();
}
private JavaClass loadRecursively(String className) throws ClassNotFoundException {
try {
L.d("recursively checking %s", className);
return new JavaClass(mClassLoader.loadClass(className));
} catch (ClassNotFoundException ex) {
int lastIndexOfDot = className.lastIndexOf(".");
if (lastIndexOfDot == -1) {
throw ex;
}
return loadRecursively(className.substring(0, lastIndexOfDot) + "$" + className
.substring(lastIndexOfDot + 1));
}
}
@Override
public List<URL> getResources(String name) {
List<URL> urlList = new ArrayList<URL>();
Enumeration<URL> urls = null;
try {
urls = mClassLoader.getResources(name);
if (urls != null) {
while (urls.hasMoreElements()) {
urlList.add(urls.nextElement());
}
}
} catch (IOException e) {
e.printStackTrace();
}
return urlList;
}
public static void initForTests() {
Map<String, String> env = System.getenv();
for (Map.Entry<String, String> entry : env.entrySet()) {
L.d("%s %s", entry.getKey(), entry.getValue());
}
String androidHome = env.get("ANDROID_HOME");
if (androidHome == null) {
throw new IllegalStateException("you need to have ANDROID_HOME set in your environment"
+ " to run compiler tests");
}
File androidJar = new File(androidHome + "/platforms/android-21/android.jar");
if (!androidJar.exists() || !androidJar.canRead()) {
throw new IllegalStateException(
"cannot find android jar at " + androidJar.getAbsolutePath());
}
// now load android data binding library as well
try {
ClassLoader classLoader = new URLClassLoader(new URL[]{androidJar.toURI().toURL()},
ModelAnalyzer.class.getClassLoader());
new JavaAnalyzer(classLoader, true);
} catch (MalformedURLException e) {
throw new RuntimeException("cannot create class loader", e);
}
SdkUtil.initialize(8, new File(androidHome));
}
}

View File

@@ -0,0 +1,245 @@
/*
* 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.reflection.java;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.reflection.SdkUtil;
import com.android.databinding.reflection.TypeUtil;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class JavaClass implements ModelClass {
public final Class mClass;
public JavaClass(Class clazz) {
mClass = clazz;
}
@Override
public String toJavaCode() {
return toJavaCode(mClass);
}
private static String toJavaCode(Class aClass) {
if (aClass.isArray()) {
Class component = aClass.getComponentType();
return toJavaCode(component) + "[]";
} else {
return aClass.getCanonicalName().replace('$', '.');
}
}
@Override
public boolean isArray() {
return mClass.isArray();
}
@Override
public ModelClass getComponentType() {
if (mClass.isArray()) {
return new JavaClass(mClass.getComponentType());
} else if (isList() || isMap()) {
return new JavaClass(Object.class);
} else {
return null;
}
}
@Override
public boolean isList() {
return List.class.isAssignableFrom(mClass);
}
@Override
public boolean isMap() {
return Map.class.isAssignableFrom(mClass);
}
@Override
public boolean isString() {
return String.class.equals(mClass);
}
@Override
public boolean isNullable() {
return Object.class.isAssignableFrom(mClass);
}
@Override
public boolean isPrimitive() {
return mClass.isPrimitive();
}
@Override
public boolean isBoolean() {
return boolean.class.equals(mClass);
}
@Override
public boolean isChar() {
return char.class.equals(mClass);
}
@Override
public boolean isByte() {
return byte.class.equals(mClass);
}
@Override
public boolean isShort() {
return short.class.equals(mClass);
}
@Override
public boolean isInt() {
return int.class.equals(mClass);
}
@Override
public boolean isLong() {
return long.class.equals(mClass);
}
@Override
public boolean isFloat() {
return float.class.equals(mClass);
}
@Override
public boolean isDouble() {
return double.class.equals(mClass);
}
@Override
public boolean isObject() {
return Object.class.equals(mClass);
}
@Override
public boolean isVoid() {
return void.class.equals(mClass);
}
@Override
public ModelClass unbox() {
if (mClass.isPrimitive()) {
return this;
}
if (Integer.class.equals(mClass)) {
return new JavaClass(int.class);
} else if (Long.class.equals(mClass)) {
return new JavaClass(long.class);
} else if (Short.class.equals(mClass)) {
return new JavaClass(short.class);
} else if (Byte.class.equals(mClass)) {
return new JavaClass(byte.class);
} else if (Character.class.equals(mClass)) {
return new JavaClass(char.class);
} else if (Double.class.equals(mClass)) {
return new JavaClass(double.class);
} else if (Float.class.equals(mClass)) {
return new JavaClass(float.class);
} else if (Boolean.class.equals(mClass)) {
return new JavaClass(boolean.class);
} else {
// not a boxed type
return this;
}
}
@Override
public JavaClass box() {
if (!mClass.isPrimitive()) {
return this;
}
if (int.class.equals(mClass)) {
return new JavaClass(Integer.class);
} else if (long.class.equals(mClass)) {
return new JavaClass(Long.class);
} else if (short.class.equals(mClass)) {
return new JavaClass(Short.class);
} else if (byte.class.equals(mClass)) {
return new JavaClass(Byte.class);
} else if (char.class.equals(mClass)) {
return new JavaClass(Character.class);
} else if (double.class.equals(mClass)) {
return new JavaClass(Double.class);
} else if (float.class.equals(mClass)) {
return new JavaClass(Float.class);
} else if (boolean.class.equals(mClass)) {
return new JavaClass(Boolean.class);
} else {
// not a valid type?
return this;
}
}
@Override
public boolean isAssignableFrom(ModelClass that) {
Class thatClass = ((JavaClass) that).mClass;
return mClass.isAssignableFrom(thatClass);
}
@Override
public ModelMethod[] getMethods(String name, int numParameters) {
Method[] methods = mClass.getMethods();
ArrayList<JavaMethod> matching = new ArrayList<>();
for (Method method : methods) {
if (method.getName().equals(name) &&
method.getParameterTypes().length == numParameters) {
matching.add(new JavaMethod(method));
}
}
return matching.toArray(new JavaMethod[matching.size()]);
}
@Override
public ModelClass getSuperclass() {
return new JavaClass(mClass.getSuperclass());
}
@Override
public String getCanonicalName() {
return mClass.getCanonicalName();
}
@Override
public int getMinApi() {
return SdkUtil.getMinApi(this);
}
@Override
public String getJniDescription() {
return TypeUtil.getInstance().getDescription(this);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof JavaClass) {
return mClass.equals(((JavaClass) obj).mClass);
} else {
return false;
}
}
@Override
public int hashCode() {
return mClass.hashCode();
}
}

View File

@@ -11,11 +11,16 @@
* limitations under the License.
*/
package com.android.databinding;
package com.android.databinding.reflection.java;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import com.android.databinding.reflection.ModelField;
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface MockBindable {
import java.lang.reflect.Field;
public class JavaField implements ModelField {
public final Field mField;
public JavaField(Field field) {
mField = field;
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.reflection.java;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.reflection.SdkUtil;
import com.android.databinding.reflection.TypeUtil;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
public class JavaMethod implements ModelMethod {
public final Method mMethod;
public JavaMethod(Method method) {
mMethod = method;
}
@Override
public ModelClass getDeclaringClass() {
return new JavaClass(mMethod.getDeclaringClass());
}
@Override
public ModelClass[] getParameterTypes() {
Class[] parameterTypes = mMethod.getParameterTypes();
ModelClass[] parameterClasses = new ModelClass[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
parameterClasses[i] = new JavaClass(parameterTypes[i]);
}
return parameterClasses;
}
@Override
public String getName() {
return mMethod.getName();
}
@Override
public ModelClass getReturnType(List<ModelClass> args) {
return new JavaClass(mMethod.getReturnType());
}
@Override
public boolean isPublic() {
return Modifier.isPublic(mMethod.getModifiers());
}
@Override
public boolean isStatic() {
return Modifier.isStatic(mMethod.getModifiers());
}
@Override
public int getMinApi() {
return SdkUtil.getMinApi(this);
}
@Override
public String getJniDescription() {
return TypeUtil.getInstance().getDescription(this);
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.reflection.java;
import com.android.databinding.reflection.ModelClass;
import com.android.databinding.reflection.ModelMethod;
import com.android.databinding.reflection.TypeUtil;
import com.android.databinding.util.L;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
public class JavaTypeUtil extends TypeUtil {
@Override
public String getDescription(ModelClass modelClass) {
return modelClass.getCanonicalName().replace('.', '/');
}
@Override
public String getDescription(ModelMethod modelMethod) {
Method method = ((JavaMethod) modelMethod).mMethod;
StringBuilder sb = new StringBuilder();
sb.append(method.getName());
sb.append("(");
for (Class param : method.getParameterTypes()) {
sb.append(getDescription(param));
}
sb.append(")");
sb.append(getDescription(method.getReturnType()));
return sb.toString();
}
private String getDescription(Class klass) {
if (klass == null) {
throw new UnsupportedOperationException();
}
if (boolean.class.equals(klass)) {
return BOOLEAN;
}
if (byte.class.equals(klass)) {
return BYTE;
}
if (short.class.equals(klass)) {
return SHORT;
}
if (int.class.equals(klass)) {
return INT;
}
if (long.class.equals(klass)) {
return LONG;
}
if (char.class.equals(klass)) {
return CHAR;
}
if (float.class.equals(klass)) {
return FLOAT;
}
if (double.class.equals(klass)) {
return DOUBLE;
}
if (void.class.equals(klass)) {
return VOID;
}
if (Object.class.isAssignableFrom(klass)) {
return CLASS_PREFIX + klass.getCanonicalName().replace('.', '/') + CLASS_SUFFIX;
}
if (Array.class.isAssignableFrom(klass)) {
return ARRAY + getDescription(klass.getComponentType());
}
UnsupportedOperationException ex
= new UnsupportedOperationException("cannot understand type "
+ klass.toString() + ", kind:");
L.e(ex, "cannot create JNI type for %s", klass.getCanonicalName());
throw ex;
}
}

View File

@@ -51,6 +51,7 @@ import org.apache.commons.io.IOUtils
import java.io.FileWriter
import java.io.ByteArrayOutputStream
import org.apache.commons.codec.binary.Base64
import com.android.builder.model.ApiVersion
class DataBinderPlugin : Plugin<Project> {
@@ -78,6 +79,8 @@ class DataBinderPlugin : Plugin<Project> {
var viewBinderSource : File by Delegates.notNull()
var sdkDir : File by Delegates.notNull()
val viewBinderSourceRoot by Delegates.lazy {
File(project.getBuildDir(), "databinder")
}
@@ -118,6 +121,8 @@ class DataBinderPlugin : Plugin<Project> {
fun createXmlProcessor(p: Project): LayoutXmlProcessor {
val ss = p.getExtensions().getByName("android") as AppExtension
sdkDir = ss.getSdkDirectory()
val minSdkVersion = ss.getDefaultConfig().getMinSdkVersion()
androidJar = File(ss.getSdkDirectory().getAbsolutePath() + "/platforms/${ss.getCompileSdkVersion()}/android.jar")
log("creating parser!")
log("project build dir:${p.getBuildDir()}")
@@ -128,10 +133,7 @@ class DataBinderPlugin : Plugin<Project> {
variantData = field.get(appVariant) as ApplicationVariantData
// TODO
val packageName = variantData.generateRClassTask.getPackageForR()
//"com.com.android.databinding.android.databinding.libraryGen"//variantData.getPackageName()
//
val sources = variantData.getJavaSources()
sources.forEach({
log("source: ${it}");
@@ -168,7 +170,7 @@ class DataBinderPlugin : Plugin<Project> {
dexTask.doFirst(MethodClosure(this, "preDexAnalysis"))
val writerOutBase = codeGenTargetFolder.getAbsolutePath();
fileWriter = GradleFileWriter(writerOutBase)
return LayoutXmlProcessor(packageName, resourceFolders, fileWriter)
return LayoutXmlProcessor(packageName, resourceFolders, fileWriter, minSdkVersion.getApiLevel())
}
@@ -195,6 +197,6 @@ class DataBinderPlugin : Plugin<Project> {
}
fun generateIntermediateFile(o: Any?) {
xmlProcessor.writeIntermediateFile()
xmlProcessor.writeIntermediateFile(sdkDir)
}
}

View File

@@ -69,17 +69,14 @@ dependencies {
android.libraryVariants.all { variant ->
def name = variant.buildType.name
if (name.equals("debug")) {
if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
return; // Skip debug builds.
}
def suffix = name.capitalize()
// def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
// classifier = 'sources'
// from android.sourceSets.main.java
// }
//
// artifacts.add('archives', sourcesJarTask);
// @Jar version is needed to run compiler tests
def task = project.tasks.create "jar${name.capitalize()}", Jar
task.dependsOn variant.javaCompile
task.from variant.javaCompile.destinationDir
artifacts.add('archives', task);
}
uploadArchives {
repositories {

View File

@@ -17,6 +17,7 @@
package com.android.databinding.library;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -30,6 +31,12 @@ public class DataBinder {
static DataBinderMapper sMapper;
/**
* Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that
* we can test API dependent behavior.
*/
static int SDK_INT = Build.VERSION.SDK_INT;
private WeakHashMap<View, ViewDataBinder> mDataBinderMap = new WeakHashMap<>();
private SparseArray<WeakReference<ViewDataBinder>> mDataBinderById = new SparseArray<>();
@@ -49,6 +56,10 @@ public class DataBinder {
return sMapper;
}
public static int getBuildSdkInt() {
return SDK_INT;
}
public static int convertToId(String key) {
return getMapper().getId(key);
}