Skip to content

Commit

Permalink
Copy JavaPoet (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
dstepanov authored Oct 18, 2023
1 parent 4e4a9cb commit 51658d5
Show file tree
Hide file tree
Showing 41 changed files with 12,239 additions and 18 deletions.
14 changes: 12 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,27 @@ micronaut-docs = "2.0.0"
micronaut-test = "3.9.1"
groovy = "4.0.12"
spock = "2.3-groovy-4.0"
managed-javapoet = "1.13.0"
managed-kotlinpoet = "1.14.2"

google-truth = "1.1.3"
google-compile-testing = "0.19"
google-jimfs = "1.2"
mockito = "3.12.4"
junit-jupiter-engine = "5.9.2"
[libraries]
# Core
micronaut-core = { module = 'io.micronaut:micronaut-core-bom', version.ref = 'micronaut' }

managed-javapoet = { module = "com.squareup:javapoet", version.ref = "managed-javapoet" }
managed-kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "managed-kotlinpoet" }
managed-kotlinpoet-javapoet = { module = "com.squareup:kotlinpoet-javapoet", version.ref = "managed-kotlinpoet" }

# Testing
google-truth = { module = "com.google.truth:truth", version.ref = "google-truth" }
google-compile-testing = { module = "com.google.testing.compile:compile-testing", version.ref = "google-compile-testing" }
google-jimfs = { module = "com.google.jimfs:jimfs", version.ref = "google-jimfs" }
mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter-engine" }

[bundles]

[plugins]
23 changes: 20 additions & 3 deletions sourcegen-generator-java/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,26 @@ plugins {
}

dependencies {
api(libs.managed.javapoet) {
because("Groovy annotation processing would fail without it")
}
implementation(projects.sourcegenGenerator)
testImplementation(libs.google.truth)
testImplementation(libs.google.compile.testing)
testImplementation(libs.google.jimfs)
testImplementation(libs.mockito)
}

tasks.withType(Test::class.java).configureEach {
useJUnit()
predictiveSelection {
enabled = false
}
}

tasks.withType(Checkstyle::class.java).configureEach {
exclude("**/javapoet/**")
}

spotless {
java {
targetExclude("**/javapoet/**")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
*/
package io.micronaut.sourcegen;

import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;
import io.micronaut.sourcegen.javapoet.AnnotationSpec;
import io.micronaut.sourcegen.javapoet.ClassName;
import io.micronaut.sourcegen.javapoet.FieldSpec;
import io.micronaut.sourcegen.javapoet.JavaFile;
import io.micronaut.sourcegen.javapoet.MethodSpec;
import io.micronaut.sourcegen.javapoet.ParameterSpec;
import io.micronaut.sourcegen.javapoet.ParameterizedTypeName;
import io.micronaut.sourcegen.javapoet.TypeName;
import io.micronaut.sourcegen.javapoet.TypeSpec;
import io.micronaut.sourcegen.javapoet.TypeVariableName;
import io.micronaut.sourcegen.javapoet.WildcardTypeName;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.naming.NameUtils;
Expand Down Expand Up @@ -239,7 +239,7 @@ private AnnotationSpec asAnnotationSpec(AnnotationDef annotationDef) {
return builder.build();
}

// Copy from com.squareup.javapoet.Util
// Copy from io.micronaut.javapoet.Util
private static String characterLiteralWithoutSingleQuotes(char c) {
// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
return switch (c) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
/*
* Copyright (C) 2015 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 io.micronaut.sourcegen.javapoet;

import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/** A generated annotation on a declaration. */
public final class AnnotationSpec {
public static final String VALUE = "value";

public final TypeName type;
public final Map<String, List<CodeBlock>> members;

private AnnotationSpec(Builder builder) {
this.type = builder.type;
this.members = Util.immutableMultimap(builder.members);
}

void emit(CodeWriter codeWriter, boolean inline) throws IOException {
String whitespace = inline ? "" : "\n";
String memberSeparator = inline ? ", " : ",\n";
if (members.isEmpty()) {
// @Singleton
codeWriter.emit("@$T", type);
} else if (members.size() == 1 && members.containsKey("value")) {
// @Named("foo")
codeWriter.emit("@$T(", type);
emitAnnotationValues(codeWriter, whitespace, memberSeparator, members.get("value"));
codeWriter.emit(")");
} else {
// Inline:
// @Column(name = "updated_at", nullable = false)
//
// Not inline:
// @Column(
// name = "updated_at",
// nullable = false
// )
codeWriter.emit("@$T(" + whitespace, type);
codeWriter.indent(2);
for (Iterator<Map.Entry<String, List<CodeBlock>>> i
= members.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<String, List<CodeBlock>> entry = i.next();
codeWriter.emit("$L = ", entry.getKey());
emitAnnotationValues(codeWriter, whitespace, memberSeparator, entry.getValue());
if (i.hasNext()) codeWriter.emit(memberSeparator);
}
codeWriter.unindent(2);
codeWriter.emit(whitespace + ")");
}
}

private void emitAnnotationValues(CodeWriter codeWriter, String whitespace,
String memberSeparator, List<CodeBlock> values) throws IOException {
if (values.size() == 1) {
codeWriter.indent(2);
codeWriter.emit(values.get(0));
codeWriter.unindent(2);
return;
}

codeWriter.emit("{" + whitespace);
codeWriter.indent(2);
boolean first = true;
for (CodeBlock codeBlock : values) {
if (!first) codeWriter.emit(memberSeparator);
codeWriter.emit(codeBlock);
first = false;
}
codeWriter.unindent(2);
codeWriter.emit(whitespace + "}");
}

public static AnnotationSpec get(Annotation annotation) {
return get(annotation, false);
}

public static AnnotationSpec get(Annotation annotation, boolean includeDefaultValues) {
Builder builder = builder(annotation.annotationType());
try {
Method[] methods = annotation.annotationType().getDeclaredMethods();
Arrays.sort(methods, Comparator.comparing(Method::getName));
for (Method method : methods) {
Object value = method.invoke(annotation);
if (!includeDefaultValues) {
if (Objects.deepEquals(value, method.getDefaultValue())) {
continue;
}
}
if (value.getClass().isArray()) {
for (int i = 0; i < Array.getLength(value); i++) {
builder.addMemberForValue(method.getName(), Array.get(value, i));
}
continue;
}
if (value instanceof Annotation) {
builder.addMember(method.getName(), "$L", get((Annotation) value));
continue;
}
builder.addMemberForValue(method.getName(), value);
}
} catch (Exception e) {
throw new RuntimeException("Reflecting " + annotation + " failed!", e);
}
return builder.build();
}

public static AnnotationSpec get(AnnotationMirror annotation) {
TypeElement element = (TypeElement) annotation.getAnnotationType().asElement();
Builder builder = AnnotationSpec.builder(ClassName.get(element));
Visitor visitor = new Visitor(builder);
for (ExecutableElement executableElement : annotation.getElementValues().keySet()) {
String name = executableElement.getSimpleName().toString();
AnnotationValue value = annotation.getElementValues().get(executableElement);
value.accept(visitor, name);
}
return builder.build();
}

public static Builder builder(ClassName type) {
Util.checkNotNull(type, "type == null");
return new Builder(type);
}

public static Builder builder(Class<?> type) {
return builder(ClassName.get(type));
}

public Builder toBuilder() {
Builder builder = new Builder(type);
for (Map.Entry<String, List<CodeBlock>> entry : members.entrySet()) {
builder.members.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return builder;
}

@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
return toString().equals(o.toString());
}

@Override public int hashCode() {
return toString().hashCode();
}

@Override public String toString() {
StringBuilder out = new StringBuilder();
try {
CodeWriter codeWriter = new CodeWriter(out);
codeWriter.emit("$L", this);
return out.toString();
} catch (IOException e) {
throw new AssertionError();
}
}

public static final class Builder {
private final TypeName type;

public final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();

private Builder(TypeName type) {
this.type = type;
}

public Builder addMember(String name, String format, Object... args) {
return addMember(name, CodeBlock.of(format, args));
}

public Builder addMember(String name, CodeBlock codeBlock) {
List<CodeBlock> values = members.computeIfAbsent(name, k -> new ArrayList<>());
values.add(codeBlock);
return this;
}

/**
* Delegates to {@link #addMember(String, String, Object...)}, with parameter {@code format}
* depending on the given {@code value} object. Falls back to {@code "$L"} literal format if
* the class of the given {@code value} object is not supported.
*/
Builder addMemberForValue(String memberName, Object value) {
Util.checkNotNull(memberName, "memberName == null");
Util.checkNotNull(value, "value == null, constant non-null value expected for %s", memberName);
Util.checkArgument(SourceVersion.isName(memberName), "not a valid name: %s", memberName);
if (value instanceof Class<?>) {
return addMember(memberName, "$T.class", value);
}
if (value instanceof Enum) {
return addMember(memberName, "$T.$L", value.getClass(), ((Enum<?>) value).name());
}
if (value instanceof String) {
return addMember(memberName, "$S", value);
}
if (value instanceof Float) {
return addMember(memberName, "$Lf", value);
}
if (value instanceof Character) {
return addMember(memberName, "'$L'", Util.characterLiteralWithoutSingleQuotes((char) value));
}
return addMember(memberName, "$L", value);
}

public AnnotationSpec build() {
for (String name : members.keySet()) {
Util.checkNotNull(name, "name == null");
Util.checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
}
return new AnnotationSpec(this);
}
}

/**
* Annotation value visitor adding members to the given builder instance.
*/
private static class Visitor extends SimpleAnnotationValueVisitor8<Builder, String> {
final Builder builder;

Visitor(Builder builder) {
super(builder);
this.builder = builder;
}

@Override protected Builder defaultAction(Object o, String name) {
return builder.addMemberForValue(name, o);
}

@Override public Builder visitAnnotation(AnnotationMirror a, String name) {
return builder.addMember(name, "$L", get(a));
}

@Override public Builder visitEnumConstant(VariableElement c, String name) {
return builder.addMember(name, "$T.$L", c.asType(), c.getSimpleName());
}

@Override public Builder visitType(TypeMirror t, String name) {
return builder.addMember(name, "$T.class", t);
}

@Override public Builder visitArray(List<? extends AnnotationValue> values, String name) {
for (AnnotationValue value : values) {
value.accept(this, name);
}
return builder;
}
}
}
Loading

0 comments on commit 51658d5

Please sign in to comment.