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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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<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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -25,4 +25,6 @@ import java.lang.annotation.Target;
|
||||
public @interface BindingAppInfo {
|
||||
String buildId();
|
||||
String applicationPackage();
|
||||
String sdkRoot();
|
||||
int minSdk();
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ dependencies {
|
||||
compile project(":baseLibrary")
|
||||
compile project(":grammarBuilder")
|
||||
compile project(":xmlGrammar")
|
||||
testCompile "com.android.databinding:library:$version@jar"
|
||||
}
|
||||
uploadArchives {
|
||||
repositories {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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("}")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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, "."));
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user