-
Notifications
You must be signed in to change notification settings - Fork 1.4k
MethodSpec factory for overriding an ExecutableElement. #229
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Modifier> 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()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Off topic: I can't think of an overriding method with type parameters. |
||
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<Modifier> modifiers) { | ||
checkNotNull(modifiers, "modifiers == null"); | ||
this.modifiers.addAll(modifiers); | ||
return this; | ||
} | ||
|
||
public Builder addTypeVariables(Collection<TypeVariableName> typeVariables) { | ||
checkArgument(typeVariables != null, "typeVariables == null"); | ||
this.typeVariables.addAll(typeVariables); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. neat. |
||
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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice. |
||
} | ||
} | ||
|
||
|
@@ -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 <T extends Runnable & Closeable> Runnable everything( | ||
@Nullable String thing, List<? extends T> 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 <T extends java.lang.Runnable & java.io.Closeable> " | ||
+ "java.lang.Runnable everything(java.lang.String arg0, java.util.List<? extends T> arg1) " | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. arg0, arg1 is very sad! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ew, yeah. wtf. that shouldn't happen. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure this is just due to our use of compile-testing which references back into the elements of the test code (cc/ @gk5885). In an annotation processor, you'd have the real names. |
||
+ "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 <T extends java.lang.Runnable & java.io.Closeable> " | ||
// + "java.lang.Runnable everything(" | ||
// + "@com.squareup.javapoet.MethodSpecTest.Nullable java.lang.String arg0, " | ||
// + "java.util.List<? extends T> 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]"); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add an
@Ignore
failing test for these?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done