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