summaryrefslogtreecommitdiff
path: root/compiler/src/main/java/android/databinding/annotationprocessor
diff options
context:
space:
mode:
authorYigit Boyar <yboyar@google.com>2015-05-21 15:49:58 -0700
committerYigit Boyar <yboyar@google.com>2015-05-22 11:11:42 -0700
commitb1356339eaa6c8e967e4fc1dc283b82909a1208d (patch)
tree0644d70a89f74d261294ce91fd2d83d9aa02d2fa /compiler/src/main/java/android/databinding/annotationprocessor
parent79ae2e5b4612d3af10bcadb235c86b564b5f97c1 (diff)
downloaddata-binding-b1356339eaa6c8e967e4fc1dc283b82909a1208d.tar.gz
Merge projects for an easier release
This CL merges annotation processor, xml grammer and grammer builder into compiler project. It also adds logic to pass gradle's --debug parameter to the annotation processor so that we can grab logs when necessary. Bug: 21327802 Change-Id: I1d07002519a6b95de3cdc0891bd00f461f73e8ea
Diffstat (limited to 'compiler/src/main/java/android/databinding/annotationprocessor')
-rw-r--r--compiler/src/main/java/android/databinding/annotationprocessor/AnnotationUtil.java41
-rw-r--r--compiler/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java55
-rw-r--r--compiler/src/main/java/android/databinding/annotationprocessor/ProcessBindable.java300
-rw-r--r--compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java128
-rw-r--r--compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java198
-rw-r--r--compiler/src/main/java/android/databinding/annotationprocessor/ProcessMethodAdapters.java214
6 files changed, 936 insertions, 0 deletions
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/AnnotationUtil.java b/compiler/src/main/java/android/databinding/annotationprocessor/AnnotationUtil.java
new file mode 100644
index 00000000..95761a4a
--- /dev/null
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/AnnotationUtil.java
@@ -0,0 +1,41 @@
+/*
+ * 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.databinding.annotationprocessor;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+
+class AnnotationUtil {
+
+ /**
+ * Returns only the elements that are annotated with the given class. For some reason
+ * RoundEnvironment is returning elements annotated by other annotations.
+ */
+ static List<Element> getElementsAnnotatedWith(RoundEnvironment roundEnv,
+ Class<? extends Annotation> annotationClass) {
+ ArrayList<Element> elements = new ArrayList<Element>();
+ for (Element element : roundEnv.getElementsAnnotatedWith(annotationClass)) {
+ if (element.getAnnotation(annotationClass) != null) {
+ elements.add(element);
+ }
+ }
+ return elements;
+ }
+}
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java b/compiler/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java
new file mode 100644
index 00000000..68ef8e70
--- /dev/null
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java
@@ -0,0 +1,55 @@
+/*
+ * 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.databinding.annotationprocessor;
+
+import com.google.common.base.Preconditions;
+
+import android.databinding.BindingBuildInfo;
+import android.databinding.tool.util.L;
+
+import java.lang.annotation.Annotation;
+
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+
+public class BuildInfoUtil {
+ private static BindingBuildInfo sCached;
+ public static BindingBuildInfo load(RoundEnvironment roundEnvironment) {
+ if (sCached == null) {
+ sCached = extractNotNull(roundEnvironment, BindingBuildInfo.class);
+ if (sCached != null) {
+ L.setDebugLog(sCached.enableDebugLogs());
+ }
+ }
+ return sCached;
+ }
+
+ private static <T extends Annotation> T extractNotNull(RoundEnvironment roundEnv,
+ Class<T> annotationClass) {
+ T result = null;
+ for (Element element : roundEnv.getElementsAnnotatedWith(annotationClass)) {
+ final T info = element.getAnnotation(annotationClass);
+ if (info == null) {
+ continue; // It gets confused between BindingAppInfo and BinderBundle
+ }
+ Preconditions.checkState(result == null, "Should have only one %s",
+ annotationClass.getCanonicalName());
+ result = info;
+ }
+ return result;
+ }
+}
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessBindable.java b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessBindable.java
new file mode 100644
index 00000000..ebfd7c14
--- /dev/null
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessBindable.java
@@ -0,0 +1,300 @@
+/*
+ * 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.databinding.annotationprocessor;
+
+import com.google.common.base.Preconditions;
+
+import android.databinding.Bindable;
+import android.databinding.BindingBuildInfo;
+import android.databinding.tool.CompilerChef.BindableHolder;
+import android.databinding.tool.util.GenerationalClassUtil;
+import android.databinding.tool.util.L;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+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;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.Types;
+
+// binding app info and library info are necessary to trigger this.
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+public class ProcessBindable extends ProcessDataBinding.ProcessingStep implements BindableHolder {
+ private static final String INTERMEDIATE_FILE_EXT = "-br.bin";
+ Intermediate mProperties;
+ HashMap<String, HashSet<String>> mLayoutVariables = new HashMap<String, HashSet<String>>();
+
+ @Override
+ public boolean onHandleStep(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv,
+ BindingBuildInfo buildInfo) {
+ if (mProperties == null) {
+ mProperties = new IntermediateV1(buildInfo.modulePackage());
+ mergeLayoutVariables();
+ mLayoutVariables.clear();
+ TypeElement observableType = processingEnv.getElementUtils().
+ getTypeElement("android.databinding.Observable");
+ Types typeUtils = processingEnv.getTypeUtils();
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, Bindable.class)) {
+ Element enclosingElement = element.getEnclosingElement();
+ ElementKind kind = enclosingElement.getKind();
+ if (kind != ElementKind.CLASS && kind != ElementKind.INTERFACE) {
+ L.e("Bindable must be on a member field or method. The enclosing type is %s",
+ enclosingElement.getKind());
+ }
+ TypeElement enclosing = (TypeElement) enclosingElement;
+ if (!typeUtils.isAssignable(enclosing.asType(), observableType.asType())) {
+ L.e("Bindable must be on a member in an Observable class. %s is not Observable",
+ enclosingElement.getSimpleName());
+ }
+ String name = getPropertyName(element);
+ if (name != null) {
+ Preconditions
+ .checkNotNull(mProperties, "Must receive app / library info before "
+ + "Bindable fields.");
+ mProperties.addProperty(enclosing.getQualifiedName().toString(), name);
+ }
+ }
+ GenerationalClassUtil.writeIntermediateFile(processingEnv,
+ mProperties.getPackage(),
+ createIntermediateFileName(mProperties.getPackage()), mProperties);
+ generateBRClasses(!buildInfo.isLibrary(), mProperties.getPackage());
+ }
+ return false;
+ }
+
+ @Override
+ public void addVariable(String variableName, String containingClassName) {
+ HashSet<String> variableNames = mLayoutVariables.get(containingClassName);
+ if (variableNames == null) {
+ variableNames = new HashSet<String>();
+ mLayoutVariables.put(containingClassName, variableNames);
+ }
+ variableNames.add(variableName);
+ }
+
+ @Override
+ public void onProcessingOver(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+ }
+
+ private String createIntermediateFileName(String appPkg) {
+ return appPkg + INTERMEDIATE_FILE_EXT;
+ }
+
+ private void generateBRClasses(boolean useFinalFields, String pkg) {
+ L.d("************* Generating BR file %s. use final: %s", pkg, useFinalFields);
+ HashSet<String> properties = new HashSet<String>();
+ mProperties.captureProperties(properties);
+ List<Intermediate> previousIntermediates = loadPreviousBRFiles();
+ for (Intermediate intermediate : previousIntermediates) {
+ intermediate.captureProperties(properties);
+ }
+ writeBRClass(useFinalFields, pkg, properties);
+ if (useFinalFields) {
+ // generate BR for all previous packages
+ for (Intermediate intermediate : previousIntermediates) {
+ writeBRClass(true, intermediate.getPackage(),
+ properties);
+ }
+ }
+ }
+
+ private void writeBRClass(boolean useFinalFields, String pkg, HashSet<String> properties) {
+ ArrayList<String> sortedProperties = new ArrayList<String>();
+ sortedProperties.addAll(properties);
+ Collections.sort(sortedProperties);
+ StringBuilder out = new StringBuilder();
+ String modifier = "public static " + (useFinalFields ? "final" : "") + " int ";
+ out.append("package " + pkg + ";\n\n" +
+ "public class BR {\n" +
+ " " + modifier + "_all = 0;\n"
+ );
+ int id = 0;
+ for (String property : sortedProperties) {
+ id++;
+ out.append(" " + modifier + property + " = " + id + ";\n");
+ }
+ out.append("}\n");
+
+ getWriter().writeToFile(pkg + ".BR", out.toString() );
+ }
+
+ private String getPropertyName(Element element) {
+ switch (element.getKind()) {
+ case FIELD:
+ return stripPrefixFromField((VariableElement) element);
+ case METHOD:
+ return stripPrefixFromMethod((ExecutableElement) element);
+ default:
+ L.e("@Bindable is not allowed on %s", element.getKind());
+ return null;
+ }
+ }
+
+ private static String stripPrefixFromField(VariableElement element) {
+ Name name = element.getSimpleName();
+ if (name.length() >= 2) {
+ char firstChar = name.charAt(0);
+ char secondChar = name.charAt(1);
+ if (name.length() > 2 && firstChar == 'm' && secondChar == '_') {
+ char thirdChar = name.charAt(2);
+ if (Character.isJavaIdentifierStart(thirdChar)) {
+ return "" + Character.toLowerCase(thirdChar) +
+ name.subSequence(3, name.length());
+ }
+ } else if ((firstChar == 'm' && Character.isUpperCase(secondChar)) ||
+ (firstChar == '_' && Character.isJavaIdentifierStart(secondChar))) {
+ return "" + Character.toLowerCase(secondChar) + name.subSequence(2, name.length());
+ }
+ }
+ return name.toString();
+ }
+
+ private String stripPrefixFromMethod(ExecutableElement element) {
+ Name name = element.getSimpleName();
+ CharSequence propertyName;
+ if (isGetter(element) || isSetter(element)) {
+ propertyName = name.subSequence(3, name.length());
+ } else if (isBooleanGetter(element)) {
+ propertyName = name.subSequence(2, name.length());
+ } else {
+ L.e("@Bindable associated with method must follow JavaBeans convention %s", element);
+ return null;
+ }
+ char firstChar = propertyName.charAt(0);
+ return "" + Character.toLowerCase(firstChar) +
+ propertyName.subSequence(1, propertyName.length());
+ }
+
+ private void mergeLayoutVariables() {
+ for (String containingClass : mLayoutVariables.keySet()) {
+ for (String variable : mLayoutVariables.get(containingClass)) {
+ mProperties.addProperty(containingClass, variable);
+ }
+ }
+ }
+
+ private static boolean prefixes(CharSequence sequence, String prefix) {
+ boolean prefixes = false;
+ if (sequence.length() > prefix.length()) {
+ int count = prefix.length();
+ prefixes = true;
+ for (int i = 0; i < count; i++) {
+ if (sequence.charAt(i) != prefix.charAt(i)) {
+ prefixes = false;
+ break;
+ }
+ }
+ }
+ return prefixes;
+ }
+
+ private static boolean isGetter(ExecutableElement element) {
+ Name name = element.getSimpleName();
+ return prefixes(name, "get") &&
+ Character.isJavaIdentifierStart(name.charAt(3)) &&
+ element.getParameters().isEmpty() &&
+ element.getReturnType().getKind() != TypeKind.VOID;
+ }
+
+ private static boolean isSetter(ExecutableElement element) {
+ Name name = element.getSimpleName();
+ return prefixes(name, "set") &&
+ Character.isJavaIdentifierStart(name.charAt(3)) &&
+ element.getParameters().size() == 1 &&
+ element.getReturnType().getKind() == TypeKind.VOID;
+ }
+
+ private static boolean isBooleanGetter(ExecutableElement element) {
+ Name name = element.getSimpleName();
+ return prefixes(name, "is") &&
+ Character.isJavaIdentifierStart(name.charAt(2)) &&
+ element.getParameters().isEmpty() &&
+ element.getReturnType().getKind() == TypeKind.BOOLEAN;
+ }
+
+ private List<Intermediate> loadPreviousBRFiles() {
+ return GenerationalClassUtil
+ .loadObjects(getClass().getClassLoader(),
+ new GenerationalClassUtil.ExtensionFilter(INTERMEDIATE_FILE_EXT));
+ }
+
+ private interface Intermediate extends Serializable {
+
+ void captureProperties(Set<String> properties);
+
+ void addProperty(String className, String propertyName);
+
+ boolean hasValues();
+
+ String getPackage();
+ }
+
+ private static class IntermediateV1 implements Serializable, Intermediate {
+
+ private static final long serialVersionUID = 2L;
+
+ private String mPackage;
+ private final HashMap<String, HashSet<String>> mProperties = new HashMap<String, HashSet<String>>();
+
+ public IntermediateV1(String aPackage) {
+ mPackage = aPackage;
+ }
+
+ @Override
+ public void captureProperties(Set<String> properties) {
+ for (HashSet<String> propertySet : mProperties.values()) {
+ properties.addAll(propertySet);
+ }
+ }
+
+ @Override
+ public void addProperty(String className, String propertyName) {
+ HashSet<String> properties = mProperties.get(className);
+ if (properties == null) {
+ properties = new HashSet<String>();
+ mProperties.put(className, properties);
+ }
+ properties.add(propertyName);
+ }
+
+ @Override
+ public boolean hasValues() {
+ return !mProperties.isEmpty();
+ }
+
+ @Override
+ public String getPackage() {
+ return mPackage;
+ }
+ }
+}
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
new file mode 100644
index 00000000..944cc20a
--- /dev/null
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
@@ -0,0 +1,128 @@
+/*
+ * 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.databinding.annotationprocessor;
+
+import android.databinding.BindingBuildInfo;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.writer.AnnotationJavaFileWriter;
+import android.databinding.tool.writer.JavaFileWriter;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+
+@SupportedAnnotationTypes({
+ "android.databinding.BindingAdapter",
+ "android.databinding.Untaggable",
+ "android.databinding.BindingMethods",
+ "android.databinding.BindingConversion",
+ "android.databinding.BindingBuildInfo"}
+)
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+/**
+ * Parent annotation processor that dispatches sub steps to ensure execution order.
+ * Use initProcessingSteps to add a new step.
+ */
+public class ProcessDataBinding extends AbstractProcessor {
+ private List<ProcessingStep> mProcessingSteps;
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (mProcessingSteps == null) {
+ initProcessingSteps();
+ }
+ final BindingBuildInfo buildInfo = BuildInfoUtil.load(roundEnv);
+ if (buildInfo == null) {
+ return false;
+ }
+ boolean done = true;
+ for (ProcessingStep step : mProcessingSteps) {
+ done = step.runStep(roundEnv, processingEnv, buildInfo) && done;
+ }
+ if (roundEnv.processingOver()) {
+ for (ProcessingStep step : mProcessingSteps) {
+ step.onProcessingOver(roundEnv, processingEnv, buildInfo);
+ }
+ }
+ return done;
+ }
+
+ private void initProcessingSteps() {
+ ProcessBindable processBindable = new ProcessBindable();
+ mProcessingSteps = Arrays.asList(
+ new ProcessMethodAdapters(),
+ new ProcessExpressions(processBindable),
+ processBindable
+ );
+ AnnotationJavaFileWriter javaFileWriter = new AnnotationJavaFileWriter(processingEnv);
+ for (ProcessingStep step : mProcessingSteps) {
+ step.mJavaFileWriter = javaFileWriter;
+ }
+ }
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ ModelAnalyzer.setProcessingEnvironment(processingEnv);
+ }
+
+ /**
+ * To ensure execution order and binding build information, we use processing steps.
+ */
+ public abstract static class ProcessingStep {
+ private boolean mDone;
+ private JavaFileWriter mJavaFileWriter;
+
+ protected JavaFileWriter getWriter() {
+ return mJavaFileWriter;
+ }
+
+ private boolean runStep(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment,
+ BindingBuildInfo buildInfo) {
+ if (mDone) {
+ return true;
+ }
+ mDone = onHandleStep(roundEnvironment, processingEnvironment, buildInfo);
+ return mDone;
+ }
+
+ /**
+ * Invoked in each annotation processing step.
+ *
+ * @return True if it is done and should never be invoked again.
+ */
+ abstract public boolean onHandleStep(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment,
+ BindingBuildInfo buildInfo);
+
+ /**
+ * Invoked when processing is done. A good place to generate the output if the
+ * processor requires multiple steps.
+ */
+ abstract public void onProcessingOver(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment,
+ BindingBuildInfo buildInfo);
+ }
+}
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java
new file mode 100644
index 00000000..0e1c11eb
--- /dev/null
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java
@@ -0,0 +1,198 @@
+/*
+ * 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.databinding.annotationprocessor;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import android.databinding.BindingBuildInfo;
+import android.databinding.tool.CompilerChef;
+import android.databinding.tool.reflection.SdkUtil;
+import android.databinding.tool.store.ResourceBundle;
+import android.databinding.tool.util.GenerationalClassUtil;
+import android.databinding.tool.util.L;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+
+public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
+
+ private static final String LAYOUT_INFO_FILE_SUFFIX = "-layoutinfo.bin";
+
+ private final ProcessBindable mProcessBindable;
+
+ public ProcessExpressions(ProcessBindable processBindable) {
+ mProcessBindable = processBindable;
+ }
+
+
+ @Override
+ public boolean onHandleStep(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+ ResourceBundle resourceBundle;
+ SdkUtil.initialize(buildInfo.minSdk(), new File(buildInfo.sdkRoot()));
+ resourceBundle = new ResourceBundle(buildInfo.modulePackage());
+ List<Intermediate> intermediateList =
+ GenerationalClassUtil.loadObjects(getClass().getClassLoader(),
+ new GenerationalClassUtil.ExtensionFilter(LAYOUT_INFO_FILE_SUFFIX));
+ IntermediateV1 mine = createIntermediateFromLayouts(buildInfo.layoutInfoDir());
+ if (mine != null) {
+ mine.removeOverridden(intermediateList);
+ intermediateList.add(mine);
+ saveIntermediate(processingEnvironment, buildInfo, mine);
+ }
+ // generate them here so that bindable parser can read
+ try {
+ generateBinders(resourceBundle, buildInfo, intermediateList);
+ } catch (Throwable t) {
+ L.e(t, "cannot generate view binders");
+ }
+ return true;
+ }
+
+ private void saveIntermediate(ProcessingEnvironment processingEnvironment,
+ BindingBuildInfo buildInfo, IntermediateV1 intermediate) {
+ GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
+ buildInfo.modulePackage(), buildInfo.modulePackage() + LAYOUT_INFO_FILE_SUFFIX,
+ intermediate);
+ }
+
+ @Override
+ public void onProcessingOver(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+ }
+
+ private void generateBinders(ResourceBundle resourceBundle, BindingBuildInfo buildInfo,
+ List<Intermediate> intermediates)
+ throws Throwable {
+ for (Intermediate intermediate : intermediates) {
+ intermediate.appendTo(resourceBundle);
+ }
+ writeResourceBundle(resourceBundle, buildInfo.isLibrary(), buildInfo.minSdk());
+ }
+
+ private IntermediateV1 createIntermediateFromLayouts(String layoutInfoFolderPath) {
+ final File layoutInfoFolder = new File(layoutInfoFolderPath);
+ if (!layoutInfoFolder.isDirectory()) {
+ L.d("layout info folder does not exist, skipping for %s", layoutInfoFolderPath);
+ return null;
+ }
+ IntermediateV1 result = new IntermediateV1();
+ for (File layoutFile : layoutInfoFolder.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".xml");
+ }
+ })) {
+ try {
+ result.addEntry(layoutFile.getName(), FileUtils.readFileToString(layoutFile));
+ } catch (IOException e) {
+ L.e(e, "cannot load layout file information. Try a clean build");
+ }
+ }
+ return result;
+ }
+
+ private void writeResourceBundle(ResourceBundle resourceBundle, boolean forLibraryModule,
+ int minSdk)
+ throws JAXBException {
+ CompilerChef compilerChef = CompilerChef.createChef(resourceBundle, getWriter());
+ if (compilerChef.hasAnythingToGenerate()) {
+ compilerChef.addBRVariables(mProcessBindable);
+ compilerChef.writeViewBinderInterfaces(forLibraryModule);
+ if (!forLibraryModule) {
+ compilerChef.writeViewBinders(minSdk);
+ }
+ }
+ if (!forLibraryModule) {
+ compilerChef.writeDbrFile(minSdk);
+ }
+ }
+
+ public static interface Intermediate extends Serializable {
+
+ Intermediate upgrade();
+
+ public void appendTo(ResourceBundle resourceBundle) throws Throwable;
+ }
+
+ public static class IntermediateV1 implements Intermediate {
+
+ transient Unmarshaller mUnmarshaller;
+
+ // name to xml content map
+ Map<String, String> mLayoutInfoMap = new HashMap<String, String>();
+
+ @Override
+ public Intermediate upgrade() {
+ return this;
+ }
+
+ @Override
+ public void appendTo(ResourceBundle resourceBundle) throws JAXBException {
+ if (mUnmarshaller == null) {
+ JAXBContext context = JAXBContext
+ .newInstance(ResourceBundle.LayoutFileBundle.class);
+ mUnmarshaller = context.createUnmarshaller();
+ }
+ for (String content : mLayoutInfoMap.values()) {
+ final InputStream is = IOUtils.toInputStream(content);
+ try {
+ final ResourceBundle.LayoutFileBundle bundle
+ = (ResourceBundle.LayoutFileBundle) mUnmarshaller.unmarshal(is);
+ resourceBundle.addLayoutBundle(bundle);
+ L.d("loaded layout info file %s", bundle);
+ } finally {
+ IOUtils.closeQuietly(is);
+ }
+ }
+ }
+
+ public void addEntry(String name, String contents) {
+ mLayoutInfoMap.put(name, contents);
+ }
+
+ public void removeOverridden(List<Intermediate> existing) {
+ // this is the way we get rid of files that are copied from previous modules
+ // it is important to do this before saving the intermediate file
+ for (Intermediate old : existing) {
+ if (old instanceof IntermediateV1) {
+ IntermediateV1 other = (IntermediateV1) old;
+ for (String key : other.mLayoutInfoMap.keySet()) {
+ // TODO we should consider the original file as the key here
+ // but aapt probably cannot provide that information
+ if (mLayoutInfoMap.remove(key) != null) {
+ L.d("removing %s from bundle because it came from another module", key);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessMethodAdapters.java b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessMethodAdapters.java
new file mode 100644
index 00000000..b23bf3e7
--- /dev/null
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessMethodAdapters.java
@@ -0,0 +1,214 @@
+/*
+ * 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.databinding.annotationprocessor;
+
+import com.google.common.base.Preconditions;
+
+import android.databinding.BindingAdapter;
+import android.databinding.BindingBuildInfo;
+import android.databinding.BindingConversion;
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+import android.databinding.Untaggable;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.store.SetterStore;
+import android.databinding.tool.util.L;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.MirroredTypeException;
+import javax.lang.model.type.ReferenceType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.tools.Diagnostic;
+
+public class ProcessMethodAdapters extends ProcessDataBinding.ProcessingStep {
+ public ProcessMethodAdapters() {
+ }
+
+ @Override
+ public boolean onHandleStep(RoundEnvironment roundEnv,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+ L.d("processing adapters");
+ final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
+ Preconditions.checkNotNull(modelAnalyzer, "Model analyzer should be"
+ + " initialized first");
+ SetterStore store = SetterStore.get(modelAnalyzer);
+ clearIncrementalClasses(roundEnv, store);
+
+ addBindingAdapters(roundEnv, processingEnvironment, store);
+ addRenamed(roundEnv, processingEnvironment, store);
+ addConversions(roundEnv, processingEnvironment, store);
+ addUntaggable(roundEnv, processingEnvironment, store);
+
+ try {
+ store.write(buildInfo.modulePackage(), processingEnvironment);
+ } catch (IOException e) {
+ L.e(e, "Could not write BindingAdapter intermediate file.");
+ }
+ return true;
+ }
+
+ @Override
+ public void onProcessingOver(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+
+ }
+
+ private void addBindingAdapters(RoundEnvironment roundEnv, ProcessingEnvironment
+ processingEnv, SetterStore store) {
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingAdapter.class)) {
+ if (element.getKind() != ElementKind.METHOD ||
+ !element.getModifiers().contains(Modifier.STATIC) ||
+ !element.getModifiers().contains(Modifier.PUBLIC)) {
+ L.e("@BindingAdapter on invalid element: %s", element);
+ continue;
+ }
+ BindingAdapter bindingAdapter = element.getAnnotation(BindingAdapter.class);
+
+ ExecutableElement executableElement = (ExecutableElement) element;
+ List<? extends VariableElement> parameters = executableElement.getParameters();
+ if (bindingAdapter.value().length == 0) {
+ L.e("@BindingAdapter requires at least one attribute. %s", element);
+ continue;
+ }
+ final int numAttributes = bindingAdapter.value().length;
+ if (parameters.size() != numAttributes + 1) {
+ L.e("@BindingAdapter does not take %d parameters: %s",numAttributes + 1, element);
+ continue;
+ }
+ warnAttributeNamespaces(bindingAdapter.value());
+ try {
+ if (numAttributes == 1) {
+ final String attribute = bindingAdapter.value()[0];
+ L.d("------------------ @BindingAdapter for %s", element);
+ store.addBindingAdapter(processingEnv, attribute, executableElement);
+ } else {
+ store.addBindingAdapter(processingEnv, bindingAdapter.value(),
+ executableElement);
+ }
+ } catch (IllegalArgumentException e) {
+ L.e(e, "@BindingAdapter for duplicate View and parameter type: %s", element);
+ }
+ }
+ }
+
+ private static void warnAttributeNamespace(String attribute) {
+ if (attribute.contains(":") && !attribute.startsWith("android:")) {
+ L.w("Application namespace for attribute %s will be ignored.", attribute);
+ }
+ }
+
+ private static void warnAttributeNamespaces(String[] attributes) {
+ for (String attribute : attributes) {
+ warnAttributeNamespace(attribute);
+ }
+ }
+
+ private void addRenamed(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv,
+ SetterStore store) {
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingMethods.class)) {
+ BindingMethods bindingMethods = element.getAnnotation(BindingMethods.class);
+
+ for (BindingMethod bindingMethod : bindingMethods.value()) {
+ final String attribute = bindingMethod.attribute();
+ final String method = bindingMethod.method();
+ warnAttributeNamespace(attribute);
+ String type;
+ try {
+ type = bindingMethod.type().getCanonicalName();
+ } catch (MirroredTypeException e) {
+ type = e.getTypeMirror().toString();
+ }
+ store.addRenamedMethod(attribute, type, method, (TypeElement) element);
+ }
+ }
+ }
+
+ private void addConversions(RoundEnvironment roundEnv,
+ ProcessingEnvironment processingEnv, SetterStore store) {
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingConversion.class)) {
+ if (element.getKind() != ElementKind.METHOD ||
+ !element.getModifiers().contains(Modifier.STATIC) ||
+ !element.getModifiers().contains(Modifier.PUBLIC)) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
+ "@BindingConversion is only allowed on public static methods: " + element);
+ continue;
+ }
+
+ ExecutableElement executableElement = (ExecutableElement) element;
+ if (executableElement.getParameters().size() != 1) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
+ "@BindingConversion method should have one parameter: " + element);
+ continue;
+ }
+ if (executableElement.getReturnType().getKind() == TypeKind.VOID) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
+ "@BindingConversion method must return a value: " + element);
+ continue;
+ }
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
+ "added conversion: " + element);
+ store.addConversionMethod(executableElement);
+ }
+ }
+
+ private void addUntaggable(RoundEnvironment roundEnv,
+ ProcessingEnvironment processingEnv, SetterStore store) {
+ for (Element element : AnnotationUtil.getElementsAnnotatedWith(roundEnv, Untaggable.class)) {
+ Untaggable untaggable = element.getAnnotation(Untaggable.class);
+ store.addUntaggableTypes(untaggable.value(), (TypeElement) element);
+ }
+ }
+
+ private void clearIncrementalClasses(RoundEnvironment roundEnv, SetterStore store) {
+ HashSet<String> classes = new HashSet<String>();
+
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingAdapter.class)) {
+ TypeElement containingClass = (TypeElement) element.getEnclosingElement();
+ classes.add(containingClass.getQualifiedName().toString());
+ }
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingMethods.class)) {
+ classes.add(((TypeElement) element).getQualifiedName().toString());
+ }
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingConversion.class)) {
+ classes.add(((TypeElement) element.getEnclosingElement()).getQualifiedName().
+ toString());
+ }
+ for (Element element : AnnotationUtil.getElementsAnnotatedWith(roundEnv, Untaggable.class)) {
+ classes.add(((TypeElement) element).getQualifiedName().toString());
+ }
+ store.clear(classes);
+ }
+
+}