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