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