Skip to content
This repository has been archived by the owner on Oct 10, 2024. It is now read-only.

MethodSpec factory for overriding an ExecutableElement. #229

Merged
merged 1 commit into from
Feb 19, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion src/main/java/com/squareup/javapoet/MethodSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Copy link
Collaborator

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// 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()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;

Expand Down Expand Up @@ -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);
Expand Down
110 changes: 101 additions & 9 deletions src/test/java/com/squareup/javapoet/MethodSpecTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.

}
}

Expand All @@ -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");
}
}

Expand All @@ -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");
}
}

Expand All @@ -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) "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arg0, arg1 is very sad!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ew, yeah. wtf. that shouldn't happen.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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]");
}
}
}
}