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