ProcessBindable.java revision 1b9940e612fc73202837fbe9db2f9035f307b5d1
1package com.android.databinding.annotationprocessor; 2 3import com.android.databinding.reflection.ModelAnalyzer; 4import com.android.databinding.reflection.ReflectionAnalyzer; 5 6import android.binding.Bindable; 7 8import java.io.File; 9import java.io.IOException; 10import java.io.InputStream; 11import java.io.ObjectInputStream; 12import java.io.ObjectOutputStream; 13import java.io.Serializable; 14import java.io.Writer; 15import java.net.URL; 16import java.util.ArrayList; 17import java.util.Collections; 18import java.util.Enumeration; 19import java.util.HashMap; 20import java.util.HashSet; 21import java.util.List; 22import java.util.Set; 23 24import javax.annotation.processing.AbstractProcessor; 25import javax.annotation.processing.ProcessingEnvironment; 26import javax.annotation.processing.RoundEnvironment; 27import javax.annotation.processing.SupportedAnnotationTypes; 28import javax.annotation.processing.SupportedSourceVersion; 29import javax.lang.model.SourceVersion; 30import javax.lang.model.element.Element; 31import javax.lang.model.element.ElementKind; 32import javax.lang.model.element.ExecutableElement; 33import javax.lang.model.element.Name; 34import javax.lang.model.element.PackageElement; 35import javax.lang.model.element.TypeElement; 36import javax.lang.model.element.VariableElement; 37import javax.lang.model.type.TypeKind; 38import javax.lang.model.util.Elements; 39import javax.lang.model.util.Types; 40import javax.tools.Diagnostic; 41import javax.tools.FileObject; 42import javax.tools.JavaFileObject; 43import javax.tools.StandardLocation; 44 45@SupportedAnnotationTypes({"android.binding.Bindable"}) 46@SupportedSourceVersion(SourceVersion.RELEASE_7) 47public class ProcessBindable extends AbstractProcessor { 48 49 private boolean mFileGenerated; 50 51 public ProcessBindable() { 52 } 53 54 @Override 55 public synchronized void init(ProcessingEnvironment processingEnv) { 56 super.init(processingEnv); 57 ReflectionAnalyzer.setProcessingEnvironment(processingEnv); 58 } 59 60 @Override 61 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 62 if (mFileGenerated) { 63 return false; 64 } 65 Intermediate properties = readIntermediateFile(); 66 for (Element element : roundEnv.getElementsAnnotatedWith(Bindable.class)) { 67 TypeElement enclosing = (TypeElement) element.getEnclosingElement(); 68 properties.cleanProperties(enclosing.getQualifiedName().toString()); 69 } 70 for (Element element : roundEnv.getElementsAnnotatedWith(Bindable.class)) { 71 TypeElement enclosing = (TypeElement) element.getEnclosingElement(); 72 String name = getPropertyName(element); 73 if (name != null) { 74 properties.addProperty(enclosing.getQualifiedName().toString(), name); 75 } 76 } 77 writeIntermediateFile(properties); 78 generateBR(properties); 79 mFileGenerated = true; 80 return true; 81 } 82 83 private void generateBR(Intermediate intermediate) { 84 processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, 85 "************* Generating BR file from Bindable attributes"); 86 HashSet<String> properties = new HashSet<>(); 87 intermediate.captureProperties(properties); 88 mergeClassPathResources(properties); 89 try { 90 ArrayList<String> sortedProperties = new ArrayList<String>(); 91 sortedProperties.addAll(properties); 92 Collections.sort(sortedProperties); 93 94 JavaFileObject fileObject = processingEnv.getFiler() 95 .createSourceFile("android.binding.BR"); 96 Writer writer = fileObject.openWriter(); 97 writer.write("package android.binding;\n\n" + 98 "public final class BR {\n" + 99 " public static final int _all = 0;\n" 100 ); 101 int id = 0; 102 for (String property : sortedProperties) { 103 id++; 104 writer.write(" public static final int " + property + " = " + id + ";\n"); 105 } 106 writer.write(" public static int getId(String key) {\n"); 107 writer.write(" switch(key) {\n"); 108 id = 0; 109 for (String property : sortedProperties) { 110 id++; 111 writer.write(" case \"" + property + "\": return " + id + ";\n"); 112 } 113 writer.write(" }\n"); 114 writer.write(" return -1;\n"); 115 writer.write(" }"); 116 writer.write("}\n"); 117 118 writer.close(); 119 } catch (IOException e) { 120 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 121 "Could not generate BR file " + e.getLocalizedMessage()); 122 } 123 } 124 125 private String getPropertyName(Element element) { 126 switch (element.getKind()) { 127 case FIELD: 128 return stripPrefixFromField((VariableElement) element); 129 case METHOD: 130 return stripPrefixFromMethod((ExecutableElement) element); 131 default: 132 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 133 "@Bindable is not allowed on " + element.getKind(), element); 134 return null; 135 } 136 } 137 138 private static String stripPrefixFromField(VariableElement element) { 139 Name name = element.getSimpleName(); 140 if (name.length() >= 2) { 141 char firstChar = name.charAt(0); 142 char secondChar = name.charAt(1); 143 if (name.length() > 2 && firstChar == 'm' && secondChar == '_') { 144 char thirdChar = name.charAt(2); 145 if (Character.isJavaIdentifierStart(thirdChar)) { 146 return "" + Character.toLowerCase(thirdChar) + 147 name.subSequence(3, name.length()); 148 } 149 } else if ((firstChar == 'm' && Character.isUpperCase(secondChar)) || 150 (firstChar == '_' && Character.isJavaIdentifierStart(secondChar))) { 151 return "" + Character.toLowerCase(secondChar) + name.subSequence(2, name.length()); 152 } 153 } 154 return name.toString(); 155 } 156 157 private String stripPrefixFromMethod(ExecutableElement element) { 158 Name name = element.getSimpleName(); 159 CharSequence propertyName; 160 if (isGetter(element) || isSetter(element)) { 161 propertyName = name.subSequence(3, name.length()); 162 } else if (isBooleanGetter(element)) { 163 propertyName = name.subSequence(2, name.length()); 164 } else { 165 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 166 "@Bindable associated with method must follow JavaBeans convention", element); 167 return null; 168 } 169 char firstChar = propertyName.charAt(0); 170 return "" + Character.toLowerCase(firstChar) + 171 propertyName.subSequence(1, propertyName.length()); 172 } 173 174 private static boolean prefixes(CharSequence sequence, String prefix) { 175 boolean prefixes = false; 176 if (sequence.length() > prefix.length()) { 177 int count = prefix.length(); 178 prefixes = true; 179 for (int i = 0; i < count; i++) { 180 if (sequence.charAt(i) != prefix.charAt(i)) { 181 prefixes = false; 182 break; 183 } 184 } 185 } 186 return prefixes; 187 } 188 189 private static boolean isGetter(ExecutableElement element) { 190 Name name = element.getSimpleName(); 191 return prefixes(name, "get") && 192 Character.isJavaIdentifierStart(name.charAt(3)) && 193 element.getParameters().isEmpty() && 194 element.getReturnType().getKind() != TypeKind.VOID; 195 } 196 197 private static boolean isSetter(ExecutableElement element) { 198 Name name = element.getSimpleName(); 199 return prefixes(name, "set") && 200 Character.isJavaIdentifierStart(name.charAt(3)) && 201 element.getParameters().size() == 1 && 202 element.getReturnType().getKind() == TypeKind.VOID; 203 } 204 205 private static boolean isBooleanGetter(ExecutableElement element) { 206 Name name = element.getSimpleName(); 207 return prefixes(name, "is") && 208 Character.isJavaIdentifierStart(name.charAt(2)) && 209 element.getParameters().isEmpty() && 210 element.getReturnType().getKind() == TypeKind.BOOLEAN; 211 } 212 213 private Intermediate readIntermediateFile() { 214 Intermediate properties = null; 215 ObjectInputStream in = null; 216 try { 217 FileObject intermediate = processingEnv.getFiler() 218 .getResource(StandardLocation.CLASS_OUTPUT, 219 ProcessBindable.class.getPackage().getName(), "binding_properties.bin"); 220 if (new File(intermediate.getName()).exists()) { 221 in = new ObjectInputStream(intermediate.openInputStream()); 222 properties = (Intermediate) in.readObject(); 223 } 224 } catch (IOException e) { 225 System.err.println("Could not read Binding properties intermediate file: " + 226 e.getLocalizedMessage()); 227 } catch (ClassNotFoundException e) { 228 System.err.println("Could not read Binding properties intermediate file: " + 229 e.getLocalizedMessage()); 230 } finally { 231 try { 232 if (in != null) { 233 in.close(); 234 } 235 } catch (IOException e) { 236 e.printStackTrace(); 237 } 238 } 239 if (properties == null) { 240 properties = new IntermediateV1(); 241 } 242 return properties; 243 } 244 245 private void mergeClassPathResources(HashSet<String> intermediateProperties) { 246 try { 247 String resourcePath = ProcessBindable.class.getPackage().getName() 248 .replace('.', '/') + "/binding_properties.bin"; 249 Enumeration<URL> resources = getClass().getClassLoader() 250 .getResources(resourcePath); 251 while (resources.hasMoreElements()) { 252 URL url = resources.nextElement(); 253 System.out.println("Merging binding adapters from " + url); 254 InputStream inputStream = null; 255 try { 256 inputStream = url.openStream(); 257 ObjectInputStream in = new ObjectInputStream(inputStream); 258 Intermediate properties = (Intermediate) in.readObject(); 259 if (properties != null) { 260 properties.captureProperties(intermediateProperties); 261 } 262 } catch (IOException e) { 263 System.err.println("Could not merge in Bindables from " + url + ": " + 264 e.getLocalizedMessage()); 265 } catch (ClassNotFoundException e) { 266 System.err.println("Could not read Binding properties intermediate file: " + 267 e.getLocalizedMessage()); 268 } finally { 269 try { 270 inputStream.close(); 271 } catch (IOException e2) { 272 System.err.println("Error closing intermediate Bindables store: " + 273 e2.getLocalizedMessage()); 274 } 275 } 276 } 277 } catch (IOException e) { 278 System.err.println("Could not read Binding properties intermediate file: " + 279 e.getLocalizedMessage()); 280 } 281 } 282 283 private void writeIntermediateFile(Intermediate properties) { 284 try { 285 FileObject intermediate = processingEnv.getFiler().createResource( 286 StandardLocation.CLASS_OUTPUT, ProcessBindable.class.getPackage().getName(), 287 "binding_properties.bin"); 288 ObjectOutputStream out = new ObjectOutputStream(intermediate.openOutputStream()); 289 out.writeObject(properties); 290 out.close(); 291 } catch (IOException e) { 292 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 293 "Could not write to intermediate file: " + e.getLocalizedMessage()); 294 } 295 } 296 297 private interface Intermediate { 298 void captureProperties(Set<String> properties); 299 300 void cleanProperties(String className); 301 302 void addProperty(String className, String propertyName); 303 } 304 305 private static class IntermediateV1 implements Serializable, Intermediate { 306 private static final long serialVersionUID = 1L; 307 308 private final HashMap<String, HashSet<String>> mProperties = new HashMap<>(); 309 310 @Override 311 public void captureProperties(Set<String> properties) { 312 for (HashSet<String> propertySet : mProperties.values()) { 313 properties.addAll(propertySet); 314 } 315 } 316 317 @Override 318 public void cleanProperties(String className) { 319 mProperties.remove(className); 320 } 321 322 @Override 323 public void addProperty(String className, String propertyName) { 324 HashSet<String> properties = mProperties.get(className); 325 if (properties == null) { 326 properties = new HashSet<>(); 327 mProperties.put(className, properties); 328 } 329 properties.add(propertyName); 330 } 331 } 332} 333