From cb2a6f497ad340b974f8817400bc755218aca59e Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Mon, 16 Feb 2015 00:39:05 -0800 Subject: [PATCH] MethodSpec factory for overriding an ExecutableElement. --- .../com/squareup/javapoet/MethodSpec.java | 60 +++++++++- .../com/squareup/javapoet/MethodSpecTest.java | 110 ++++++++++++++++-- 2 files changed, 160 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/squareup/javapoet/MethodSpec.java b/src/main/java/com/squareup/javapoet/MethodSpec.java index a05e29f43..fb9524351 100644 --- a/src/main/java/com/squareup/javapoet/MethodSpec.java +++ b/src/main/java/com/squareup/javapoet/MethodSpec.java @@ -19,13 +19,19 @@ import java.io.StringWriter; import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Collections; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.lang.model.SourceVersion; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; import static com.squareup.javapoet.Util.checkArgument; import static com.squareup.javapoet.Util.checkNotNull; @@ -150,6 +156,52 @@ public static Builder constructorBuilder() { return new Builder(CONSTRUCTOR); } + /** + * Create a builder which overrides {@code method}. This will copy its visibility modifiers, type + * parameters, return type, name, parameters, and throws declarations. An {@link Override} + * annotation will be added. + */ + public static Builder overriding(ExecutableElement method) { + checkNotNull(method, "method == null"); + + Set modifiers = method.getModifiers(); + if (modifiers.contains(Modifier.PRIVATE) + || modifiers.contains(Modifier.FINAL) + || modifiers.contains(Modifier.STATIC)) { + throw new IllegalArgumentException("cannot override method with modifiers: " + modifiers); + } + + String methodName = method.getSimpleName().toString(); + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName); + + // TODO copy method annotations. + // TODO check to ensure we're not duplicating override annotation. + methodBuilder.addAnnotation(Override.class); + + modifiers = new LinkedHashSet<>(modifiers); // Local copy so we can remove. + modifiers.remove(Modifier.ABSTRACT); + methodBuilder.addModifiers(modifiers); + + for (TypeParameterElement typeParameterElement : method.getTypeParameters()) { + methodBuilder.addTypeVariable( + TypeVariableName.get((TypeVariable) typeParameterElement.asType())); + } + + methodBuilder.returns(TypeName.get(method.getReturnType())); + + for (VariableElement parameter : method.getParameters()) { + // TODO copy parameter annotations. + methodBuilder.addParameter(TypeName.get(parameter.asType()), + parameter.getSimpleName().toString()); + } + + for (TypeMirror thrownType : method.getThrownTypes()) { + methodBuilder.addException(TypeName.get(thrownType)); + } + + return methodBuilder; + } + public static final class Builder { private final String name; @@ -200,6 +252,12 @@ public Builder addModifiers(Modifier... modifiers) { return this; } + public Builder addModifiers(Collection modifiers) { + checkNotNull(modifiers, "modifiers == null"); + this.modifiers.addAll(modifiers); + return this; + } + public Builder addTypeVariables(Collection typeVariables) { checkArgument(typeVariables != null, "typeVariables == null"); this.typeVariables.addAll(typeVariables); diff --git a/src/test/java/com/squareup/javapoet/MethodSpecTest.java b/src/test/java/com/squareup/javapoet/MethodSpecTest.java index 041fd2977..146b7a75f 100644 --- a/src/test/java/com/squareup/javapoet/MethodSpecTest.java +++ b/src/test/java/com/squareup/javapoet/MethodSpecTest.java @@ -15,20 +15,40 @@ */ package com.squareup.javapoet; +import com.google.common.collect.ImmutableSet; +import com.google.testing.compile.CompilationRule; +import java.io.Closeable; +import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.util.List; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; +import static javax.lang.model.util.ElementFilter.methodsIn; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public final class MethodSpecTest { + @Rule public final CompilationRule compilation = new CompilationRule(); + + private TypeElement getElement(Class clazz) { + return compilation.getElements().getTypeElement(clazz.getCanonicalName()); + } @Test public void nullAnnotationsAddition() { try { MethodSpec.methodBuilder("doSomething").addAnnotations(null); fail(); } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()) - .isEqualTo("annotationSpecs == null"); + assertThat(expected).hasMessage("annotationSpecs == null"); } } @@ -37,8 +57,7 @@ public final class MethodSpecTest { MethodSpec.methodBuilder("doSomething").addTypeVariables(null); fail(); } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()) - .isEqualTo("typeVariables == null"); + assertThat(expected).hasMessage("typeVariables == null"); } } @@ -47,8 +66,7 @@ public final class MethodSpecTest { MethodSpec.methodBuilder("doSomething").addParameters(null); fail(); } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()) - .isEqualTo("parameterSpecs == null"); + assertThat(expected).hasMessage("parameterSpecs == null"); } } @@ -57,8 +75,82 @@ public final class MethodSpecTest { MethodSpec.methodBuilder("doSomething").addExceptions(null); fail(); } catch (IllegalArgumentException expected) { - assertThat(expected.getMessage()) - .isEqualTo("exceptions == null"); + assertThat(expected).hasMessage("exceptions == null"); + } + } + + @Target(ElementType.PARAMETER) + @interface Nullable { + } + + @SuppressWarnings("unused") // Used via mirror API. + abstract static class Everything { + @Deprecated protected abstract Runnable everything( + @Nullable String thing, List things) throws IOException, SecurityException; + } + + @SuppressWarnings("unused") // Used via mirror API. + abstract static class HasAnnotation { + @Override public abstract String toString(); + } + + @Test public void overrideEverything() { + TypeElement classElement = getElement(Everything.class); + ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements())); + + MethodSpec method = MethodSpec.overriding(methodElement).build(); + assertThat(method.toString()).isEqualTo("" + + "@java.lang.Override\n" + + "protected " + + "java.lang.Runnable everything(java.lang.String arg0, java.util.List arg1) " + + "throws java.io.IOException, java.lang.SecurityException {\n" + + "}\n"); + // TODO see TODOs in MethodSpec.override + //assertThat(method.toString()).isEqualTo("" + // + "@java.lang.Override\n" + // + "@java.lang.Deprecated\n" + // + "protected " + // + "java.lang.Runnable everything(" + // + "@com.squareup.javapoet.MethodSpecTest.Nullable java.lang.String arg0, " + // + "java.util.List arg1) " + // + "throws java.io.IOException, java.lang.SecurityException {\n" + // + "}\n"); + } + + @Ignore // TODO see TODOs in MethodSpec.override + @Test public void overrideDoesNotCopyOverrideAnnotation() { + TypeElement classElement = getElement(Everything.class); + ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements())); + + MethodSpec method = MethodSpec.overriding(methodElement).build(); + assertThat(method.toString()).isEqualTo("" + + "@java.lang.Override\n" + + "public java.lang.String toString() {" + + "}"); + } + + @Test public void overrideInvalidModifiers() { + ExecutableElement method = mock(ExecutableElement.class); + when(method.getModifiers()).thenReturn(ImmutableSet.of(Modifier.FINAL)); + try { + MethodSpec.overriding(method); + fail(); + } catch (IllegalArgumentException expected) { + assertThat(expected).hasMessage("cannot override method with modifiers: [final]"); + } + when(method.getModifiers()).thenReturn(ImmutableSet.of(Modifier.PRIVATE)); + try { + MethodSpec.overriding(method); + fail(); + } catch (IllegalArgumentException expected) { + assertThat(expected).hasMessage("cannot override method with modifiers: [private]"); + } + when(method.getModifiers()).thenReturn(ImmutableSet.of(Modifier.STATIC)); + try { + MethodSpec.overriding(method); + fail(); + } catch (IllegalArgumentException expected) { + assertThat(expected).hasMessage("cannot override method with modifiers: [static]"); } } -} \ No newline at end of file +}