| /* |
| * 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)); |
| } |
| } |
| } |