• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.javapoet;
17 
18 import com.google.testing.compile.Compilation;
19 import com.google.testing.compile.CompilationRule;
20 import java.io.Closeable;
21 import java.io.IOException;
22 import java.lang.annotation.ElementType;
23 import java.lang.annotation.Target;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.concurrent.Callable;
29 import java.util.concurrent.TimeoutException;
30 import javax.lang.model.element.ExecutableElement;
31 import javax.lang.model.element.Modifier;
32 import javax.lang.model.element.TypeElement;
33 import javax.lang.model.type.DeclaredType;
34 import javax.lang.model.util.ElementFilter;
35 import javax.lang.model.util.Elements;
36 import javax.lang.model.util.Types;
37 import javax.tools.JavaFileObject;
38 
39 import org.junit.Before;
40 import org.junit.Rule;
41 import org.junit.Test;
42 
43 import static com.google.common.collect.Iterables.getOnlyElement;
44 import static com.google.common.truth.Truth.assertThat;
45 import static com.google.testing.compile.CompilationSubject.assertThat;
46 import static com.google.testing.compile.Compiler.javac;
47 import static com.squareup.javapoet.TestUtil.findFirst;
48 import static javax.lang.model.util.ElementFilter.methodsIn;
49 import static org.junit.Assert.fail;
50 
51 public final class MethodSpecTest {
52   @Rule public final CompilationRule compilation = new CompilationRule();
53 
54   private Elements elements;
55   private Types types;
56 
setUp()57   @Before public void setUp() {
58     elements = compilation.getElements();
59     types = compilation.getTypes();
60   }
61 
getElement(Class<?> clazz)62   private TypeElement getElement(Class<?> clazz) {
63     return elements.getTypeElement(clazz.getCanonicalName());
64   }
65 
nullAnnotationsAddition()66   @Test public void nullAnnotationsAddition() {
67     try {
68       MethodSpec.methodBuilder("doSomething").addAnnotations(null);
69       fail();
70     } catch (IllegalArgumentException expected) {
71       assertThat(expected).hasMessageThat().isEqualTo("annotationSpecs == null");
72     }
73   }
74 
nullTypeVariablesAddition()75   @Test public void nullTypeVariablesAddition() {
76     try {
77       MethodSpec.methodBuilder("doSomething").addTypeVariables(null);
78       fail();
79     } catch (IllegalArgumentException expected) {
80       assertThat(expected).hasMessageThat().isEqualTo("typeVariables == null");
81     }
82   }
83 
nullParametersAddition()84   @Test public void nullParametersAddition() {
85     try {
86       MethodSpec.methodBuilder("doSomething").addParameters(null);
87       fail();
88     } catch (IllegalArgumentException expected) {
89       assertThat(expected).hasMessageThat().isEqualTo("parameterSpecs == null");
90     }
91   }
92 
nullExceptionsAddition()93   @Test public void nullExceptionsAddition() {
94     try {
95       MethodSpec.methodBuilder("doSomething").addExceptions(null);
96       fail();
97     } catch (IllegalArgumentException expected) {
98       assertThat(expected).hasMessageThat().isEqualTo("exceptions == null");
99     }
100   }
101 
102   @Target(ElementType.PARAMETER)
103   @interface Nullable {
104   }
105 
106   abstract static class Everything {
everything( @ullable String thing, List<? extends T> things)107     @Deprecated protected abstract <T extends Runnable & Closeable> Runnable everything(
108         @Nullable String thing, List<? extends T> things) throws IOException, SecurityException;
109   }
110 
111   abstract static class Generics {
run(R param)112     <T, R, V extends Throwable> T run(R param) throws V {
113       return null;
114     }
115   }
116 
117   abstract static class HasAnnotation {
toString()118     @Override public abstract String toString();
119   }
120 
121   interface Throws<R extends RuntimeException> {
fail()122     void fail() throws R;
123   }
124 
125   interface ExtendsOthers extends Callable<Integer>, Comparable<ExtendsOthers>,
126       Throws<IllegalStateException> {
127   }
128 
129   interface ExtendsIterableWithDefaultMethods extends Iterable<Object> {
130   }
131 
132   final class FinalClass {
method()133     void method() {
134     }
135   }
136 
137   abstract static class InvalidOverrideMethods {
finalMethod()138     final void finalMethod() {
139     }
140 
privateMethod()141     private void privateMethod() {
142     }
143 
staticMethod()144     static void staticMethod() {
145     }
146   }
147 
overrideEverything()148   @Test public void overrideEverything() {
149     TypeElement classElement = getElement(Everything.class);
150     ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
151     MethodSpec method = MethodSpec.overriding(methodElement).build();
152     assertThat(method.toString()).isEqualTo(""
153         + "@java.lang.Override\n"
154         + "protected <T extends java.lang.Runnable & java.io.Closeable> java.lang.Runnable "
155         + "everything(\n"
156         + "    java.lang.String arg0, java.util.List<? extends T> arg1) throws java.io.IOException,\n"
157         + "    java.lang.SecurityException {\n"
158         + "}\n");
159   }
160 
overrideGenerics()161   @Test public void overrideGenerics() {
162     TypeElement classElement = getElement(Generics.class);
163     ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
164     MethodSpec method = MethodSpec.overriding(methodElement)
165         .addStatement("return null")
166         .build();
167     assertThat(method.toString()).isEqualTo(""
168         + "@java.lang.Override\n"
169         + "<T, R, V extends java.lang.Throwable> T run(R param) throws V {\n"
170         + "  return null;\n"
171         + "}\n");
172   }
173 
overrideDoesNotCopyOverrideAnnotation()174   @Test public void overrideDoesNotCopyOverrideAnnotation() {
175     TypeElement classElement = getElement(HasAnnotation.class);
176     ExecutableElement exec = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
177     MethodSpec method = MethodSpec.overriding(exec).build();
178     assertThat(method.toString()).isEqualTo(""
179         + "@java.lang.Override\n"
180         + "public java.lang.String toString() {\n"
181         + "}\n");
182   }
183 
overrideDoesNotCopyDefaultModifier()184   @Test public void overrideDoesNotCopyDefaultModifier() {
185     TypeElement classElement = getElement(ExtendsIterableWithDefaultMethods.class);
186     DeclaredType classType = (DeclaredType) classElement.asType();
187     List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
188     ExecutableElement exec = findFirst(methods, "spliterator");
189     MethodSpec method = MethodSpec.overriding(exec, classType, types).build();
190     assertThat(method.toString()).isEqualTo(""
191         + "@java.lang.Override\n"
192         + "public java.util.Spliterator<java.lang.Object> spliterator() {\n"
193         + "}\n");
194   }
195 
overrideExtendsOthersWorksWithActualTypeParameters()196   @Test public void overrideExtendsOthersWorksWithActualTypeParameters() {
197     TypeElement classElement = getElement(ExtendsOthers.class);
198     DeclaredType classType = (DeclaredType) classElement.asType();
199     List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
200     ExecutableElement exec = findFirst(methods, "call");
201     MethodSpec method = MethodSpec.overriding(exec, classType, types).build();
202     assertThat(method.toString()).isEqualTo(""
203         + "@java.lang.Override\n"
204         + "public java.lang.Integer call() throws java.lang.Exception {\n"
205         + "}\n");
206     exec = findFirst(methods, "compareTo");
207     method = MethodSpec.overriding(exec, classType, types).build();
208     assertThat(method.toString()).isEqualTo(""
209         + "@java.lang.Override\n"
210         + "public int compareTo(" + ExtendsOthers.class.getCanonicalName() + " arg0) {\n"
211         + "}\n");
212     exec = findFirst(methods, "fail");
213     method = MethodSpec.overriding(exec, classType, types).build();
214     assertThat(method.toString()).isEqualTo(""
215         + "@java.lang.Override\n"
216         + "public void fail() throws java.lang.IllegalStateException {\n"
217         + "}\n");
218   }
219 
overrideFinalClassMethod()220   @Test public void overrideFinalClassMethod() {
221     TypeElement classElement = getElement(FinalClass.class);
222     List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
223     try {
224       MethodSpec.overriding(findFirst(methods, "method"));
225       fail();
226     } catch (IllegalArgumentException expected) {
227       assertThat(expected).hasMessageThat().isEqualTo(
228           "Cannot override method on final class com.squareup.javapoet.MethodSpecTest.FinalClass");
229     }
230   }
231 
overrideInvalidModifiers()232   @Test public void overrideInvalidModifiers() {
233     TypeElement classElement = getElement(InvalidOverrideMethods.class);
234     List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
235     try {
236       MethodSpec.overriding(findFirst(methods, "finalMethod"));
237       fail();
238     } catch (IllegalArgumentException expected) {
239       assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [final]");
240     }
241     try {
242       MethodSpec.overriding(findFirst(methods, "privateMethod"));
243       fail();
244     } catch (IllegalArgumentException expected) {
245       assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [private]");
246     }
247     try {
248       MethodSpec.overriding(findFirst(methods, "staticMethod"));
249       fail();
250     } catch (IllegalArgumentException expected) {
251       assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [static]");
252     }
253   }
254 
255   abstract static class AbstractClassWithPrivateAnnotation {
256 
257     private @interface PrivateAnnotation{ }
258 
foo(@rivateAnnotation final String bar)259     abstract void foo(@PrivateAnnotation final String bar);
260   }
261 
overrideDoesNotCopyParameterAnnotations()262   @Test public void overrideDoesNotCopyParameterAnnotations() {
263     TypeElement abstractTypeElement = getElement(AbstractClassWithPrivateAnnotation.class);
264     ExecutableElement fooElement = ElementFilter.methodsIn(abstractTypeElement.getEnclosedElements()).get(0);
265     ClassName implClassName = ClassName.get("com.squareup.javapoet", "Impl");
266     TypeSpec type = TypeSpec.classBuilder(implClassName)
267             .superclass(abstractTypeElement.asType())
268             .addMethod(MethodSpec.overriding(fooElement).build())
269             .build();
270     JavaFileObject jfo = JavaFile.builder(implClassName.packageName, type).build().toJavaFileObject();
271     Compilation compilation = javac().compile(jfo);
272     assertThat(compilation).succeeded();
273   }
274 
equalsAndHashCode()275   @Test public void equalsAndHashCode() {
276     MethodSpec a = MethodSpec.constructorBuilder().build();
277     MethodSpec b = MethodSpec.constructorBuilder().build();
278     assertThat(a.equals(b)).isTrue();
279     assertThat(a.hashCode()).isEqualTo(b.hashCode());
280     a = MethodSpec.methodBuilder("taco").build();
281     b = MethodSpec.methodBuilder("taco").build();
282     assertThat(a.equals(b)).isTrue();
283     assertThat(a.hashCode()).isEqualTo(b.hashCode());
284     TypeElement classElement = getElement(Everything.class);
285     ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
286     a = MethodSpec.overriding(methodElement).build();
287     b = MethodSpec.overriding(methodElement).build();
288     assertThat(a.equals(b)).isTrue();
289     assertThat(a.hashCode()).isEqualTo(b.hashCode());
290   }
291 
withoutParameterJavaDoc()292   @Test public void withoutParameterJavaDoc() {
293     MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco")
294         .addModifiers(Modifier.PRIVATE)
295         .addParameter(TypeName.DOUBLE, "money")
296         .addJavadoc("Gets the best Taco\n")
297         .build();
298     assertThat(methodSpec.toString()).isEqualTo(""
299         + "/**\n"
300         + " * Gets the best Taco\n"
301         + " */\n"
302         + "private void getTaco(double money) {\n"
303         + "}\n");
304   }
305 
withParameterJavaDoc()306   @Test public void withParameterJavaDoc() {
307     MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco")
308         .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money")
309             .addJavadoc("the amount required to buy the taco.\n")
310             .build())
311         .addParameter(ParameterSpec.builder(TypeName.INT, "count")
312             .addJavadoc("the number of Tacos to buy.\n")
313             .build())
314         .addJavadoc("Gets the best Taco money can buy.\n")
315         .build();
316     assertThat(methodSpec.toString()).isEqualTo(""
317         + "/**\n"
318         + " * Gets the best Taco money can buy.\n"
319         + " *\n"
320         + " * @param money the amount required to buy the taco.\n"
321         + " * @param count the number of Tacos to buy.\n"
322         + " */\n"
323         + "void getTaco(double money, int count) {\n"
324         + "}\n");
325   }
326 
withParameterJavaDocAndWithoutMethodJavadoc()327   @Test public void withParameterJavaDocAndWithoutMethodJavadoc() {
328     MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco")
329         .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money")
330             .addJavadoc("the amount required to buy the taco.\n")
331             .build())
332         .addParameter(ParameterSpec.builder(TypeName.INT, "count")
333             .addJavadoc("the number of Tacos to buy.\n")
334             .build())
335         .build();
336     assertThat(methodSpec.toString()).isEqualTo(""
337         + "/**\n"
338         + " * @param money the amount required to buy the taco.\n"
339         + " * @param count the number of Tacos to buy.\n"
340         + " */\n"
341         + "void getTaco(double money, int count) {\n"
342         + "}\n");
343   }
344 
duplicateExceptionsIgnored()345   @Test public void duplicateExceptionsIgnored() {
346     ClassName ioException = ClassName.get(IOException.class);
347     ClassName timeoutException = ClassName.get(TimeoutException.class);
348     MethodSpec methodSpec = MethodSpec.methodBuilder("duplicateExceptions")
349       .addException(ioException)
350       .addException(timeoutException)
351       .addException(timeoutException)
352       .addException(ioException)
353       .build();
354     assertThat(methodSpec.exceptions).isEqualTo(Arrays.asList(ioException, timeoutException));
355     assertThat(methodSpec.toBuilder().addException(ioException).build().exceptions)
356       .isEqualTo(Arrays.asList(ioException, timeoutException));
357   }
358 
nullIsNotAValidMethodName()359   @Test public void nullIsNotAValidMethodName() {
360     try {
361       MethodSpec.methodBuilder(null);
362       fail("NullPointerException expected");
363     } catch (NullPointerException e) {
364       assertThat(e.getMessage()).isEqualTo("name == null");
365     }
366   }
367 
addModifiersVarargsShouldNotBeNull()368   @Test public void addModifiersVarargsShouldNotBeNull() {
369     try {
370       MethodSpec.methodBuilder("taco")
371               .addModifiers((Modifier[]) null);
372       fail("NullPointerException expected");
373     } catch (NullPointerException e) {
374       assertThat(e.getMessage()).isEqualTo("modifiers == null");
375     }
376   }
377 
modifyMethodName()378   @Test public void modifyMethodName() {
379     MethodSpec methodSpec = MethodSpec.methodBuilder("initialMethod")
380         .build()
381         .toBuilder()
382         .setName("revisedMethod")
383         .build();
384 
385     assertThat(methodSpec.toString()).isEqualTo("" + "void revisedMethod() {\n" + "}\n");
386   }
387 
modifyAnnotations()388   @Test public void modifyAnnotations() {
389     MethodSpec.Builder builder = MethodSpec.methodBuilder("foo")
390             .addAnnotation(Override.class)
391             .addAnnotation(SuppressWarnings.class);
392 
393     builder.annotations.remove(1);
394     assertThat(builder.build().annotations).hasSize(1);
395   }
396 
modifyModifiers()397   @Test public void modifyModifiers() {
398     MethodSpec.Builder builder = MethodSpec.methodBuilder("foo")
399             .addModifiers(Modifier.PUBLIC, Modifier.STATIC);
400 
401     builder.modifiers.remove(1);
402     assertThat(builder.build().modifiers).containsExactly(Modifier.PUBLIC);
403   }
404 
modifyParameters()405   @Test public void modifyParameters() {
406     MethodSpec.Builder builder = MethodSpec.methodBuilder("foo")
407             .addParameter(int.class, "source");
408 
409     builder.parameters.remove(0);
410     assertThat(builder.build().parameters).isEmpty();
411   }
412 
modifyTypeVariables()413   @Test public void modifyTypeVariables() {
414     TypeVariableName t = TypeVariableName.get("T");
415     MethodSpec.Builder builder = MethodSpec.methodBuilder("foo")
416             .addTypeVariable(t)
417             .addTypeVariable(TypeVariableName.get("V"));
418 
419     builder.typeVariables.remove(1);
420     assertThat(builder.build().typeVariables).containsExactly(t);
421   }
422 
ensureTrailingNewline()423   @Test public void ensureTrailingNewline() {
424     MethodSpec methodSpec = MethodSpec.methodBuilder("method")
425         .addCode("codeWithNoNewline();")
426         .build();
427 
428     assertThat(methodSpec.toString()).isEqualTo(""
429         + "void method() {\n"
430         + "  codeWithNoNewline();\n"
431         + "}\n");
432   }
433 
434   /** Ensures that we don't add a duplicate newline if one is already present. */
ensureTrailingNewlineWithExistingNewline()435   @Test public void ensureTrailingNewlineWithExistingNewline() {
436     MethodSpec methodSpec = MethodSpec.methodBuilder("method")
437         .addCode("codeWithNoNewline();\n") // Have a newline already, so ensure we're not adding one
438         .build();
439 
440     assertThat(methodSpec.toString()).isEqualTo(""
441         + "void method() {\n"
442         + "  codeWithNoNewline();\n"
443         + "}\n");
444   }
445 
controlFlowWithNamedCodeBlocks()446   @Test public void controlFlowWithNamedCodeBlocks() {
447     Map<String, Object> m = new HashMap<>();
448     m.put("field", "valueField");
449     m.put("threshold", "5");
450 
451     MethodSpec methodSpec = MethodSpec.methodBuilder("method")
452         .beginControlFlow(named("if ($field:N > $threshold:L)", m))
453         .nextControlFlow(named("else if ($field:N == $threshold:L)", m))
454         .endControlFlow()
455         .build();
456 
457     assertThat(methodSpec.toString()).isEqualTo(""
458         + "void method() {\n"
459         + "  if (valueField > 5) {\n"
460         + "  } else if (valueField == 5) {\n"
461         + "  }\n"
462         + "}\n");
463   }
464 
doWhileWithNamedCodeBlocks()465   @Test public void doWhileWithNamedCodeBlocks() {
466     Map<String, Object> m = new HashMap<>();
467     m.put("field", "valueField");
468     m.put("threshold", "5");
469 
470     MethodSpec methodSpec = MethodSpec.methodBuilder("method")
471         .beginControlFlow("do")
472         .addStatement(named("$field:N--", m))
473         .endControlFlow(named("while ($field:N > $threshold:L)", m))
474         .build();
475 
476     assertThat(methodSpec.toString()).isEqualTo(""
477         + "void method() {\n" +
478         "  do {\n" +
479         "    valueField--;\n" +
480         "  } while (valueField > 5);\n" +
481         "}\n");
482   }
483 
named(String format, Map<String, ?> args)484   private static CodeBlock named(String format, Map<String, ?> args){
485     return CodeBlock.builder().addNamed(format, args).build();
486   }
487 
488 }
489