| /* | |
| * Copyright 2011 Google Inc. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
| * use this file except in compliance with the License. You may obtain a copy of | |
| * the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| * License for the specific language governing permissions and limitations under | |
| * the License. | |
| */ | |
| package com.google.web.bindery.requestfactory.apt; | |
| import static com.google.web.bindery.requestfactory.vm.impl.TypeTokenResolver.TOKEN_MANIFEST; | |
| import com.google.web.bindery.requestfactory.shared.BaseProxy; | |
| import com.google.web.bindery.requestfactory.vm.impl.OperationKey; | |
| import com.google.web.bindery.requestfactory.vm.impl.TypeTokenResolver; | |
| import java.io.IOException; | |
| import java.io.PrintWriter; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import javax.annotation.processing.AbstractProcessor; | |
| import javax.annotation.processing.Filer; | |
| import javax.annotation.processing.FilerException; | |
| import javax.annotation.processing.ProcessingEnvironment; | |
| import javax.annotation.processing.RoundEnvironment; | |
| import javax.annotation.processing.SupportedAnnotationTypes; | |
| import javax.annotation.processing.SupportedOptions; | |
| import javax.annotation.processing.SupportedSourceVersion; | |
| import javax.lang.model.SourceVersion; | |
| import javax.lang.model.element.Element; | |
| import javax.lang.model.element.TypeElement; | |
| import javax.lang.model.type.TypeMirror; | |
| import javax.lang.model.util.ElementFilter; | |
| import javax.lang.model.util.ElementScanner6; | |
| import javax.lang.model.util.Elements; | |
| import javax.lang.model.util.Types; | |
| import javax.tools.Diagnostic.Kind; | |
| import javax.tools.FileObject; | |
| import javax.tools.JavaFileObject; | |
| import javax.tools.StandardLocation; | |
| /** | |
| * An annotation processor that creates an obfuscated type manifest that is used | |
| * by {@link com.google.web.bindery.requestfactory.vm.RequestFactorySource} and | |
| * related implementation classes. | |
| */ | |
| @SupportedAnnotationTypes("com.google.web.bindery.requestfactory.*") | |
| @SupportedSourceVersion(SourceVersion.RELEASE_6) | |
| @SupportedOptions("verbose") | |
| public class RfApt extends AbstractProcessor { | |
| /** | |
| * Looks for all types assignable to {@link BaseProxy} and adds them to the | |
| * output state. | |
| */ | |
| private class Finder extends ElementScanner6<Void, Void> { | |
| // Only valid for a single round | |
| TypeMirror baseProxyType = elements.getTypeElement(BaseProxy.class.getCanonicalName()).asType(); | |
| @Override | |
| public Void visitType(TypeElement elt, Void arg1) { | |
| String binaryName = elements.getBinaryName(elt).toString(); | |
| if (types.isSubtype(elt.asType(), baseProxyType)) { | |
| String hash = OperationKey.hash(binaryName); | |
| builder.addTypeToken(hash, binaryName); | |
| log(elt, "Processed proxy %s %s", binaryName, hash); | |
| } | |
| return super.visitType(elt, arg1); | |
| } | |
| } | |
| private TypeTokenResolver.Builder builder; | |
| private Elements elements; | |
| private Filer filer; | |
| private boolean verbose; | |
| private Types types; | |
| @Override | |
| public synchronized void init(ProcessingEnvironment processingEnv) { | |
| super.init(processingEnv); | |
| log("RfApt init"); | |
| elements = processingEnv.getElementUtils(); | |
| filer = processingEnv.getFiler(); | |
| types = processingEnv.getTypeUtils(); | |
| verbose = Boolean.parseBoolean(processingEnv.getOptions().get("verbose")); | |
| } | |
| @Override | |
| public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | |
| log("RequestFactory processing a round"); | |
| if (builder == null) { | |
| builder = new TypeTokenResolver.Builder(); | |
| // Try not to obliterate existing data | |
| try { | |
| FileObject resource = filer.getResource(StandardLocation.CLASS_OUTPUT, "", TOKEN_MANIFEST); | |
| builder.load(resource.openInputStream()); | |
| log("Reusing old data"); | |
| } catch (IOException e) { | |
| // Likely because the file does not exist | |
| log("Not reusing existing manifest file: " + e.getMessage()); | |
| } | |
| } | |
| // Extract data | |
| new Finder().scan(ElementFilter.typesIn(roundEnv.getRootElements()), null); | |
| // On the last round, write out accumulated data | |
| if (roundEnv.processingOver()) { | |
| TypeTokenResolver d = builder.build(); | |
| builder = null; | |
| try { | |
| FileObject res = filer.createResource(StandardLocation.CLASS_OUTPUT, "", TOKEN_MANIFEST); | |
| d.store(res.openOutputStream()); | |
| } catch (IOException e) { | |
| error("Could not write output: " + e.getMessage()); | |
| } | |
| /* | |
| * Getting the TOKEN_MANIFEST resource into an Android APK generated by | |
| * the Android Eclipse plugin is non-trivial. (Users of ant and apkbuilder | |
| * can just use -rf to include the relevant file). To support the common | |
| * use-case, we'll generate a subclass of TypeTokenResolver.Builder that | |
| * has all of the data already baked into it. This synthetic subtype is | |
| * looked for first by TypeTokenResolver and any manifests that are on the | |
| * classpath will be added on top. | |
| */ | |
| try { | |
| String packageName = TypeTokenResolver.class.getPackage().getName(); | |
| String simpleName = TypeTokenResolver.class.getSimpleName() + "BuilderImpl"; | |
| JavaFileObject classfile = filer.createSourceFile(packageName + "." + simpleName); | |
| PrintWriter pw = new PrintWriter(classfile.openWriter()); | |
| pw.println("package " + packageName + ";"); | |
| pw.println("public class " + simpleName + " extends " | |
| + TypeTokenResolver.Builder.class.getCanonicalName() + " {"); | |
| pw.println("public " + simpleName + "() {"); | |
| for (Map.Entry<String, String> entry : d.getAllTypeTokens().entrySet()) { | |
| if (elements.getTypeElement(entry.getValue()) != null) { | |
| pw.println("addTypeToken(\"" + entry.getKey() + "\", " + entry.getValue() | |
| + ".class.getName());"); | |
| } | |
| } | |
| pw.println("}"); | |
| pw.println("}"); | |
| pw.close(); | |
| } catch (FilerException e) { | |
| log("Ignoring exception: %s", e.getMessage()); | |
| } catch (IOException e) { | |
| error("Could not write BuilderImpl: " + e.getMessage()); | |
| } | |
| log("Finished!"); | |
| } | |
| return false; | |
| } | |
| private void error(String message, Object... args) { | |
| processingEnv.getMessager().printMessage(Kind.ERROR, "ERROR: " + String.format(message, args)); | |
| } | |
| private void log(Element elt, String message, Object... args) { | |
| if (verbose) { | |
| processingEnv.getMessager().printMessage(Kind.NOTE, String.format(message, args), elt); | |
| } | |
| } | |
| private void log(String message, Object... args) { | |
| if (verbose) { | |
| processingEnv.getMessager().printMessage(Kind.NOTE, String.format(message, args)); | |
| } | |
| } | |
| } |