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

Commit

Permalink
Merge pull request #229 from square/jw/override
Browse files Browse the repository at this point in the history
MethodSpec factory for overriding an ExecutableElement.
  • Loading branch information
JakeWharton committed Feb 19, 2015
2 parents 33bb07e + cb2a6f4 commit 395a2bc
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 10 deletions.
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.
// 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;

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

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

0 comments on commit 395a2bc

Please sign in to comment.