From 3be354eb76e075ca5e81e911f9f3bd51c046da0c Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Wed, 1 Aug 2012 22:58:04 +0000 Subject: [PATCH] APT hello world. This generates an empty class for each class annotated @GeneratedTypeAdapter. --- gson/gson-codegen/pom.xml | 7 +- .../java/com/google/gson/codegen/CodeGen.java | 53 +++ .../gson/codegen/GeneratedTypeAdapter.java | 21 + .../GeneratedTypeAdapterProcessor.java | 58 +++ .../com/google/gson/codegen/JavaWriter.java | 443 ++++++++++++++++++ .../javax.annotation.processing.Processor | 1 + 6 files changed, 577 insertions(+), 6 deletions(-) create mode 100644 gson/gson-codegen/src/main/java/com/google/gson/codegen/CodeGen.java create mode 100644 gson/gson-codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapter.java create mode 100644 gson/gson-codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java create mode 100644 gson/gson-codegen/src/main/java/com/google/gson/codegen/JavaWriter.java create mode 100644 gson/gson-codegen/src/main/resources/META-INF/services/javax.annotation.processing.Processor diff --git a/gson/gson-codegen/pom.xml b/gson/gson-codegen/pom.xml index 07e44c1c..07c37403 100644 --- a/gson/gson-codegen/pom.xml +++ b/gson/gson-codegen/pom.xml @@ -37,12 +37,6 @@ http://www.google.com - - com.google.code.gson - gson - 2.2.3-SNAPSHOT - compile - junit junit @@ -90,6 +84,7 @@ 1.5 1.5 + -proc:none diff --git a/gson/gson-codegen/src/main/java/com/google/gson/codegen/CodeGen.java b/gson/gson-codegen/src/main/java/com/google/gson/codegen/CodeGen.java new file mode 100644 index 00000000..011568ff --- /dev/null +++ b/gson/gson-codegen/src/main/java/com/google/gson/codegen/CodeGen.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012 Square, 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.gson.codegen; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +public class CodeGen { + private CodeGen() { + } + + public static PackageElement getPackage(Element type) { + while (type.getKind() != ElementKind.PACKAGE) { + type = type.getEnclosingElement(); + } + return (PackageElement) type; + } + + /** + * Returns a fully qualified class name to complement {@code type}. + */ + public static String adapterName(TypeElement typeElement, String suffix) { + StringBuilder builder = new StringBuilder(); + rawTypeToString(builder, typeElement, '$'); + builder.append(suffix); + return builder.toString(); + } + + static void rawTypeToString(StringBuilder result, TypeElement type, char innerClassSeparator) { + String packageName = getPackage(type).getQualifiedName().toString(); + String qualifiedName = type.getQualifiedName().toString(); + result.append(packageName); + result.append('.'); + result.append( + qualifiedName.substring(packageName.length() + 1).replace('.', innerClassSeparator)); + } +} diff --git a/gson/gson-codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapter.java b/gson/gson-codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapter.java new file mode 100644 index 00000000..1694d88a --- /dev/null +++ b/gson/gson-codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapter.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2012 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.gson.codegen; + +public @interface GeneratedTypeAdapter { + Class[] value() default {}; +} diff --git a/gson/gson-codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java b/gson/gson-codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java new file mode 100644 index 00000000..dfaf91c0 --- /dev/null +++ b/gson/gson-codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 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.gson.codegen; + +import java.io.IOException; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; + +import static java.lang.reflect.Modifier.FINAL; + +@SupportedAnnotationTypes("com.google.gson.codegen.GeneratedTypeAdapter") +@SupportedSourceVersion(SourceVersion.RELEASE_6) +public final class GeneratedTypeAdapterProcessor extends AbstractProcessor { + @Override public boolean process(Set types, RoundEnvironment env) { + try { + for (Element element : env.getElementsAnnotatedWith(GeneratedTypeAdapter.class)) { + writeAdapter((TypeElement) element); + } + } catch (IOException e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); + } + return true; + } + + private void writeAdapter(TypeElement type) throws IOException { + String typeAdapterName = CodeGen.adapterName(type, "$TypeAdapter"); + JavaFileObject sourceFile = processingEnv.getFiler() + .createSourceFile(typeAdapterName, type); + + JavaWriter writer = new JavaWriter(sourceFile.openWriter()); + writer.addPackage(CodeGen.getPackage(type).getQualifiedName().toString()); + writer.beginType(typeAdapterName, "class", FINAL, null); + writer.endType(); + writer.close(); + } +} diff --git a/gson/gson-codegen/src/main/java/com/google/gson/codegen/JavaWriter.java b/gson/gson-codegen/src/main/java/com/google/gson/codegen/JavaWriter.java new file mode 100644 index 00000000..ccba6c8c --- /dev/null +++ b/gson/gson-codegen/src/main/java/com/google/gson/codegen/JavaWriter.java @@ -0,0 +1,443 @@ +/** + * Copyright (C) 2012 Square, 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.gson.codegen; + +import java.io.IOException; +import java.io.Writer; +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Emits Java source files. + */ +public final class JavaWriter { + private static final Pattern TYPE_PATTERN = Pattern.compile("(?:[\\w$]+\\.)*([\\w$]+)"); + private static final String INDENT = " "; + + /** Map fully qualified type names to their short names. */ + private final Map importedTypes = new HashMap(); + + private String packagePrefix; + private final List scopes = new ArrayList(); + private final Writer out; + + /** + * @param out the stream to which Java source will be written. This should be + * a buffered stream. + */ + public JavaWriter(Writer out) { + this.out = out; + } + + /** + * Emit a package declaration. + */ + public void addPackage(String packageName) throws IOException { + if (this.packagePrefix != null) { + throw new IllegalStateException(); + } + out.write("package "); + out.write(packageName); + out.write(";\n"); + this.packagePrefix = packageName + "."; + } + + /** + * Equivalent to {@code addImport(type.getName())}. + */ + public void addImport(Class type) throws IOException { + addImport(type.getName()); + } + + /** + * Emit an import for {@code type}. For the duration of the file, all + * references to this class will be automatically shortened. + */ + public void addImport(String type) throws IOException { + Matcher matcher = TYPE_PATTERN.matcher(type); + if (!matcher.matches()) { + throw new IllegalArgumentException(type); + } + if (importedTypes.put(type, matcher.group(1)) != null) { + throw new IllegalArgumentException(type); + } + out.write("import "); + out.write(type); + out.write(";\n"); + } + + /** + * Emits a name like {@code java.lang.String} or {@code + * java.util.List}, shorting it with imports if + * possible. + */ + private void type(String type) throws IOException { + if (this.packagePrefix == null) { + throw new IllegalStateException(); + } + + Matcher m = TYPE_PATTERN.matcher(type); + int pos = 0; + while (true) { + boolean found = m.find(pos); + + // copy non-matching characters like "<" + int typeStart = found ? m.start() : type.length(); + out.write(type, pos, typeStart - pos); + + if (!found) { + break; + } + + // copy a single class name, shortening it if possible + String name = m.group(0); + String imported; + if ((imported = importedTypes.get(name)) != null) { + out.write(imported); + } else if (name.startsWith(packagePrefix) + && name.indexOf('.', packagePrefix.length()) == -1) { + out.write(name.substring(packagePrefix.length())); + } else if (name.startsWith("java.lang.")) { + out.write(name.substring("java.lang.".length())); + } else { + out.write(name); + } + pos = m.end(); + } + } + + /** + * Emits a type declaration. + * + * @param kind such as "class", "interface" or "enum". + */ + public void beginType(String type, String kind, int modifiers) throws IOException { + beginType(type, kind, modifiers, null); + } + + /** + * Emits a type declaration. + * + * @param kind such as "class", "interface" or "enum". + * @param extendsType the class to extend, or null for no extends clause. + */ + public void beginType(String type, String kind, int modifiers, + String extendsType, String... implementsTypes) throws IOException { + indent(); + modifiers(modifiers); + out.write(kind); + out.write(" "); + type(type); + if (extendsType != null) { + out.write("\n"); + indent(); + out.write(" extends "); + type(extendsType); + } + if (implementsTypes.length > 0) { + out.write("\n"); + indent(); + out.write(" implements "); + for (int i = 0; i < implementsTypes.length; i++) { + if (i != 0) { + out.write(", "); + } + type(implementsTypes[i]); + } + } + out.write(" {\n"); + pushScope(Scope.TYPE_DECLARATION); + } + + /** + * Completes the current type declaration. + */ + public void endType() throws IOException { + if (popScope() != Scope.TYPE_DECLARATION) { + throw new IllegalStateException(); + } + indent(); + out.write("}\n"); + } + + /** + * Emits a field declaration. + */ + public void field(String type, String name, int modifiers) throws IOException { + field(type, name, modifiers, null); + } + + public void field(String type, String name, int modifiers, String initialValue) + throws IOException { + indent(); + modifiers(modifiers); + type(type); + out.write(" "); + out.write(name); + + if (initialValue != null) { + out.write(" = "); + out.write(initialValue); + } + out.write(";\n"); + } + + /** + * Emit a method declaration. + * + * @param returnType the method's return type, or null for constructors. + * @param parameters alternating parameter types and names. + * @param name the method name, or the fully qualified class name for + * constructors. + */ + public void beginMethod(String returnType, String name, int modifiers, String... parameters) + throws IOException { + indent(); + modifiers(modifiers); + if (returnType != null) { + type(returnType); + out.write(" "); + out.write(name); + } else { + type(name); + } + out.write("("); + for (int p = 0; p < parameters.length; ) { + if (p != 0) { + out.write(", "); + } + type(parameters[p++]); + out.write(" "); + type(parameters[p++]); + } + out.write(")"); + if ((modifiers & Modifier.ABSTRACT) != 0) { + out.write(";\n"); + pushScope(Scope.ABSTRACT_METHOD); + } else { + out.write(" {\n"); + pushScope(Scope.NON_ABSTRACT_METHOD); + } + } + + /** + * Annotates the next element with {@code annotation}. The annotation has no + * attributes. + */ + public void annotation(String annotation) throws IOException { + indent(); + out.write("@"); + type(annotation); + out.write("\n"); + } + + /** + * Equivalent to {@code annotation(annotationType.getName())}. + */ + public void annotation(Class annotationType) throws IOException { + annotation(annotationType.getName()); + } + + /** + * @param pattern a code pattern like "int i = %s". Shouldn't contain a + * trailing semicolon or newline character. + */ + public void statement(String pattern, Object... args) throws IOException { + checkInMethod(); + indent(); + out.write(String.format(pattern, args)); + out.write(";\n"); + } + + /** + * @param controlFlow the control flow construct and its code, such as + * "if (foo == 5)". Shouldn't contain braces or newline characters. + */ + public void beginControlFlow(String controlFlow) throws IOException { + checkInMethod(); + indent(); + out.write(controlFlow); + out.write(" {\n"); + pushScope(Scope.CONTROL_FLOW); + } + + /** + * @param controlFlow the control flow construct and its code, such as + * "else if (foo == 10)". Shouldn't contain braces or newline characters. + */ + public void nextControlFlow(String controlFlow) throws IOException { + if (popScope() != Scope.CONTROL_FLOW) { + throw new IllegalArgumentException(); + } + + indent(); + pushScope(Scope.CONTROL_FLOW); + out.write("} "); + out.write(controlFlow); + out.write(" {\n"); + } + + public void endControlFlow() throws IOException { + endControlFlow(null); + } + + /** + * @param controlFlow the optional control flow construct and its code, such + * as "while(foo == 20)". Only used for "do/while" control flows. + */ + public void endControlFlow(String controlFlow) throws IOException { + if (popScope() != Scope.CONTROL_FLOW) { + throw new IllegalArgumentException(); + } + + indent(); + if (controlFlow != null) { + out.write("} "); + out.write(controlFlow); + out.write(";\n"); + } else { + out.write("}\n"); + } + } + + /** + * Completes the current method declaration. + */ + public void endMethod() throws IOException { + Scope popped = popScope(); + if (popped == Scope.NON_ABSTRACT_METHOD) { + indent(); + out.write("}\n"); + } else if (popped != Scope.ABSTRACT_METHOD) { + throw new IllegalStateException(); + } + } + + /** + * Returns the string literal representing {@code data}, including wrapping + * quotes. + */ + public static String stringLiteral(String data) { + StringBuilder result = new StringBuilder(); + result.append('"'); + for (int i = 0; i < data.length(); i++) { + char c = data.charAt(i); + switch (c) { + case '"': + result.append("\\\""); + break; + case '\\': + result.append("\\\\"); + break; + case '\t': + result.append("\\\t"); + break; + case '\b': + result.append("\\\b"); + break; + case '\n': + result.append("\\\n"); + break; + case '\r': + result.append("\\\r"); + break; + case '\f': + result.append("\\\f"); + break; + default: + result.append(c); + } + } + result.append('"'); + return result.toString(); + } + + public void close() throws IOException { + out.close(); + } + + /** + * Emit modifier names. + */ + private void modifiers(int modifiers) throws IOException { + if ((modifiers & Modifier.PUBLIC) != 0) { + out.write("public "); + } + if ((modifiers & Modifier.PRIVATE) != 0) { + out.write("private "); + } + if ((modifiers & Modifier.PROTECTED) != 0) { + out.write("protected "); + } + if ((modifiers & Modifier.STATIC) != 0) { + out.write("static "); + } + if ((modifiers & Modifier.FINAL) != 0) { + out.write("final "); + } + if ((modifiers & Modifier.ABSTRACT) != 0) { + out.write("abstract "); + } + if ((modifiers & Modifier.SYNCHRONIZED) != 0) { + out.write("synchronized "); + } + if ((modifiers & Modifier.TRANSIENT) != 0) { + out.write("transient "); + } + if ((modifiers & Modifier.VOLATILE) != 0) { + out.write("volatile "); + } + } + + private void indent() throws IOException { + for (int i = 0; i < scopes.size(); i++) { + out.write(INDENT); + } + } + + private void checkInMethod() { + Scope scope = peekScope(); + if (scope != Scope.NON_ABSTRACT_METHOD && scope != Scope.CONTROL_FLOW) { + throw new IllegalArgumentException(); + } + } + + private void pushScope(Scope pushed) { + scopes.add(pushed); + } + + private Scope peekScope() { + return scopes.get(scopes.size() - 1); + } + + private Scope popScope() { + return scopes.remove(scopes.size() - 1); + } + + private enum Scope { + TYPE_DECLARATION, + ABSTRACT_METHOD, + NON_ABSTRACT_METHOD, + CONTROL_FLOW, + } +} diff --git a/gson/gson-codegen/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/gson/gson-codegen/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 00000000..a052da0c --- /dev/null +++ b/gson/gson-codegen/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +com.google.gson.codegen.GeneratedTypeAdapterProcessor \ No newline at end of file