ProcessBindable.java revision 2611838bffef5a009ca71e3e9e59a93f29b098ed
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; 25 26import java.io.Serializable; 27import java.util.ArrayList; 28import java.util.Collections; 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 writeBRClass(useFinalFields, pkg, properties); 121 if (useFinalFields) { 122 // generate BR for all previous packages 123 for (Intermediate intermediate : previousIntermediates) { 124 writeBRClass(true, intermediate.getPackage(), 125 properties); 126 } 127 } 128 } 129 130 private void writeBRClass(boolean useFinalFields, String pkg, HashSet<String> properties) { 131 ArrayList<String> sortedProperties = new ArrayList<String>(); 132 sortedProperties.addAll(properties); 133 Collections.sort(sortedProperties); 134 StringBuilder out = new StringBuilder(); 135 String modifier = "public static " + (useFinalFields ? "final" : "") + " int "; 136 out.append("package " + pkg + ";\n\n" + 137 "public class BR {\n" + 138 " " + modifier + "_all = 0;\n" 139 ); 140 int id = 0; 141 for (String property : sortedProperties) { 142 id++; 143 out.append(" " + modifier + property + " = " + id + ";\n"); 144 } 145 out.append("}\n"); 146 147 getWriter().writeToFile(pkg + ".BR", out.toString() ); 148 } 149 150 private String getPropertyName(Element element) { 151 switch (element.getKind()) { 152 case FIELD: 153 return stripPrefixFromField((VariableElement) element); 154 case METHOD: 155 return stripPrefixFromMethod((ExecutableElement) element); 156 default: 157 L.e("@Bindable is not allowed on %s", element.getKind()); 158 return null; 159 } 160 } 161 162 private static String stripPrefixFromField(VariableElement element) { 163 Name name = element.getSimpleName(); 164 if (name.length() >= 2) { 165 char firstChar = name.charAt(0); 166 char secondChar = name.charAt(1); 167 if (name.length() > 2 && firstChar == 'm' && secondChar == '_') { 168 char thirdChar = name.charAt(2); 169 if (Character.isJavaIdentifierStart(thirdChar)) { 170 return "" + Character.toLowerCase(thirdChar) + 171 name.subSequence(3, name.length()); 172 } 173 } else if ((firstChar == 'm' && Character.isUpperCase(secondChar)) || 174 (firstChar == '_' && Character.isJavaIdentifierStart(secondChar))) { 175 return "" + Character.toLowerCase(secondChar) + name.subSequence(2, name.length()); 176 } 177 } 178 return name.toString(); 179 } 180 181 private String stripPrefixFromMethod(ExecutableElement element) { 182 Name name = element.getSimpleName(); 183 CharSequence propertyName; 184 if (isGetter(element) || isSetter(element)) { 185 propertyName = name.subSequence(3, name.length()); 186 } else if (isBooleanGetter(element)) { 187 propertyName = name.subSequence(2, name.length()); 188 } else { 189 L.e("@Bindable associated with method must follow JavaBeans convention %s", element); 190 return null; 191 } 192 char firstChar = propertyName.charAt(0); 193 return "" + Character.toLowerCase(firstChar) + 194 propertyName.subSequence(1, propertyName.length()); 195 } 196 197 private void mergeLayoutVariables() { 198 for (String containingClass : mLayoutVariables.keySet()) { 199 for (String variable : mLayoutVariables.get(containingClass)) { 200 mProperties.addProperty(containingClass, variable); 201 } 202 } 203 } 204 205 private static boolean prefixes(CharSequence sequence, String prefix) { 206 boolean prefixes = false; 207 if (sequence.length() > prefix.length()) { 208 int count = prefix.length(); 209 prefixes = true; 210 for (int i = 0; i < count; i++) { 211 if (sequence.charAt(i) != prefix.charAt(i)) { 212 prefixes = false; 213 break; 214 } 215 } 216 } 217 return prefixes; 218 } 219 220 private static boolean isGetter(ExecutableElement element) { 221 Name name = element.getSimpleName(); 222 return prefixes(name, "get") && 223 Character.isJavaIdentifierStart(name.charAt(3)) && 224 element.getParameters().isEmpty() && 225 element.getReturnType().getKind() != TypeKind.VOID; 226 } 227 228 private static boolean isSetter(ExecutableElement element) { 229 Name name = element.getSimpleName(); 230 return prefixes(name, "set") && 231 Character.isJavaIdentifierStart(name.charAt(3)) && 232 element.getParameters().size() == 1 && 233 element.getReturnType().getKind() == TypeKind.VOID; 234 } 235 236 private static boolean isBooleanGetter(ExecutableElement element) { 237 Name name = element.getSimpleName(); 238 return prefixes(name, "is") && 239 Character.isJavaIdentifierStart(name.charAt(2)) && 240 element.getParameters().isEmpty() && 241 element.getReturnType().getKind() == TypeKind.BOOLEAN; 242 } 243 244 private List<Intermediate> loadPreviousBRFiles() { 245 return GenerationalClassUtil 246 .loadObjects(getClass().getClassLoader(), 247 new GenerationalClassUtil.ExtensionFilter(INTERMEDIATE_FILE_EXT)); 248 } 249 250 private interface Intermediate extends Serializable { 251 252 void captureProperties(Set<String> properties); 253 254 void addProperty(String className, String propertyName); 255 256 boolean hasValues(); 257 258 String getPackage(); 259 } 260 261 private static class IntermediateV1 implements Serializable, Intermediate { 262 263 private static final long serialVersionUID = 2L; 264 265 private String mPackage; 266 private final HashMap<String, HashSet<String>> mProperties = new HashMap<String, HashSet<String>>(); 267 268 public IntermediateV1(String aPackage) { 269 mPackage = aPackage; 270 } 271 272 @Override 273 public void captureProperties(Set<String> properties) { 274 for (HashSet<String> propertySet : mProperties.values()) { 275 properties.addAll(propertySet); 276 } 277 } 278 279 @Override 280 public void addProperty(String className, String propertyName) { 281 HashSet<String> properties = mProperties.get(className); 282 if (properties == null) { 283 properties = new HashSet<String>(); 284 mProperties.put(className, properties); 285 } 286 properties.add(propertyName); 287 } 288 289 @Override 290 public boolean hasValues() { 291 return !mProperties.isEmpty(); 292 } 293 294 @Override 295 public String getPackage() { 296 return mPackage; 297 } 298 } 299} 300