diff --git a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindable.java b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindable.java index f964d6372f5fb..ca554db788012 100644 --- a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindable.java +++ b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessBindable.java @@ -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 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 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 properties = mProperties.get(className); diff --git a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessExpressions.java b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessExpressions.java index 8138383dd9ad0..cfa0415616d60 100644 --- a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessExpressions.java +++ b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessExpressions.java @@ -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 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(); } } diff --git a/tools/data-binding/baseLibrary/src/main/java/android/binding/Bindable.java b/tools/data-binding/baseLibrary/src/main/java/android/binding/Bindable.java index ee5d6b04a4a36..80464b453b101 100644 --- a/tools/data-binding/baseLibrary/src/main/java/android/binding/Bindable.java +++ b/tools/data-binding/baseLibrary/src/main/java/android/binding/Bindable.java @@ -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 { } diff --git a/tools/data-binding/baseLibrary/src/main/java/android/binding/BindingAppInfo.java b/tools/data-binding/baseLibrary/src/main/java/android/binding/BindingAppInfo.java new file mode 100644 index 0000000000000..42a6f3d1d76d9 --- /dev/null +++ b/tools/data-binding/baseLibrary/src/main/java/android/binding/BindingAppInfo.java @@ -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(); +} diff --git a/tools/data-binding/compiler/build.gradle b/tools/data-binding/compiler/build.gradle index 216b14ad1b349..96917aecff9ef 100644 --- a/tools/data-binding/compiler/build.gradle +++ b/tools/data-binding/compiler/build.gradle @@ -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") diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/CompilerChef.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/CompilerChef.java index fc6858661d99f..015c7e02f9cab 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/CompilerChef.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/CompilerChef.java @@ -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 mResourceFolders; private CompilerChef() { - } - public static CompilerChef createChef(String appPkg, List 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 fileExists = new Predicate() { - @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"); - } - }; } diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java index 9acf52ffdbf67..fc29b9e84ed02 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java @@ -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); } } diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/ExpressionVisitor.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/ExpressionVisitor.java index 356d629d769dd..9adca33b02031 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/ExpressionVisitor.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/ExpressionVisitor.java @@ -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 { @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 diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java new file mode 100644 index 0000000000000..cb4860e5d392d --- /dev/null +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java @@ -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 mResourceFolders; + + public LayoutXmlProcessor(String applicationPackage, List 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 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 fileExists = new Predicate() { + @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"); + } + }; +} diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/BracketExpr.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/BracketExpr.java index 7b5752b8be395..a46e4d4588853 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/BracketExpr.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/BracketExpr.java @@ -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(); } diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/Expr.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/Expr.java index 170a417485cfe..d2b95143c8483 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/Expr.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/Expr.java @@ -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; } diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/FieldAccessExpr.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/FieldAccessExpr.java index fdff0233838a6..a44c8d8c778c8 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/FieldAccessExpr.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/FieldAccessExpr.java @@ -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; diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/MethodCallExpr.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/MethodCallExpr.java index 5c3bd04113997..fcec227a9d3a6 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/MethodCallExpr.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/expr/MethodCallExpr.java @@ -45,7 +45,6 @@ public class MethodCallExpr extends Expr { @Override protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { if (mGetter == null) { - replaceStaticIdentifiers(modelAnalyzer); List args = new ArrayList<>(); for (Expr expr : getArgs()) { args.add(expr.getResolvedType()); diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationAnalyzer.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationAnalyzer.java index 41fead91d516e..194c020b0ebe4 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationAnalyzer.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationAnalyzer.java @@ -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 splitTemplateParameters(String templateParameters) { diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationClass.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationClass.java index 5d5f813b35c73..a73105a2c91b6 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationClass.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationClass.java @@ -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) { diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/store/LayoutFileParser.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/LayoutFileParser.java index 38764ba1ccf12..7ad7455d17589 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/store/LayoutFileParser.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/LayoutFileParser.java @@ -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 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(" must have android:id attribute with binding expressions."); + throw new RuntimeException(" 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); diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/store/ResourceBundle.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/ResourceBundle.java index 3c36141f96519..bd0a4629a087b 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/store/ResourceBundle.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/ResourceBundle.java @@ -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> 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> getLayoutBundles() { return mLayoutBundles; } @@ -154,7 +162,8 @@ public class ResourceBundle implements Serializable { for (Map.Entry 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 mVariables = new HashMap<>(); + @XmlAttribute(name="directory", required = true) + public String mDirectory; + public boolean mHasVariations; - private Map mImports = new HashMap<>(); + @XmlElement(name="Variables") + @XmlJavaTypeAdapter(NameTypeAdapter.class) + public Map mVariables = new HashMap<>(); - private List mBindingTargetBundles = new ArrayList<>(); - public LayoutFileBundle(String fileName) { + @XmlElement(name="Imports") + @XmlJavaTypeAdapter(NameTypeAdapter.class) + public Map mImports = new HashMap<>(); + + @XmlElementWrapper(name="Targets") + @XmlElement(name="Target") + public List 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 mBindingBundleList = new ArrayList<>(); - private String mIncludedLayout; + @XmlAttribute(name="name", required = true) + public String name; + } + + public static class MarshalledMapType { + public List 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 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> { + + @Override + public HashMap unmarshal(MarshalledMapType v) throws Exception { + HashMap 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 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; + } + } } diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java index 4706594c7cd85..6eb1e38f8a66f 100644 --- a/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java +++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java @@ -265,49 +265,55 @@ public class SetterStore { attribute = attribute.substring(colon + 1); } } - HashMap 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 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) { diff --git a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/util/XmlEditor.kt b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/util/XmlEditor.kt index c524d535330af..7c49a20c56fd7 100644 --- a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/util/XmlEditor.kt +++ b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/util/XmlEditor.kt @@ -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>>() { - override fun visitAttribute(ctx: XMLParser.AttributeContext): MutableList>? { + data class LayoutXmlElements(val start: Position, val end: Position, + val isTag: kotlin.Boolean, val isReserved: kotlin.Boolean, + val attributes: MutableList?) + + val visitor = object : XMLParserBaseVisitor>() { + override fun visitAttribute(ctx: XMLParser.AttributeContext): MutableList? { 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.visitAttribute(ctx) } - override fun visitElement(ctx: XMLParser.ElementContext): MutableList>? { + override fun visitElement(ctx: XMLParser.ElementContext): MutableList? { 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.visitElement(ctx); + if (elements != null && !elements.isEmpty()) { + val attributes : MutableList = arrayListOf(); + val others : MutableList = 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>? = arrayListOf() + override fun defaultResult(): MutableList? = arrayListOf() - override fun aggregateResult(aggregate: MutableList>?, nextResult: MutableList>?): MutableList>? = + override fun aggregateResult(aggregate: MutableList?, nextResult: MutableList?): MutableList? = 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> { - override fun compare(o1: Pair, o2: Pair): Int { - val lineCmp = o1.first.line.compareTo(o2.first.charIndex) + val sorted = parsedExpr.sortBy(object : Comparator { + 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> = 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() } -} \ No newline at end of file + + 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, 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, pos : Position) : kotlin.Int { + return lineStarts[pos.line] + pos.charIndex; + } +} diff --git a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/writer/LayoutBinderWriter.kt b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/writer/LayoutBinderWriter.kt index e14bb6b536a5c..aae01d111d6e8 100644 --- a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/writer/LayoutBinderWriter.kt +++ b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/writer/LayoutBinderWriter.kt @@ -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 { diff --git a/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt b/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt index e423eed0376f1..01a99273c3d55 100644 --- a/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt +++ b/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt @@ -63,7 +63,7 @@ class DataBinderPlugin : Plugin { } } - 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 { 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 { // } 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 { 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 { 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 { 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() - 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() - // 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() } }