Move expression parsing to Annotation Processing stage.

Change-Id: Ibf1e9c02856212c20300f10e4c63b96ec33b7a13
This commit is contained in:
George Mount
2015-02-25 14:13:10 -08:00
parent 2af44f8417
commit 81536a4da5
21 changed files with 736 additions and 488 deletions

View File

@@ -28,6 +28,7 @@ import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
@@ -41,8 +42,7 @@ import javax.tools.StandardLocation;
@SupportedAnnotationTypes({"android.binding.Bindable"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ProcessBindable extends AbstractProcessor {
private boolean mFileGenerated;
Intermediate mProperties = new IntermediateV1();
public ProcessBindable() {
}
@@ -55,24 +55,25 @@ public class ProcessBindable extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (mFileGenerated) {
return false;
}
Intermediate properties = readIntermediateFile();
for (Element element : roundEnv.getElementsAnnotatedWith(Bindable.class)) {
TypeElement enclosing = (TypeElement) element.getEnclosingElement();
properties.cleanProperties(enclosing.getQualifiedName().toString());
}
for (Element element : roundEnv.getElementsAnnotatedWith(Bindable.class)) {
TypeElement enclosing = (TypeElement) element.getEnclosingElement();
Element enclosingElement = element.getEnclosingElement();
ElementKind kind = enclosingElement.getKind();
if (kind != ElementKind.CLASS && kind != ElementKind.INTERFACE) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Bindable must be on a member field or method. The enclosing type is " +
enclosingElement.getKind(), element);
continue;
}
TypeElement enclosing = (TypeElement) enclosingElement;
String name = getPropertyName(element);
if (name != null) {
properties.addProperty(enclosing.getQualifiedName().toString(), name);
mProperties.addProperty(enclosing.getQualifiedName().toString(), name);
}
}
writeIntermediateFile(properties);
generateBR(properties);
mFileGenerated = true;
if (roundEnv.processingOver()) {
writeIntermediateFile(mProperties);
generateBR(mProperties);
}
return true;
}
@@ -294,8 +295,6 @@ public class ProcessBindable extends AbstractProcessor {
private interface Intermediate {
void captureProperties(Set<String> properties);
void cleanProperties(String className);
void addProperty(String className, String propertyName);
}
@@ -311,11 +310,6 @@ public class ProcessBindable extends AbstractProcessor {
}
}
@Override
public void cleanProperties(String className) {
mProperties.remove(className);
}
@Override
public void addProperty(String className, String propertyName) {
HashSet<String> properties = mProperties.get(className);

View File

@@ -1,15 +1,21 @@
package com.android.databinding.annotationprocessor;
import com.android.databinding.CompilerChef;
import com.android.databinding.store.ResourceBundle;
import com.android.databinding.writer.AnnotationJavaFileWriter;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import android.binding.BinderBundle;
import android.binding.BindingAppInfo;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
@@ -21,60 +27,79 @@ import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
@SupportedAnnotationTypes({"android.binding.BinderBundle"})
@SupportedAnnotationTypes({"android.binding.BinderBundle", "android.binding.BindingAppInfo"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ProcessExpressions extends AbstractProcessor {
private boolean mGenerationComplete;
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (mGenerationComplete) {
return false;
ResourceBundle resourceBundle = null;
for (Element element : roundEnv.getElementsAnnotatedWith(BindingAppInfo.class)) {
if (element.getKind() != ElementKind.CLASS) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"BindingAppInfo associated with wrong type. Should be a class.", element);
continue;
}
BindingAppInfo appInfo = element.getAnnotation(BindingAppInfo.class);
if (resourceBundle == null) {
resourceBundle = new ResourceBundle(appInfo.applicationPackage());
processLayouts(resourceBundle, roundEnv);
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"BindingAppInfo must be applied to only one class.", element);
}
}
String binderBundle64 = null;
return true;
}
private void processLayouts(ResourceBundle resourceBundle, RoundEnvironment roundEnv) {
Unmarshaller unmarshaller = null;
for (Element element : roundEnv.getElementsAnnotatedWith(BinderBundle.class)) {
if (element.getKind() != ElementKind.CLASS) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"BinderBundle associated with wrong type. Should be a class.", element);
continue;
}
TypeElement binderBundleClass = (TypeElement) element;
if (!"BinderInfo".equals(binderBundleClass.getQualifiedName().toString())) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Only a generated class may use @BinderBundle attribute.", element);
continue;
}
BinderBundle binderBundle = binderBundleClass.getAnnotation(BinderBundle.class);
binderBundle64 = binderBundle.value();
}
if (binderBundle64 != null) {
ByteArrayInputStream in = null;
try {
if (unmarshaller == null) {
JAXBContext context =
JAXBContext.newInstance(ResourceBundle.LayoutFileBundle.class);
unmarshaller = context.createUnmarshaller();
}
BinderBundle binderBundle = element.getAnnotation(BinderBundle.class);
String binderBundle64 = binderBundle.value();
byte[] buf = Base64.decodeBase64(binderBundle64);
in = new ByteArrayInputStream(buf);
AnnotationJavaFileWriter annotationJavaFileWriter =
new AnnotationJavaFileWriter(processingEnv);
CompilerChef compilerChef = CompilerChef.createChef(in, annotationJavaFileWriter);
if (compilerChef.hasAnythingToGenerate()) {
compilerChef.writeViewBinders();
compilerChef.writeDbrFile();
}
} catch (IOException e) {
Reader reader = new InputStreamReader(in);
ResourceBundle.LayoutFileBundle layoutFileBundle
= (ResourceBundle.LayoutFileBundle)
unmarshaller.unmarshal(reader);
resourceBundle
.addLayoutBundle(layoutFileBundle, layoutFileBundle.getLayoutId());
} catch (Exception e) {
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Could not generate Binders from binder data store. " +
e.getLocalizedMessage());
} catch (ClassNotFoundException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Error generating Binders from binder data store. " +
e.getLocalizedMessage());
"Could not generate Binders from binder data store: " +
stringWriter.getBuffer().toString(), element);
} finally {
if (in != null) {
IOUtils.closeQuietly(in);
}
}
}
mGenerationComplete = true;
return true;
CompilerChef compilerChef = CompilerChef.createChef(resourceBundle,
new AnnotationJavaFileWriter(processingEnv));
compilerChef.writeDbrFile();
compilerChef.writeViewBinderInterfaces();
compilerChef.writeViewBinders();
}
}

View File

@@ -21,6 +21,5 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Bindable {
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.binding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface BindingAppInfo {
String buildId();
String applicationPackage();
}

View File

@@ -36,6 +36,7 @@ dependencies {
compile 'org.apache.commons:commons-io:1.3.2'
compile 'com.google.guava:guava:18.0'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
compile 'commons-codec:commons-codec:1.10'
compile project(":baseLibrary")
compile project(":grammarBuilder")
compile project(":xmlGrammar")

View File

@@ -13,122 +13,46 @@
package com.android.databinding;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.android.databinding.store.LayoutFileParser;
import com.android.databinding.store.ResourceBundle;
import com.android.databinding.util.L;
import com.android.databinding.writer.DataBinderWriter;
import com.android.databinding.writer.JavaFileWriter;
import org.apache.commons.io.IOUtils;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
/**
* Chef class for compiler.
*
* Different build systems can initiate a version of this to handle their work
*/
public class CompilerChef {
public static final String RESOURCE_BUNDLE_FILE_NAME = "BinderInfo.java";
private JavaFileWriter mFileWriter;
private LayoutFileParser mLayoutFileParser;
private ResourceBundle mResourceBundle;
private DataBinder mDataBinder;
private String mAppPackage;
private List<File> mResourceFolders;
private CompilerChef() {
}
public static CompilerChef createChef(String appPkg, List<File> resourceFolders,
JavaFileWriter fileWriter) {
public static CompilerChef createChef(ResourceBundle bundle, JavaFileWriter fileWriter) {
CompilerChef chef = new CompilerChef();
chef.mAppPackage = appPkg;
chef.mResourceFolders = resourceFolders;
chef.mResourceBundle = bundle;
chef.mFileWriter = fileWriter;
chef.mResourceBundle.validateMultiResLayouts();
return chef;
}
public static CompilerChef createChef(InputStream inputStream, JavaFileWriter fileWriter)
throws IOException, ClassNotFoundException {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(inputStream);
return createChef((ResourceBundle) ois.readObject(), fileWriter);
} finally {
IOUtils.closeQuietly(ois);
}
public ResourceBundle getResourceBundle() {
return mResourceBundle;
}
public static CompilerChef createChef(ResourceBundle resourceBundle, JavaFileWriter fileWriter) {
CompilerChef chef = new CompilerChef();
chef.mResourceBundle = resourceBundle;
chef.mFileWriter = fileWriter;
chef.mAppPackage = resourceBundle.getAppPackage();
return chef;
}
public void exportResourceBundle(OutputStream outputStream) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(outputStream);
oos.writeObject(mResourceBundle);
} catch (IOException e) {
IOUtils.closeQuietly(oos);
}
}
public boolean processResources()
throws ParserConfigurationException, SAXException, XPathExpressionException,
IOException {
if (mResourceBundle != null) {
return false; // already processed
}
mResourceBundle = new ResourceBundle();
mResourceBundle.setAppPackage(mAppPackage);
mLayoutFileParser = new LayoutFileParser();
int layoutId = 0;
for (File resFolder : Iterables.filter(mResourceFolders, fileExists)) {
for (File layoutFolder : resFolder.listFiles(layoutFolderFilter)) {
for (File xmlFile : layoutFolder.listFiles(xmlFileFilter)) {
final ResourceBundle.LayoutFileBundle bundle = mLayoutFileParser
.parseXml(xmlFile, mAppPackage, layoutId);
if (bundle != null && !bundle.isEmpty()) {
mResourceBundle.addLayoutBundle(bundle, layoutId);
layoutId ++;
}
}
}
}
mResourceBundle.validateMultiResLayouts();
return true;
}
public void ensureDataBinder() {
if (mDataBinder == null) {
mDataBinder = new DataBinder(mResourceBundle);
mDataBinder.setFileWriter(mFileWriter);
}
}
public boolean hasAnythingToGenerate() {
L.d("checking if we have anything to genreate. bundle size: %s",
L.d("checking if we have anything to generate. bundle size: %s",
mResourceBundle == null ? -1 : mResourceBundle.getLayoutBundles().size());
return mResourceBundle != null && mResourceBundle.getLayoutBundles().size() > 0;
}
@@ -147,30 +71,9 @@ public class CompilerChef {
ensureDataBinder();
mDataBinder.writerBinderInterfaces();
}
public void writeViewBinders() {
ensureDataBinder();
mDataBinder.writeBinders();
}
private final Predicate<File> fileExists = new Predicate<File>() {
@Override
public boolean apply(File input) {
return input.exists() && input.canRead();
}
};
private final FilenameFilter layoutFolderFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith("layout");
}
};
private final FilenameFilter xmlFileFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".xml");
}
};
}

View File

@@ -54,8 +54,9 @@ public class DataBinder {
if (writtenFiles.contains(interfaceName)) {
continue;
}
mFileWriter.writeToFile(layoutBinder.getPackage() + "." + layoutBinder.getInterfaceName(),
mFileWriter.writeToFile(layoutBinder.getPackage() + "." + interfaceName,
layoutBinder.writeViewBinderInterface());
writtenFiles.add(interfaceName);
}
}

View File

@@ -20,6 +20,9 @@ import com.google.common.base.Preconditions;
import com.android.databinding.expr.Expr;
import com.android.databinding.expr.ExprModel;
import com.android.databinding.expr.StaticIdentifierExpr;
import com.android.databinding.reflection.ModelAnalyzer;
import com.android.databinding.reflection.ModelClass;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.tree.ParseTree;
@@ -72,8 +75,17 @@ public class ExpressionVisitor extends BindingExpressionBaseVisitor<Expr> {
@Override
public Expr visitDotOp(@NotNull BindingExpressionParser.DotOpContext ctx) {
return mModel.field(ctx.expression().accept(this),
ctx.Identifier().getSymbol().getText());
ModelAnalyzer analyzer = ModelAnalyzer.getInstance();
ModelClass modelClass = analyzer.findClass(ctx.getText(), mModel.getImports());
if (modelClass == null) {
return mModel.field(ctx.expression().accept(this),
ctx.Identifier().getSymbol().getText());
} else {
String name = modelClass.toJavaCode();
StaticIdentifierExpr expr = mModel.staticIdentifier(name);
expr.setUserDefinedType(name);
return expr;
}
}
@Override

View File

@@ -0,0 +1,155 @@
/*
* 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.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.android.databinding.store.LayoutFileParser;
import com.android.databinding.store.ResourceBundle;
import com.android.databinding.writer.JavaFileWriter;
import org.apache.commons.codec.binary.Base64;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
/**
* Processes the layout XML, stripping the binding attributes and elements
* and writes the information into an annotated class file for the annotation
* processor to work with.
*/
public class LayoutXmlProcessor {
public static final String RESOURCE_BUNDLE_PACKAGE = "com.android.databinding.layouts.";
public static final String APPLICATION_INFO_CLASS = "ApplicationBindingInfo";
private final JavaFileWriter mFileWriter;
private final ResourceBundle mResourceBundle;
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) {
mFileWriter = fileWriter;
mResourceBundle = new ResourceBundle(applicationPackage);
mResourceFolders = resourceFolders;
}
public boolean processResources()
throws ParserConfigurationException, SAXException, XPathExpressionException,
IOException {
if (mProcessingComplete) {
return false;
}
LayoutFileParser layoutFileParser = new LayoutFileParser();
int layoutId = 0;
for (File resFolder : Iterables.filter(mResourceFolders, fileExists)) {
for (File layoutFolder : resFolder.listFiles(layoutFolderFilter)) {
for (File xmlFile : layoutFolder.listFiles(xmlFileFilter)) {
final ResourceBundle.LayoutFileBundle bindingLayout = layoutFileParser
.parseXml(xmlFile, mResourceBundle.getAppPackage(), layoutId);
if (bindingLayout != null && !bindingLayout.isEmpty()) {
mResourceBundle.addLayoutBundle(bindingLayout, layoutId);
layoutId++;
}
}
}
}
mProcessingComplete = true;
return true;
}
public void writeIntermediateFile() throws JAXBException {
if (mWritten) {
return;
}
JAXBContext context = JAXBContext.newInstance(ResourceBundle.LayoutFileBundle.class);
Marshaller marshaller = context.createMarshaller();
writeAppInfo(marshaller);
for (List<ResourceBundle.LayoutFileBundle> layouts : mResourceBundle.getLayoutBundles()
.values()) {
for (ResourceBundle.LayoutFileBundle layout : layouts) {
writeAnnotatedFile(layout, marshaller);
}
}
mWritten = true;
}
private void writeAnnotatedFile(ResourceBundle.LayoutFileBundle layout, Marshaller marshaller)
throws JAXBException {
StringBuilder className = new StringBuilder(layout.getFileName());
className.append('-').append(layout.getDirectory());
for (int i = className.length() - 1; i >= 0; i--) {
char c = className.charAt(i);
if (c == '-') {
className.deleteCharAt(i);
c = Character.toUpperCase(className.charAt(i));
className.setCharAt(i, c);
}
}
className.setCharAt(0, Character.toUpperCase(className.charAt(0)));
StringWriter writer = new StringWriter();
marshaller.marshal(layout, writer);
String xml = writer.getBuffer().toString();
String classString = "import android.binding.BinderBundle;\n\n" +
"@BinderBundle(\"" +
Base64.encodeBase64String(xml.getBytes(StandardCharsets.UTF_8)) +
"\")\n" +
"public class " + className + " {}\n";
mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + className, classString);
}
private void writeAppInfo(Marshaller marshaller) {
String classString = "import android.binding.BindingAppInfo;\n\n" +
"@BindingAppInfo(buildId=\"" + mBuildId + "\", applicationPackage=\"" +
mResourceBundle.getAppPackage() + "\")\n" +
"public class " + APPLICATION_INFO_CLASS + " {}\n";
mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + APPLICATION_INFO_CLASS, classString);
}
private final Predicate<File> fileExists = new Predicate<File>() {
@Override
public boolean apply(File input) {
return input.exists() && input.canRead();
}
};
private final FilenameFilter layoutFolderFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith("layout");
}
};
private final FilenameFilter xmlFileFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".xml");
}
};
}

View File

@@ -46,8 +46,8 @@ public class BracketExpr extends Expr {
mAccessor = BracketAccessor.MAP;
} else {
throw new IllegalArgumentException("Cannot determine variable type used in [] " +
"expression. Cast the value to List, ObservableList, Map, " +
"Cursor, or array.");
"expression. Cast the value to List, Map, " +
"or array. Type detected: " + targetType.toJavaCode());
}
return targetType.getComponentType();
}

View File

@@ -575,36 +575,6 @@ abstract public class Expr {
}
}
protected void replaceStaticIdentifiers(ModelAnalyzer modelAnalyzer) {
for (int i = 0; i < mChildren.size(); i++) {
Expr child = mChildren.get(i);
String packageName = child.asPackage();
if (packageName != null) {
ModelClass modelClass = modelAnalyzer.findClass(packageName,
getModel().getImports());
if (modelClass != null) {
child.removeParentAndUnregisterIfOrphan(this);
StaticIdentifierExpr staticAccessExpr = getModel().staticIdentifier(packageName);
staticAccessExpr.setUserDefinedType(packageName);
staticAccessExpr.getParents().add(this);
mChildren.set(i, staticAccessExpr);
}
}
}
}
private void removeParentAndUnregisterIfOrphan(Expr parent) {
while (mParents.remove(parent)) {
}
if (getParents().isEmpty()) {
for (Expr expr : mChildren) {
expr.removeParentAndUnregisterIfOrphan(this);
}
mChildren.clear();
getModel().unregister(this);
}
}
protected String asPackage() {
return null;
}

View File

@@ -94,7 +94,6 @@ public class FieldAccessExpr extends Expr {
@Override
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
if (mGetter == null) {
replaceStaticIdentifiers(modelAnalyzer);
Expr child = getChild();
child.resolveType(modelAnalyzer);
boolean isStatic = child instanceof StaticIdentifierExpr;

View File

@@ -45,7 +45,6 @@ public class MethodCallExpr extends Expr {
@Override
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
if (mGetter == null) {
replaceStaticIdentifiers(modelAnalyzer);
List<ModelClass> args = new ArrayList<>();
for (Expr expr : getArgs()) {
args.add(expr.getResolvedType());

View File

@@ -82,35 +82,91 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
.build();
public final ProcessingEnvironment processingEnv;
public final TypeMirror listType;
public final TypeMirror mapType;
public final TypeMirror stringType;
public final TypeMirror objectType;
private final AnnotationClass mObservableType;
private final AnnotationClass mObservableListType;
private final AnnotationClass mObservableMapType;
private final AnnotationClass[] mObservableFieldTypes;
private final AnnotationClass mIViewDataBinderType;
private AnnotationClass mListType;
private AnnotationClass mMapType;
private AnnotationClass mStringType;
private AnnotationClass mObjectType;
private AnnotationClass mObservableType;
private AnnotationClass mObservableListType;
private AnnotationClass mObservableMapType;
private AnnotationClass[] mObservableFieldTypes;
private AnnotationClass mIViewDataBinderType;
public AnnotationAnalyzer(ProcessingEnvironment processingEnvironment) {
processingEnv = processingEnvironment;
instance = this;
Types typeUtil = processingEnv.getTypeUtils();
listType = typeUtil.erasure(findType(LIST_CLASS_NAME).asType());
mapType = typeUtil.erasure(findType(MAP_CLASS_NAME).asType());
stringType = typeUtil.erasure(findType(STRING_CLASS_NAME).asType());
objectType = typeUtil.erasure(findType(OBJECT_CLASS_NAME).asType());
mObservableType = new AnnotationClass(findType(OBSERVABLE_CLASS_NAME).asType());
mObservableListType = new AnnotationClass(typeUtil.erasure(
findType(OBSERVABLE_LIST_CLASS_NAME).asType()));
mObservableMapType = new AnnotationClass(typeUtil.erasure(
findType(OBSERVABLE_MAP_CLASS_NAME).asType()));
mIViewDataBinderType = new AnnotationClass(findType(I_VIEW_DATA_BINDER).asType());
mObservableFieldTypes = new AnnotationClass[OBSERVABLE_FIELDS.length];
for (int i = 0; i < OBSERVABLE_FIELDS.length; i++) {
mObservableFieldTypes[i] = new AnnotationClass(findType(OBSERVABLE_FIELDS[i]).asType());
}
public AnnotationClass getListType() {
if (mListType == null) {
mListType = loadClassErasure(LIST_CLASS_NAME);
}
return mListType;
}
public AnnotationClass getMapType() {
if (mMapType == null) {
mMapType = loadClassErasure(MAP_CLASS_NAME);
}
return mMapType;
}
public AnnotationClass getStringType() {
if (mStringType == null) {
mStringType = new AnnotationClass(findType(STRING_CLASS_NAME).asType());
}
return mStringType;
}
public AnnotationClass getObjectType() {
if (mObjectType == null) {
mObjectType = new AnnotationClass(findType(OBJECT_CLASS_NAME).asType());
}
return mObjectType;
}
private AnnotationClass getObservableType() {
if (mObservableType == null) {
mObservableType = new AnnotationClass(findType(OBSERVABLE_CLASS_NAME).asType());
}
return mObservableType;
}
private AnnotationClass getObservableListType() {
if (mObservableListType == null) {
mObservableListType = loadClassErasure(OBSERVABLE_LIST_CLASS_NAME);
}
return mObservableListType;
}
private AnnotationClass getObservableMapType() {
if (mObservableMapType == null) {
mObservableMapType = loadClassErasure(OBSERVABLE_MAP_CLASS_NAME);
}
return mObservableMapType;
}
private AnnotationClass getIViewDataBinderType() {
if (mIViewDataBinderType == null) {
mIViewDataBinderType = new AnnotationClass(findType(I_VIEW_DATA_BINDER).asType());
}
return mIViewDataBinderType;
}
private AnnotationClass loadClassErasure(String className) {
Types typeUtils = getTypeUtils();
return new AnnotationClass(typeUtils.erasure(findType(className).asType()));
}
private AnnotationClass[] getObservableFieldTypes() {
if (mObservableFieldTypes == null) {
mObservableFieldTypes = new AnnotationClass[OBSERVABLE_FIELDS.length];
for (int i = 0; i < OBSERVABLE_FIELDS.length; i++) {
mObservableFieldTypes[i] = loadClassErasure(OBSERVABLE_FIELDS[i]);
}
}
return mObservableFieldTypes;
}
private TypeElement findType(String type) {
@@ -119,7 +175,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
@Override
public boolean isDataBinder(ModelClass modelClass) {
return mIViewDataBinderType.isAssignableFrom(modelClass);
return getIViewDataBinderType().isAssignableFrom(modelClass);
}
@Override
@@ -154,7 +210,7 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
}
}
}
String message = "cannot find method " + name + " at class " + clazz.toJavaCode();
String message = "cannot find method '" + name + "' in class " + clazz.toJavaCode();
printMessage(Diagnostic.Kind.ERROR, message);
throw new IllegalArgumentException(message);
}
@@ -162,16 +218,16 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
@Override
public boolean isObservable(ModelClass modelClass) {
AnnotationClass annotationClass = (AnnotationClass)modelClass;
return mObservableType.isAssignableFrom(annotationClass) ||
mObservableListType.isAssignableFrom(annotationClass) ||
mObservableMapType.isAssignableFrom(annotationClass);
return getObservableType().isAssignableFrom(annotationClass) ||
getObservableListType().isAssignableFrom(annotationClass) ||
getObservableMapType().isAssignableFrom(annotationClass);
}
@Override
public boolean isObservableField(ModelClass modelClass) {
AnnotationClass annotationClass = (AnnotationClass)modelClass;
AnnotationClass erasure = new AnnotationClass(getTypeUtils().erasure(annotationClass.mTypeMirror));
for (AnnotationClass observableField : mObservableFieldTypes) {
for (AnnotationClass observableField : getObservableFieldTypes()) {
if (observableField.isAssignableFrom(erasure)) {
return true;
}
@@ -337,12 +393,20 @@ public class AnnotationAnalyzer extends ModelAnalyzer {
if (className.indexOf('.') < 0) {
// try java.lang.
String javaLangClass = "java.lang." + className;
TypeElement javaLang = elementUtils.getTypeElement(javaLangClass);
if (javaLang != null) {
return javaLang;
try {
TypeElement javaLang = elementUtils.getTypeElement(javaLangClass);
if (javaLang != null) {
return javaLang;
}
} catch (Exception e) {
// try the normal way
}
}
return elementUtils.getTypeElement(className);
try {
return elementUtils.getTypeElement(className);
} catch (Exception e) {
return null;
}
}
private ArrayList<String> splitTemplateParameters(String templateParameters) {

View File

@@ -59,13 +59,13 @@ public class AnnotationClass implements ModelClass {
if (isArray()) {
component = ((ArrayType) mTypeMirror).getComponentType();
} else if (isList()) {
DeclaredType listType = findInterface(getListType());
DeclaredType listType = findInterface(getListType().mTypeMirror);
if (listType == null) {
return null;
}
component = listType.getTypeArguments().get(0);
} else {
DeclaredType mapType = findInterface(getMapType());
DeclaredType mapType = findInterface(getMapType().mTypeMirror);
if (mapType == null) {
return null;
}
@@ -111,18 +111,18 @@ public class AnnotationClass implements ModelClass {
@Override
public boolean isList() {
Types typeUtil = getTypeUtils();
return typeUtil.isAssignable(typeUtil.erasure(mTypeMirror), getListType());
return typeUtil.isAssignable(typeUtil.erasure(mTypeMirror), getListType().mTypeMirror);
}
@Override
public boolean isMap() {
Types typeUtil = getTypeUtils();
return typeUtil.isAssignable(typeUtil.erasure(mTypeMirror), getMapType());
return typeUtil.isAssignable(typeUtil.erasure(mTypeMirror), getMapType().mTypeMirror);
}
@Override
public boolean isString() {
return getTypeUtils().isSameType(mTypeMirror, getStringType());
return getTypeUtils().isSameType(mTypeMirror, getStringType().mTypeMirror);
}
@Override
@@ -196,7 +196,7 @@ public class AnnotationClass implements ModelClass {
@Override
public boolean isObject() {
return getTypeUtils().isSameType(mTypeMirror, getObjectType());
return getTypeUtils().isSameType(mTypeMirror, getObjectType().mTypeMirror);
}
@Override
@@ -230,10 +230,8 @@ public class AnnotationClass implements ModelClass {
if (that == null) {
return false;
}
AnnotationClass thisBoxed = box();
AnnotationClass thatBoxed = (AnnotationClass) that.box();
final TypeMirror thatType = thatBoxed.mTypeMirror;
return getTypeUtils().isAssignable(thatType, thisBoxed.mTypeMirror);
AnnotationClass thatAnnotationClass = (AnnotationClass) that;
return getTypeUtils().isAssignable(thatAnnotationClass.mTypeMirror, this.mTypeMirror);
}
@Override
@@ -296,20 +294,20 @@ public class AnnotationClass implements ModelClass {
return AnnotationAnalyzer.instance.processingEnv.getElementUtils();
}
private static TypeMirror getListType() {
return AnnotationAnalyzer.instance.listType;
private static AnnotationClass getListType() {
return AnnotationAnalyzer.instance.getListType();
}
private static TypeMirror getMapType() {
return AnnotationAnalyzer.instance.mapType;
private static AnnotationClass getMapType() {
return AnnotationAnalyzer.instance.getMapType();
}
private static TypeMirror getStringType() {
return AnnotationAnalyzer.instance.stringType;
private static AnnotationClass getStringType() {
return AnnotationAnalyzer.instance.getStringType();
}
private static TypeMirror getObjectType() {
return AnnotationAnalyzer.instance.objectType;
private static AnnotationClass getObjectType() {
return AnnotationAnalyzer.instance.getObjectType();
}
private static void printMessage(Diagnostic.Kind kind, String message) {

View File

@@ -61,21 +61,15 @@ public class LayoutFileParser {
if (original == null) {
return null;
}
return parseOriginalXml(original, pkg);
}
private ResourceBundle.LayoutFileBundle parseOriginalXml(File xml, String pkg)
throws ParserConfigurationException, IOException, SAXException,
XPathExpressionException {
ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle(ParserHelper.INSTANCE$.stripExtension(xml.getName()));
L.d("parsing file %s", xml.getAbsolutePath());
bundle.setTransientFile(xml);
ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle(
ParserHelper.INSTANCE$.stripExtension(xml.getName()), layoutId,
xml.getParentFile().getName());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilder builder = factory.newDocumentBuilder();
final Document doc = builder.parse(xml);
final Document doc = builder.parse(original);
final XPathFactory xPathFactory = XPathFactory.newInstance();
final XPath xPath = xPathFactory.newXPath();
@@ -110,41 +104,49 @@ public class LayoutFileParser {
final List<Node> bindingNodes = getBindingNodes(doc, xPath);
L.d("number of binding nodes %d", bindingNodes.size());
int tagNumber = 1;
for (Node parent : bindingNodes) {
NamedNodeMap attributes = parent.getAttributes();
Node id = attributes.getNamedItem("android:id");
if (id != null) {
String nodeName = parent.getNodeName();
String layoutName = null;
final String fullClassName;
if ("include".equals(nodeName)) {
// get the layout attribute
final Node includedLayout = attributes.getNamedItem("layout");
Preconditions.checkNotNull(includedLayout, "must include a layout");
final String includeValue = includedLayout.getNodeValue();
Preconditions.checkArgument(includeValue.startsWith(LAYOUT_PREFIX));
// if user is binding something there, there MUST be a layout file to be
// generated.
layoutName = includeValue.substring(LAYOUT_PREFIX.length());
L.d("replaced node name to " + nodeName);
fullClassName = pkg + ".generated." + ParserHelper.INSTANCE$.toClassName(layoutName) + "Binder";
} else {
fullClassName = getFullViewClassName(nodeName);
}
final ResourceBundle.BindingTargetBundle bindingTargetBundle = bundle.createBindingTarget(id.getNodeValue(), fullClassName, true);
bindingTargetBundle.setIncludedLayout(layoutName);
int attrCount = attributes.getLength();
for (int i = 0; i < attrCount; i ++) {
final Node attr = attributes.item(i);
String value = attr.getNodeValue();
if (value.charAt(0) == '@' && value.charAt(1) == '{' &&
value.charAt(value.length() - 1) == '}') {
final String strippedValue = value.substring(2, value.length() - 1);
bindingTargetBundle.addBinding(attr.getNodeName(), strippedValue);
}
String nodeName = parent.getNodeName();
String className;
String includedLayoutName = null;
final Node id = attributes.getNamedItem("android:id");
if ("include".equals(nodeName)) {
if (id == null) {
L.e("<include> must have android:id attribute with binding expressions.");
throw new RuntimeException("<include> must have android:id attribute " +
"with binding expressions.");
}
// get the layout attribute
final Node includedLayout = attributes.getNamedItem("layout");
Preconditions.checkNotNull(includedLayout, "must include a layout");
final String includeValue = includedLayout.getNodeValue();
Preconditions.checkArgument(includeValue.startsWith(LAYOUT_PREFIX));
// if user is binding something there, there MUST be a layout file to be
// generated.
String layoutName = includeValue.substring(LAYOUT_PREFIX.length());
className = pkg + ".generated." +
ParserHelper.INSTANCE$.toClassName(layoutName) + "Binder";
includedLayoutName = layoutName;
} else {
throw new RuntimeException("data binding requires id for now.");
className = getFullViewClassName(nodeName);
}
final Node originalTag = attributes.getNamedItem("android:tag");
final String tag = String.valueOf(tagNumber++);
final ResourceBundle.BindingTargetBundle bindingTargetBundle =
bundle.createBindingTarget(id == null ? null : id.getNodeValue(),
className, true, tag, originalTag == null ? null : originalTag.getNodeValue());
bindingTargetBundle.setIncludedLayout(includedLayoutName);
final int attrCount = attributes.getLength();
for (int i = 0; i < attrCount; i ++) {
final Node attr = attributes.item(i);
String value = attr.getNodeValue();
if (value.charAt(0) == '@' && value.charAt(1) == '{' &&
value.charAt(value.length() - 1) == '}') {
final String strippedValue = value.substring(2, value.length() - 1);
bindingTargetBundle.addBinding(attr.getNodeName(), strippedValue);
}
}
}
@@ -199,7 +201,7 @@ public class LayoutFileParser {
private File stripFileAndGetOriginal(File xml, String binderId)
throws ParserConfigurationException, IOException, SAXException,
XPathExpressionException {
L.d("parsing resourceY file %s", xml.getAbsolutePath());
L.d("parsing resource file %s", xml.getAbsolutePath());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(xml);

View File

@@ -20,7 +20,6 @@ import com.google.common.collect.Iterables;
import com.android.databinding.util.L;
import com.android.databinding.util.ParserHelper;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
@@ -29,6 +28,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* This is a serializable class that can keep the result of parsing layout files.
*/
@@ -38,6 +46,10 @@ public class ResourceBundle implements Serializable {
private HashMap<String, List<LayoutFileBundle>> mLayoutBundles = new HashMap<>();
public ResourceBundle(String appPackage) {
mAppPackage = appPackage;
}
public void addLayoutBundle(LayoutFileBundle bundle, int layoutId) {
Preconditions.checkArgument(bundle.mFileName != null, "File bundle must have a name");
if (!mLayoutBundles.containsKey(bundle.mFileName)) {
@@ -47,10 +59,6 @@ public class ResourceBundle implements Serializable {
mLayoutBundles.get(bundle.mFileName).add(bundle);
}
public void setAppPackage(String appPackage) {
mAppPackage = appPackage;
}
public HashMap<String, List<LayoutFileBundle>> getLayoutBundles() {
return mLayoutBundles;
}
@@ -154,7 +162,8 @@ public class ResourceBundle implements Serializable {
for (Map.Entry<String, String> viewType : viewTypes.entrySet()) {
BindingTargetBundle target = bundle.getBindingTargetById(viewType.getKey());
if (target == null) {
bundle.createBindingTarget(viewType.getKey(), viewType.getValue(), false);
bundle.createBindingTarget(viewType.getKey(), viewType.getValue(), false,
null, null);
} else {
L.d("setting interface type on %s (%s) as %s", target.mId, target.mFullClassName, viewType.getValue());
target.setInterfaceType(viewType.getValue());
@@ -168,8 +177,8 @@ public class ResourceBundle implements Serializable {
final String configName;
if (bundle.hasVariations()) {
// append configuration specifiers.
final String parentFileName = bundle.mTransientFile.getParentFile().getName();
L.d("parent file for %s is %s", bundle.mTransientFile.getName(), parentFileName);
final String parentFileName = bundle.mDirectory;
L.d("parent file for %s is %s", bundle.getFileName(), parentFileName);
if ("layout".equals(parentFileName)) {
configName = "";
} else {
@@ -183,20 +192,39 @@ public class ResourceBundle implements Serializable {
}
}
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="Layout")
public static class LayoutFileBundle implements Serializable {
private int mLayoutId;
private String mFileName;
@XmlAttribute(name="layoutId", required = true)
public int mLayoutId;
@XmlAttribute(name="layout", required = true)
public String mFileName;
private String mConfigName;
private boolean mHasVariations;
transient private File mTransientFile;
private Map<String, String> mVariables = new HashMap<>();
@XmlAttribute(name="directory", required = true)
public String mDirectory;
public boolean mHasVariations;
private Map<String, String> mImports = new HashMap<>();
@XmlElement(name="Variables")
@XmlJavaTypeAdapter(NameTypeAdapter.class)
public Map<String, String> mVariables = new HashMap<>();
private List<BindingTargetBundle> mBindingTargetBundles = new ArrayList<>();
public LayoutFileBundle(String fileName) {
@XmlElement(name="Imports")
@XmlJavaTypeAdapter(NameTypeAdapter.class)
public Map<String, String> mImports = new HashMap<>();
@XmlElementWrapper(name="Targets")
@XmlElement(name="Target")
public List<BindingTargetBundle> mBindingTargetBundles = new ArrayList<>();
// for XML binding
public LayoutFileBundle() {
}
public LayoutFileBundle(String fileName, int layoutId, String directory) {
mFileName = fileName;
mLayoutId = layoutId;
mDirectory = directory;
}
public void addVariable(String name, String type) {
@@ -207,12 +235,10 @@ public class ResourceBundle implements Serializable {
mImports.put(alias, type);
}
public void setTransientFile(File transientFile) {
mTransientFile = transientFile;
}
public BindingTargetBundle createBindingTarget(String id, String fullClassName, boolean used) {
BindingTargetBundle target = new BindingTargetBundle(id, fullClassName, used);
public BindingTargetBundle createBindingTarget(String id, String fullClassName,
boolean used, String tag, String originalTag) {
BindingTargetBundle target = new BindingTargetBundle(id, fullClassName, used, tag,
originalTag);
mBindingTargetBundles.add(target);
return target;
}
@@ -242,6 +268,10 @@ public class ResourceBundle implements Serializable {
return mConfigName;
}
public String getDirectory() {
return mDirectory;
}
public boolean hasVariations() {
return mHasVariations;
}
@@ -259,19 +289,49 @@ public class ResourceBundle implements Serializable {
}
}
public static class BindingTargetBundle implements Serializable {
@XmlAccessorType(XmlAccessType.NONE)
public static class MarshalledNameType {
@XmlAttribute(name="type", required = true)
public String type;
private String mId;
private String mFullClassName;
private boolean mUsed;
private List<BindingBundle> mBindingBundleList = new ArrayList<>();
private String mIncludedLayout;
@XmlAttribute(name="name", required = true)
public String name;
}
public static class MarshalledMapType {
public List<MarshalledNameType> entries;
}
@XmlAccessorType(XmlAccessType.NONE)
public static class BindingTargetBundle implements Serializable {
// public for XML serialization
@XmlAttribute(name="id")
public String mId;
@XmlAttribute(name="tag", required = true)
public String mTag;
@XmlAttribute(name="originalTag")
public String mOriginalTag;
@XmlAttribute(name="boundClass", required = true)
public String mFullClassName;
public boolean mUsed = true;
@XmlElementWrapper(name="Expressions")
@XmlElement(name="Expression")
public List<BindingBundle> mBindingBundleList = new ArrayList<>();
@XmlAttribute(name="include")
public String mIncludedLayout;
private String mInterfaceType;
public BindingTargetBundle(String id, String fullClassName, boolean used) {
// For XML serialization
public BindingTargetBundle() {}
public BindingTargetBundle(String id, String fullClassName, boolean used,
String tag, String originalTag) {
mId = id;
mFullClassName = fullClassName;
mUsed = used;
mTag = tag;
mOriginalTag = originalTag;
}
public void addBinding(String name, String expr) {
@@ -314,24 +374,67 @@ public class ResourceBundle implements Serializable {
return mInterfaceType;
}
@XmlAccessorType(XmlAccessType.NONE)
public static class BindingBundle implements Serializable {
private String mName;
private String mExpr;
public BindingBundle() {}
public BindingBundle(String name, String expr) {
mName = name;
mExpr = expr;
}
@XmlAttribute(name="attribute", required=true)
public String getName() {
return mName;
}
@XmlAttribute(name="text", required=true)
public String getExpr() {
return mExpr;
}
public void setName(String name) {
mName = name;
}
public void setExpr(String expr) {
mExpr = expr;
}
}
}
private final static class NameTypeAdapter
extends XmlAdapter<MarshalledMapType, Map<String, String>> {
@Override
public HashMap<String, String> unmarshal(MarshalledMapType v) throws Exception {
HashMap<String, String> map = new HashMap<>();
if (v.entries != null) {
for (MarshalledNameType entry : v.entries) {
map.put(entry.name, entry.type);
}
}
return map;
}
@Override
public MarshalledMapType marshal(Map<String, String> v) throws Exception {
if (v.isEmpty()) {
return null;
}
MarshalledMapType marshalled = new MarshalledMapType();
marshalled.entries = new ArrayList<>();
for (String name : v.keySet()) {
MarshalledNameType nameType = new MarshalledNameType();
nameType.name = name;
nameType.type = v.get(name);
marshalled.entries.add(nameType);
}
return marshalled;
}
}
}

View File

@@ -265,49 +265,55 @@ public class SetterStore {
attribute = attribute.substring(colon + 1);
}
}
HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
MethodDescription adapter = null;
String setterName = null;
ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports);
ModelClass bestViewType = null;
ModelClass bestValueType = null;
if (bestSetterMethod != null) {
bestViewType = bestSetterMethod.getDeclaringClass();
bestValueType = bestSetterMethod.getParameterTypes()[0];
setterName = bestSetterMethod.getName();
}
if (viewType != null) {
HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports);
ModelClass bestViewType = null;
ModelClass bestValueType = null;
if (bestSetterMethod != null) {
bestViewType = bestSetterMethod.getDeclaringClass();
bestValueType = bestSetterMethod.getParameterTypes()[0];
setterName = bestSetterMethod.getName();
}
if (adapters != null) {
for (AccessorKey key : adapters.keySet()) {
try {
ModelClass adapterViewType = mClassAnalyzer.findClass(key.viewType, imports);
if (adapterViewType.isAssignableFrom(viewType)) {
try {
ModelClass adapterValueType = mClassAnalyzer.findClass(key.valueType,
imports);
boolean isBetterView = bestViewType == null ||
bestValueType.isAssignableFrom(adapterValueType);
if (isBetterParameter(valueType, adapterValueType, bestValueType,
isBetterView, imports)) {
bestViewType = adapterViewType;
bestValueType = adapterValueType;
adapter = adapters.get(key);
if (adapters != null) {
for (AccessorKey key : adapters.keySet()) {
try {
ModelClass adapterViewType = mClassAnalyzer
.findClass(key.viewType, imports);
if (adapterViewType.isAssignableFrom(viewType)) {
try {
ModelClass adapterValueType = mClassAnalyzer
.findClass(key.valueType,
imports);
boolean isBetterView = bestViewType == null ||
bestValueType.isAssignableFrom(adapterValueType);
if (isBetterParameter(valueType, adapterValueType, bestValueType,
isBetterView, imports)) {
bestViewType = adapterViewType;
bestValueType = adapterValueType;
adapter = adapters.get(key);
}
} catch (Exception e) {
printMessage(Diagnostic.Kind.NOTE,
"Unknown class: " + key.valueType);
}
} catch (Exception e) {
printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + key.valueType);
}
} catch (Exception e) {
printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + key.viewType);
}
} catch (Exception e) {
printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + key.viewType);
}
}
}
MethodDescription conversionMethod = getConversionMethod(valueType, bestValueType, imports);
if (conversionMethod != null) {
valueExpression = conversionMethod.type + "." + conversionMethod.method + "(" +
valueExpression + ")";
MethodDescription conversionMethod = getConversionMethod(valueType, bestValueType,
imports);
if (conversionMethod != null) {
valueExpression = conversionMethod.type + "." + conversionMethod.method + "(" +
valueExpression + ")";
}
}
if (adapter == null) {
if (setterName == null) {

View File

@@ -29,6 +29,7 @@ import com.android.databinding.toPosition
import com.android.databinding.toEndPosition
import java.util.Comparator
import com.google.common.base.Preconditions
import java.util.ArrayList
/**
* Ugly inefficient class to strip unwanted tags from XML.
@@ -38,37 +39,63 @@ object XmlEditor {
val reservedElementNames = arrayListOf("variable", "import")
var rootNodeContext: XMLParser.ElementContext? = null
var rootNodeHasTag = false
val visitor = object : XMLParserBaseVisitor<MutableList<Pair<Position, Position>>>() {
override fun visitAttribute(ctx: XMLParser.AttributeContext): MutableList<Pair<Position, Position>>? {
data class LayoutXmlElements(val start: Position, val end: Position,
val isTag: kotlin.Boolean, val isReserved: kotlin.Boolean,
val attributes: MutableList<LayoutXmlElements>?)
val visitor = object : XMLParserBaseVisitor<MutableList<LayoutXmlElements>>() {
override fun visitAttribute(ctx: XMLParser.AttributeContext): MutableList<LayoutXmlElements>? {
log { "attr:${ctx.attrName.getText()} ${ctx.attrValue.getText()} . parent: ${ctx.getParent()}" }
if (ctx.getParent() == rootNodeContext && ctx.attrName.getText() == "android:tag") {
rootNodeHasTag = true
}
if (ctx.attrName.getText().startsWith("bind:")) {
return arrayListOf(Pair(ctx.getStart().toPosition(), ctx.getStop().toEndPosition()))
} else if (ctx.attrValue.getText().startsWith("\"@{") && ctx.attrValue.getText().endsWith("}\"")) {
return arrayListOf(Pair(ctx.getStart().toPosition(), ctx.getStop().toEndPosition()))
val isTag = ctx.attrName.getText().equals("android:tag")
if (isTag || ctx.attrName.getText().startsWith("bind:") ||
(ctx.attrValue.getText().startsWith("\"@{") && ctx.attrValue.getText().endsWith("}\""))) {
return arrayListOf(LayoutXmlElements(ctx.getStart().toPosition(),
ctx.getStop().toEndPosition(), isTag, false, null))
}
//log{"visiting attr: ${ctx.getText()} at location ${ctx.getStart().toS()} ${ctx.getStop().toS()}"}
return super<XMLParserBaseVisitor>.visitAttribute(ctx)
}
override fun visitElement(ctx: XMLParser.ElementContext): MutableList<Pair<Position, Position>>? {
override fun visitElement(ctx: XMLParser.ElementContext): MutableList<LayoutXmlElements>? {
log { "elm ${ctx.elmName.getText()} || ${ctx.Name()} paren : ${ctx.getParent()}" }
if (rootNodeContext == null) {
rootNodeContext = ctx
}
if (reservedElementNames.contains(ctx.elmName?.getText()) || ctx.elmName.getText().startsWith("bind:")) {
return arrayListOf(Pair(ctx.getStart().toPosition(), ctx.getStop().toEndPosition()))
return arrayListOf(LayoutXmlElements(ctx.getStart().toPosition(),
ctx.getStop().toEndPosition(), false, true, arrayListOf()));
}
val elements = super<XMLParserBaseVisitor>.visitElement(ctx);
if (elements != null && !elements.isEmpty()) {
val attributes : MutableList<LayoutXmlElements> = arrayListOf();
val others : MutableList<LayoutXmlElements> = arrayListOf();
elements.forEach {
if (it.attributes == null) {
attributes.add(it);
} else {
others.add(it);
}
}
if (attributes.isEmpty()) {
return elements;
} else {
val element = LayoutXmlElements(ctx.getStart().toPosition(),
ctx.getStop().toEndPosition(), false, false, attributes)
others.add(0, element);
return others;
}
} else {
return elements;
}
return super< XMLParserBaseVisitor>.visitElement(ctx)
}
override fun defaultResult(): MutableList<Pair<Position, Position>>? = arrayListOf()
override fun defaultResult(): MutableList<LayoutXmlElements>? = arrayListOf()
override fun aggregateResult(aggregate: MutableList<Pair<Position, Position>>?, nextResult: MutableList<Pair<Position, Position>>?): MutableList<Pair<Position, Position>>? =
override fun aggregateResult(aggregate: MutableList<LayoutXmlElements>?, nextResult: MutableList<LayoutXmlElements>?): MutableList<LayoutXmlElements>? =
if (aggregate == null) {
nextResult
} else if (nextResult == null) {
@@ -103,13 +130,13 @@ object XmlEditor {
lines.forEach { out.appendln(it) }
// TODO we probably don't need to sort
val sorted = parsedExpr.sortBy(object : Comparator<Pair<Position, Position>> {
override fun compare(o1: Pair<Position, Position>, o2: Pair<Position, Position>): Int {
val lineCmp = o1.first.line.compareTo(o2.first.charIndex)
val sorted = parsedExpr.sortBy(object : Comparator<LayoutXmlElements> {
override fun compare(o1: LayoutXmlElements, o2: LayoutXmlElements): Int {
val lineCmp = o1.start.line.compareTo(o2.start.charIndex)
if (lineCmp != 0) {
return lineCmp
}
return o1.first.line.compareTo(o2.first.charIndex)
return o1.start.line.compareTo(o2.start.charIndex)
}
})
var lineStarts = arrayListOf(0)
@@ -120,15 +147,50 @@ object XmlEditor {
}
val separator = System.lineSeparator().charAt(0)
val noTag : ArrayList<Pair<String, LayoutXmlElements>> = ArrayList()
var bindingIndex = 1
val rootNodeEnd = toIndex(lineStarts, rootNodeContext!!.getStop().toPosition())
sorted.forEach {
val posStart = lineStarts[it.first.line] + it.first.charIndex
val posEnd = lineStarts[it.second.line] + it.second.charIndex
for ( i in posStart..(posEnd - 1)) {
if (out.charAt(i) != separator) {
out.setCharAt(i, ' ')
if (it.isReserved) {
log {"Replacing reserved tag at ${it.start} to ${it.end}"}
replace(out, lineStarts, it, "", separator);
} else if (it.attributes != null) {
log {"Found attribute for tag at ${it.start} to ${it.end}"}
if (it.attributes.size() == 1 && it.attributes.get(0).isTag) {
log {"only android:tag"}
// no binding, just tag -- don't replace anything
} else {
var replaced = false
val tag : String
if (toIndex(lineStarts, it.start) < rootNodeEnd) {
tag = ""
} else {
val index = bindingIndex++;
tag = "android:tag=\"${index}\"";
}
it.attributes.forEach {
if (!replaced && tagWillFit(it.start, it.end, tag)) {
replace(out, lineStarts, it, tag, separator)
replaced = true;
} else {
replace(out, lineStarts, it, "", separator)
}
}
if (!replaced) {
noTag.add(0, Pair(tag, it))
}
}
}
}
noTag.forEach {
val element = it.second
val tag = it.first;
val end = toIndex(lineStarts, element.end)
out.insert(end, " ${tag}")
}
Log.d{"new tag to set: $newTag"}
if (newTag != null) {
Preconditions.checkState(rootNodeBounds.first.line != rootNodeBounds.second.line,
@@ -140,4 +202,29 @@ object XmlEditor {
return out.toString()
}
}
fun tagWillFit(start: Position, end: Position, tag: String) : kotlin.Boolean {
if (start.line != end.line) {
return end.charIndex >= tag.length();
}
return (end.charIndex - start.charIndex >= tag.length());
}
fun replace(out : StringBuilder, lineStarts : ArrayList<kotlin.Int>, element : LayoutXmlElements,
tag : String, separator : kotlin.Char) {
val posStart = toIndex(lineStarts, element.start)
val posEnd = toIndex(lineStarts, element.end)
log {"replacing '${out.substring(posStart, posEnd)}' with '${tag}'"}
val spaceEnd = posEnd - tag.length();
for ( i in posStart..(spaceEnd - 1)) {
if (out.charAt(i) != separator) {
out.setCharAt(i, ' ')
}
}
out.replace(spaceEnd, posEnd, tag);
}
fun toIndex(lineStarts : ArrayList<kotlin.Int>, pos : Position) : kotlin.Int {
return lineStarts[pos.line] + pos.charIndex;
}
}

View File

@@ -658,25 +658,12 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
kcode("package ${layoutBinder.getPackage()};") {
nl("import android.binding.Bindable;")
nl("import com.android.databinding.library.IViewDataBinder;")
// TODO: use real types instead of imports. When this moves
// to the annotation processor, we'll be able to grab types better.
val imports = model.getImports();
imports.keySet().forEach {
val className = imports.get(it);
if (className != null) {
val lastDotIndex = className.lastIndexOf('.');
if (lastDotIndex >= 0 && lastDotIndex < className.length() - 1 &&
it.equals(className.substring(lastDotIndex + 1))) {
nl("import ${className};")
}
}
}
nl("public interface ${interfaceName} extends IViewDataBinder {")
variables.forEach {
if (it.getUserDefinedType() != null) {
tab("@Bindable")
tab("public void ${it.setterName}(${it.getUserDefinedType()} ${it.readableUniqueName});")
val type = it.getResolvedType().toJavaCode();
tab("public void ${it.setterName}(${type} ${it.readableUniqueName});")
}
}
layoutBinder.getBindingTargets().forEach {

View File

@@ -63,7 +63,7 @@ class DataBinderPlugin : Plugin<Project> {
}
}
var xmlParserChef: CompilerChef by Delegates.notNull()
var xmlProcessor: LayoutXmlProcessor by Delegates.notNull()
var project : Project by Delegates.notNull()
var generatedBinderSrc : File by Delegates.notNull()
@@ -92,14 +92,13 @@ class DataBinderPlugin : Plugin<Project> {
override fun apply(project: Project?) {
if (project == null) return
val generateAttr = MethodClosure(this, "generateAttr")
val generateBrFile = MethodClosure(this, "generateBrFile")
val generateBinders = MethodClosure(this, "generateBinders")
val generateIntermediateFile = MethodClosure(this, "generateIntermediateFile")
val preprocessLayoutFiles = MethodClosure(this, "preprocessLayoutFiles")
this.project = project
project.afterEvaluate {
// TODO read from app
val variants = arrayListOf("Debug")
xmlParserChef = createChef(project)
xmlProcessor = createXmlProcessor(project)
log("after eval")
//processDebugResources
variants.forEach { variant ->
@@ -109,19 +108,8 @@ class DataBinderPlugin : Plugin<Project> {
// }
val processResTasks = it.getTasksByName("process${variant}Resources", true)
processResTasks.forEach {
it.doFirst (generateAttr)
}
val generateSourcesTasks = it.getTasksByName("generate${variant}Sources", true)
log("generate sources tasks ${generateSourcesTasks}")
generateSourcesTasks.forEach {
it.doFirst(generateBrFile)
}
val compileTasks = it.getTasksByName("compile${variant}Java", true)
log("compile tasks ${compileTasks}")
compileTasks.forEach {
it.doFirst(MethodClosure(this, "cleanBinderOutFolder"))
it.doFirst(generateBinders)
it.doFirst(preprocessLayoutFiles)
it.doLast(generateIntermediateFile)
}
}
}
@@ -131,7 +119,7 @@ class DataBinderPlugin : Plugin<Project> {
System.out.println("PLOG: $s")
}
fun createChef(p: Project): CompilerChef {
fun createXmlProcessor(p: Project): LayoutXmlProcessor {
val ss = p.getExtensions().getByName("android") as AppExtension
androidJar = File(ss.getSdkDirectory().getAbsolutePath() + "/platforms/${ss.getCompileSdkVersion()}/android.jar")
log("creating parser!")
@@ -183,7 +171,7 @@ class DataBinderPlugin : Plugin<Project> {
dexTask.doFirst(MethodClosure(this, "preDexAnalysis"))
val writerOutBase = codeGenTargetFolder.getAbsolutePath();
fileWriter = GradleFileWriter(writerOutBase)
return CompilerChef.createChef(packageName, resourceFolders, fileWriter)
return LayoutXmlProcessor(packageName, resourceFolders, fileWriter)
}
@@ -204,85 +192,12 @@ class DataBinderPlugin : Plugin<Project> {
cpFiles.addAll(jCompileTask.getClasspath().getFiles())
//project.task("compileGenerated", MethodClosure(this, "compileGenerated"))
}
fun compileGenerated(o : Any?) {
val fis = FileInputStream(serializedBinderBundlePath)
val compilerChef = CompilerChef.createChef(
fis, GradleFileWriter(viewBinderSource.getAbsolutePath())
)
IOUtils.closeQuietly(fis)
log("compiling generated. ${compilerChef.hasAnythingToGenerate()}")
if (!compilerChef.hasAnythingToGenerate()) {
return
}
val compiler = ToolProvider.getSystemJavaCompiler()
val fileManager = compiler.getStandardFileManager(null, null, null)
val javaCompileTask = variantData.javaCompileTask
val dexTask = variantData.dexTask
//fileWriter.outputBase = viewBinderSource.getAbsolutePath()
compilerChef.writeViewBinders()
compilerChef.writeDbrFile()
viewBinderCompileOutput.mkdirs()
val cpFiles = arrayListOf<File>()
cpFiles.addAll(dexTask.getInputFiles())
cpFiles.addAll(javaCompileTask.getClasspath().getFiles())
cpFiles.add(javaCompileTask.getDestinationDir())
cpFiles.add(androidJar)
val filesToCompile = FileUtils.listFiles(viewBinderSource, array("java"), true).map { (it as File).getAbsolutePath() }
log("files to compile ${filesToCompile}")
val fileObjects = fileManager.getJavaFileObjectsFromStrings(filesToCompile)
val optionList = arrayListOf<String>()
// set compiler's classpath to be same as the runtime's
optionList.addAll(Arrays.asList("-classpath",cpFiles.map{it.getAbsolutePath()}.join(":")))
optionList.add("-verbose")
optionList.add("-d")
optionList.add(viewBinderCompileOutput.getAbsolutePath())
log("compile options: ${optionList}")
val javac = compiler.getTask(null, fileManager, null, optionList, null, fileObjects) as JavaCompiler.CompilationTask
val compileResult = javac.call()
if (!compileResult) {
throw RuntimeException("cannot compile generated files. see error for details")
}
fun preprocessLayoutFiles(o: Any?) {
xmlProcessor.processResources()
}
fun generateAttr(o: Any?) {
xmlParserChef.processResources()
}
fun cleanBinderOutFolder(o : Any?) {
log("cleaning out folder pre-compile of $o")
viewBinderSource.mkdirs()
FileUtils.cleanDirectory(viewBinderSource)
viewBinderCompileOutput.mkdirs()
FileUtils.cleanDirectory(viewBinderCompileOutput)
saveResourceBundle(xmlParserChef)
}
fun generateBrFile(o: Any?) {
xmlParserChef.processResources()
log("generating BR ${o}")
xmlParserChef.writeViewBinderInterfaces()
}
fun saveResourceBundle(chef : CompilerChef) {
File(serializedBinderBundlePath).getParentFile().mkdirs()
val bundleStream = ByteArrayOutputStream();
chef.exportResourceBundle(bundleStream)
IOUtils.closeQuietly(bundleStream)
val fw = FileWriter(serializedBinderBundlePath);
fw.write("import android.binding.BinderBundle;\n\n")
fw.write("@BinderBundle(\"");
fw.write(Base64.encodeBase64String(bundleStream.toByteArray()));
fw.write("\")\n");
fw.write("public class BinderInfo {}\n");
IOUtils.closeQuietly(fw);
}
fun generateBinders(o: Any?) {
log("generating binders ${o}")
// parser.writeViewBinders()
// parser.writeDbrFile()
fun generateIntermediateFile(o: Any?) {
xmlProcessor.writeIntermediateFile()
}
}