• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.databinding.annotationprocessor;
18 
19 import android.databinding.Bindable;
20 import android.databinding.BindingBuildInfo;
21 import android.databinding.tool.CompilerChef.BindableHolder;
22 import android.databinding.tool.util.GenerationalClassUtil;
23 import android.databinding.tool.util.L;
24 import android.databinding.tool.util.Preconditions;
25 import android.databinding.tool.writer.BRWriter;
26 import android.databinding.tool.writer.JavaFileWriter;
27 
28 import java.io.Serializable;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Set;
33 
34 import javax.annotation.processing.ProcessingEnvironment;
35 import javax.annotation.processing.RoundEnvironment;
36 import javax.annotation.processing.SupportedSourceVersion;
37 import javax.lang.model.SourceVersion;
38 import javax.lang.model.element.Element;
39 import javax.lang.model.element.ElementKind;
40 import javax.lang.model.element.ExecutableElement;
41 import javax.lang.model.element.Name;
42 import javax.lang.model.element.TypeElement;
43 import javax.lang.model.element.VariableElement;
44 import javax.lang.model.type.TypeKind;
45 import javax.lang.model.util.Types;
46 
47 // binding app info and library info are necessary to trigger this.
48 @SupportedSourceVersion(SourceVersion.RELEASE_7)
49 public class ProcessBindable extends ProcessDataBinding.ProcessingStep implements BindableHolder {
50     Intermediate mProperties;
51     HashMap<String, HashSet<String>> mLayoutVariables = new HashMap<String, HashSet<String>>();
52 
53     @Override
onHandleStep(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv, BindingBuildInfo buildInfo)54     public boolean onHandleStep(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv,
55             BindingBuildInfo buildInfo) {
56         if (mProperties == null) {
57             mProperties = new IntermediateV1(buildInfo.modulePackage());
58             mergeLayoutVariables();
59             mLayoutVariables.clear();
60             TypeElement observableType = processingEnv.getElementUtils().
61                     getTypeElement("android.databinding.Observable");
62             Types typeUtils = processingEnv.getTypeUtils();
63             for (Element element : AnnotationUtil
64                     .getElementsAnnotatedWith(roundEnv, Bindable.class)) {
65                 Element enclosingElement = element.getEnclosingElement();
66                 ElementKind kind = enclosingElement.getKind();
67                 if (kind != ElementKind.CLASS && kind != ElementKind.INTERFACE) {
68                     L.e("Bindable must be on a member field or method. The enclosing type is %s",
69                             enclosingElement.getKind());
70                 }
71                 TypeElement enclosing = (TypeElement) enclosingElement;
72                 if (!typeUtils.isAssignable(enclosing.asType(), observableType.asType())) {
73                     L.e("Bindable must be on a member in an Observable class. %s is not Observable",
74                             enclosingElement.getSimpleName());
75                 }
76                 String name = getPropertyName(element);
77                 if (name != null) {
78                     Preconditions
79                             .checkNotNull(mProperties, "Must receive app / library info before "
80                                     + "Bindable fields.");
81                     mProperties.addProperty(enclosing.getQualifiedName().toString(), name);
82                 }
83             }
84             GenerationalClassUtil.writeIntermediateFile(processingEnv,
85                     mProperties.getPackage(),
86                     createIntermediateFileName(mProperties.getPackage()), mProperties);
87             generateBRClasses(!buildInfo.isLibrary(), mProperties.getPackage());
88         }
89         return false;
90     }
91 
92     @Override
addVariable(String variableName, String containingClassName)93     public void addVariable(String variableName, String containingClassName) {
94         HashSet<String> variableNames = mLayoutVariables.get(containingClassName);
95         if (variableNames == null) {
96             variableNames = new HashSet<String>();
97             mLayoutVariables.put(containingClassName, variableNames);
98         }
99         variableNames.add(variableName);
100     }
101 
102     @Override
onProcessingOver(RoundEnvironment roundEnvironment, ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo)103     public void onProcessingOver(RoundEnvironment roundEnvironment,
104             ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
105     }
106 
createIntermediateFileName(String appPkg)107     private String createIntermediateFileName(String appPkg) {
108         return appPkg + GenerationalClassUtil.ExtensionFilter.BR.getExtension();
109     }
110 
generateBRClasses(boolean useFinalFields, String pkg)111     private void generateBRClasses(boolean useFinalFields, String pkg) {
112         L.d("************* Generating BR file %s. use final: %s", pkg, useFinalFields);
113         HashSet<String> properties = new HashSet<String>();
114         mProperties.captureProperties(properties);
115         List<Intermediate> previousIntermediates = loadPreviousBRFiles();
116         for (Intermediate intermediate : previousIntermediates) {
117             intermediate.captureProperties(properties);
118         }
119         final JavaFileWriter writer = getWriter();
120         BRWriter brWriter = new BRWriter(properties, useFinalFields);
121         writer.writeToFile(pkg + ".BR", brWriter.write(pkg));
122         //writeBRClass(useFinalFields, pkg, properties);
123         if (useFinalFields) {
124             // generate BR for all previous packages
125             for (Intermediate intermediate : previousIntermediates) {
126                 writer.writeToFile(intermediate.getPackage() + ".BR",
127                         brWriter.write(intermediate.getPackage()));
128             }
129         }
130         mCallback.onBrWriterReady(brWriter);
131     }
132 
getPropertyName(Element element)133     private String getPropertyName(Element element) {
134         switch (element.getKind()) {
135             case FIELD:
136                 return stripPrefixFromField((VariableElement) element);
137             case METHOD:
138                 return stripPrefixFromMethod((ExecutableElement) element);
139             default:
140                 L.e("@Bindable is not allowed on %s", element.getKind());
141                 return null;
142         }
143     }
144 
stripPrefixFromField(VariableElement element)145     private static String stripPrefixFromField(VariableElement element) {
146         Name name = element.getSimpleName();
147         if (name.length() >= 2) {
148             char firstChar = name.charAt(0);
149             char secondChar = name.charAt(1);
150             if (name.length() > 2 && firstChar == 'm' && secondChar == '_') {
151                 char thirdChar = name.charAt(2);
152                 if (Character.isJavaIdentifierStart(thirdChar)) {
153                     return "" + Character.toLowerCase(thirdChar) +
154                             name.subSequence(3, name.length());
155                 }
156             } else if ((firstChar == 'm' && Character.isUpperCase(secondChar)) ||
157                     (firstChar == '_' && Character.isJavaIdentifierStart(secondChar))) {
158                 return "" + Character.toLowerCase(secondChar) + name.subSequence(2, name.length());
159             }
160         }
161         return name.toString();
162     }
163 
stripPrefixFromMethod(ExecutableElement element)164     private String stripPrefixFromMethod(ExecutableElement element) {
165         Name name = element.getSimpleName();
166         CharSequence propertyName;
167         if (isGetter(element) || isSetter(element)) {
168             propertyName = name.subSequence(3, name.length());
169         } else if (isBooleanGetter(element)) {
170             propertyName = name.subSequence(2, name.length());
171         } else {
172             L.e("@Bindable associated with method must follow JavaBeans convention %s", element);
173             return null;
174         }
175         char firstChar = propertyName.charAt(0);
176         return "" + Character.toLowerCase(firstChar) +
177                 propertyName.subSequence(1, propertyName.length());
178     }
179 
mergeLayoutVariables()180     private void mergeLayoutVariables() {
181         for (String containingClass : mLayoutVariables.keySet()) {
182             for (String variable : mLayoutVariables.get(containingClass)) {
183                 mProperties.addProperty(containingClass, variable);
184             }
185         }
186     }
187 
prefixes(CharSequence sequence, String prefix)188     private static boolean prefixes(CharSequence sequence, String prefix) {
189         boolean prefixes = false;
190         if (sequence.length() > prefix.length()) {
191             int count = prefix.length();
192             prefixes = true;
193             for (int i = 0; i < count; i++) {
194                 if (sequence.charAt(i) != prefix.charAt(i)) {
195                     prefixes = false;
196                     break;
197                 }
198             }
199         }
200         return prefixes;
201     }
202 
isGetter(ExecutableElement element)203     private static boolean isGetter(ExecutableElement element) {
204         Name name = element.getSimpleName();
205         return prefixes(name, "get") &&
206                 Character.isJavaIdentifierStart(name.charAt(3)) &&
207                 element.getParameters().isEmpty() &&
208                 element.getReturnType().getKind() != TypeKind.VOID;
209     }
210 
isSetter(ExecutableElement element)211     private static boolean isSetter(ExecutableElement element) {
212         Name name = element.getSimpleName();
213         return prefixes(name, "set") &&
214                 Character.isJavaIdentifierStart(name.charAt(3)) &&
215                 element.getParameters().size() == 1 &&
216                 element.getReturnType().getKind() == TypeKind.VOID;
217     }
218 
isBooleanGetter(ExecutableElement element)219     private static boolean isBooleanGetter(ExecutableElement element) {
220         Name name = element.getSimpleName();
221         return prefixes(name, "is") &&
222                 Character.isJavaIdentifierStart(name.charAt(2)) &&
223                 element.getParameters().isEmpty() &&
224                 element.getReturnType().getKind() == TypeKind.BOOLEAN;
225     }
226 
loadPreviousBRFiles()227     private List<Intermediate> loadPreviousBRFiles() {
228         return GenerationalClassUtil
229                 .loadObjects(GenerationalClassUtil.ExtensionFilter.BR);
230     }
231 
232     private interface Intermediate extends Serializable {
233 
captureProperties(Set<String> properties)234         void captureProperties(Set<String> properties);
235 
addProperty(String className, String propertyName)236         void addProperty(String className, String propertyName);
237 
hasValues()238         boolean hasValues();
239 
getPackage()240         String getPackage();
241     }
242 
243     private static class IntermediateV1 implements Serializable, Intermediate {
244 
245         private static final long serialVersionUID = 2L;
246 
247         private String mPackage;
248         private final HashMap<String, HashSet<String>> mProperties = new HashMap<String, HashSet<String>>();
249 
IntermediateV1(String aPackage)250         public IntermediateV1(String aPackage) {
251             mPackage = aPackage;
252         }
253 
254         @Override
captureProperties(Set<String> properties)255         public void captureProperties(Set<String> properties) {
256             for (HashSet<String> propertySet : mProperties.values()) {
257                 properties.addAll(propertySet);
258             }
259         }
260 
261         @Override
addProperty(String className, String propertyName)262         public void addProperty(String className, String propertyName) {
263             HashSet<String> properties = mProperties.get(className);
264             if (properties == null) {
265                 properties = new HashSet<String>();
266                 mProperties.put(className, properties);
267             }
268             properties.add(propertyName);
269         }
270 
271         @Override
hasValues()272         public boolean hasValues() {
273             return !mProperties.isEmpty();
274         }
275 
276         @Override
getPackage()277         public String getPackage() {
278             return mPackage;
279         }
280     }
281 }
282