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