Move expression parsing to Annotation Processing stage.
Change-Id: Ibf1e9c02856212c20300f10e4c63b96ec33b7a13
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user