diff options
author | Yigit Boyar <yboyar@google.com> | 2015-05-21 15:49:58 -0700 |
---|---|---|
committer | Yigit Boyar <yboyar@google.com> | 2015-05-22 11:11:42 -0700 |
commit | b1356339eaa6c8e967e4fc1dc283b82909a1208d (patch) | |
tree | 0644d70a89f74d261294ce91fd2d83d9aa02d2fa /compiler/src/main/java/android/databinding/annotationprocessor | |
parent | 79ae2e5b4612d3af10bcadb235c86b564b5f97c1 (diff) | |
download | data-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')
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); + } + +} |