ProcessBindable.java revision e8609ca3a9e95cb730d74f8a6114bc2ae11b6a10
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
17package android.databinding.annotationprocessor;
18
19import android.databinding.Bindable;
20import android.databinding.BindingBuildInfo;
21import android.databinding.tool.CompilerChef.BindableHolder;
22import android.databinding.tool.util.GenerationalClassUtil;
23import android.databinding.tool.util.L;
24import android.databinding.tool.util.Preconditions;
25import android.databinding.tool.writer.BRWriter;
26import android.databinding.tool.writer.JavaFileWriter;
27
28import java.io.Serializable;
29import java.util.HashMap;
30import java.util.HashSet;
31import java.util.List;
32import java.util.Set;
33
34import javax.annotation.processing.ProcessingEnvironment;
35import javax.annotation.processing.RoundEnvironment;
36import javax.annotation.processing.SupportedSourceVersion;
37import javax.lang.model.SourceVersion;
38import javax.lang.model.element.Element;
39import javax.lang.model.element.ElementKind;
40import javax.lang.model.element.ExecutableElement;
41import javax.lang.model.element.Name;
42import javax.lang.model.element.TypeElement;
43import javax.lang.model.element.VariableElement;
44import javax.lang.model.type.TypeKind;
45import javax.lang.model.util.Types;
46
47// binding app info and library info are necessary to trigger this.
48@SupportedSourceVersion(SourceVersion.RELEASE_7)
49public class ProcessBindable extends ProcessDataBinding.ProcessingStep implements BindableHolder {
50    Intermediate mProperties;
51    HashMap<String, HashSet<String>> mLayoutVariables = new HashMap<String, HashSet<String>>();
52
53    @Override
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
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
103    public void onProcessingOver(RoundEnvironment roundEnvironment,
104            ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
105    }
106
107    private String createIntermediateFileName(String appPkg) {
108        return appPkg + GenerationalClassUtil.ExtensionFilter.BR.getExtension();
109    }
110
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
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
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
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
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
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
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
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
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
227    private List<Intermediate> loadPreviousBRFiles() {
228        return GenerationalClassUtil
229                .loadObjects(GenerationalClassUtil.ExtensionFilter.BR);
230    }
231
232    private interface Intermediate extends Serializable {
233
234        void captureProperties(Set<String> properties);
235
236        void addProperty(String className, String propertyName);
237
238        boolean hasValues();
239
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
250        public IntermediateV1(String aPackage) {
251            mPackage = aPackage;
252        }
253
254        @Override
255        public void captureProperties(Set<String> properties) {
256            for (HashSet<String> propertySet : mProperties.values()) {
257                properties.addAll(propertySet);
258            }
259        }
260
261        @Override
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
272        public boolean hasValues() {
273            return !mProperties.isEmpty();
274        }
275
276        @Override
277        public String getPackage() {
278            return mPackage;
279        }
280    }
281}
282