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.CompilationRule;
19 import java.io.Closeable;
20 import java.io.IOException;
21 import java.lang.annotation.ElementType;
22 import java.lang.annotation.Target;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.List;
26 import java.util.concurrent.Callable;
27 import java.util.concurrent.TimeoutException;
28 import javax.lang.model.element.ExecutableElement;
29 import javax.lang.model.element.Modifier;
30 import javax.lang.model.element.TypeElement;
31 import javax.lang.model.type.DeclaredType;
32 import javax.lang.model.util.Elements;
33 import javax.lang.model.util.Types;
34 import org.junit.Before;
35 import org.junit.Rule;
36 import org.junit.Test;
37 
38 import static com.google.common.collect.Iterables.getOnlyElement;
39 import static com.google.common.truth.Truth.assertThat;
40 import static javax.lang.model.util.ElementFilter.methodsIn;
41 import static org.junit.Assert.fail;
42 
43 public final class MethodSpecTest {
44   @Rule public final CompilationRule compilation = new CompilationRule();
45 
46   private Elements elements;
47   private Types types;
48 
setUp()49   @Before public void setUp() {
50     elements = compilation.getElements();
51     types = compilation.getTypes();
52   }
53 
getElement(Class<?> clazz)54   private TypeElement getElement(Class<?> clazz) {
55     return elements.getTypeElement(clazz.getCanonicalName());
56   }
57 
findFirst(Collection<ExecutableElement> elements, String name)58   private ExecutableElement findFirst(Collection<ExecutableElement> elements, String name) {
59     for (ExecutableElement executableElement : elements) {
60       if (executableElement.getSimpleName().toString().equals(name)) {
61         return executableElement;
62       }
63     }
64     throw new IllegalArgumentException(name + " not found in " + elements);
65   }
66 
nullAnnotationsAddition()67   @Test public void nullAnnotationsAddition() {
68     try {
69       MethodSpec.methodBuilder("doSomething").addAnnotations(null);
70       fail();
71     } catch (IllegalArgumentException expected) {
72       assertThat(expected).hasMessageThat().isEqualTo("annotationSpecs == null");
73     }
74   }
75 
nullTypeVariablesAddition()76   @Test public void nullTypeVariablesAddition() {
77     try {
78       MethodSpec.methodBuilder("doSomething").addTypeVariables(null);
79       fail();
80     } catch (IllegalArgumentException expected) {
81       assertThat(expected).hasMessageThat().isEqualTo("typeVariables == null");
82     }
83   }
84 
nullParametersAddition()85   @Test public void nullParametersAddition() {
86     try {
87       MethodSpec.methodBuilder("doSomething").addParameters(null);
88       fail();
89     } catch (IllegalArgumentException expected) {
90       assertThat(expected).hasMessageThat().isEqualTo("parameterSpecs == null");
91     }
92   }
93 
nullExceptionsAddition()94   @Test public void nullExceptionsAddition() {
95     try {
96       MethodSpec.methodBuilder("doSomething").addExceptions(null);
97       fail();
98     } catch (IllegalArgumentException expected) {
99       assertThat(expected).hasMessageThat().isEqualTo("exceptions == null");
100     }
101   }
102 
103   @Target(ElementType.PARAMETER)
104   @interface Nullable {
105   }
106 
107   abstract static class Everything {
everything( @ullable String thing, List<? extends T> things)108     @Deprecated protected abstract <T extends Runnable & Closeable> Runnable everything(
109         @Nullable String thing, List<? extends T> things) throws IOException, SecurityException;
110   }
111 
112   abstract static class Generics {
run(R param)113     <T, R, V extends Throwable> T run(R param) throws V {
114       return null;
115     }
116   }
117 
118   abstract static class HasAnnotation {
toString()119     @Override public abstract String toString();
120   }
121 
122   interface Throws<R extends RuntimeException> {
fail()123     void fail() throws R;
124   }
125 
126   interface ExtendsOthers extends Callable<Integer>, Comparable<ExtendsOthers>,
127       Throws<IllegalStateException> {
128   }
129 
130   interface ExtendsIterableWithDefaultMethods extends Iterable<Object> {
131   }
132 
133   final class FinalClass {
method()134     void method() {
135     }
136   }
137 
138   abstract static class InvalidOverrideMethods {
finalMethod()139     final void finalMethod() {
140     }
141 
privateMethod()142     private void privateMethod() {
143     }
144 
staticMethod()145     static void staticMethod() {
146     }
147   }
148 
overrideEverything()149   @Test public void overrideEverything() {
150     TypeElement classElement = getElement(Everything.class);
151     ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
152     MethodSpec method = MethodSpec.overriding(methodElement).build();
153     assertThat(method.toString()).isEqualTo(""
154         + "@java.lang.Override\n"
155         + "protected <T extends java.lang.Runnable & java.io.Closeable> java.lang.Runnable "
156         + "everything(\n"
157         + "    java.lang.String arg0, java.util.List<? extends T> arg1) throws java.io.IOException,\n"
158         + "    java.lang.SecurityException {\n"
159         + "}\n");
160   }
161 
overrideGenerics()162   @Test public void overrideGenerics() {
163     TypeElement classElement = getElement(Generics.class);
164     ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
165     MethodSpec method = MethodSpec.overriding(methodElement)
166         .addStatement("return null")
167         .build();
168     assertThat(method.toString()).isEqualTo(""
169         + "@java.lang.Override\n"
170         + "<T, R, V extends java.lang.Throwable> T run(R param) throws V {\n"
171         + "  return null;\n"
172         + "}\n");
173   }
174 
overrideDoesNotCopyOverrideAnnotation()175   @Test public void overrideDoesNotCopyOverrideAnnotation() {
176     TypeElement classElement = getElement(HasAnnotation.class);
177     ExecutableElement exec = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
178     MethodSpec method = MethodSpec.overriding(exec).build();
179     assertThat(method.toString()).isEqualTo(""
180         + "@java.lang.Override\n"
181         + "public java.lang.String toString() {\n"
182         + "}\n");
183   }
184 
overrideDoesNotCopyDefaultModifier()185   @Test public void overrideDoesNotCopyDefaultModifier() {
186     TypeElement classElement = getElement(ExtendsIterableWithDefaultMethods.class);
187     DeclaredType classType = (DeclaredType) classElement.asType();
188     List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
189     ExecutableElement exec = findFirst(methods, "spliterator");
190     MethodSpec method = MethodSpec.overriding(exec, classType, types).build();
191     assertThat(method.toString()).isEqualTo(""
192         + "@java.lang.Override\n"
193         + "public java.util.Spliterator<java.lang.Object> spliterator() {\n"
194         + "}\n");
195   }
196 
overrideExtendsOthersWorksWithActualTypeParameters()197   @Test public void overrideExtendsOthersWorksWithActualTypeParameters() {
198     TypeElement classElement = getElement(ExtendsOthers.class);
199     DeclaredType classType = (DeclaredType) classElement.asType();
200     List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
201     ExecutableElement exec = findFirst(methods, "call");
202     MethodSpec method = MethodSpec.overriding(exec, classType, types).build();
203     assertThat(method.toString()).isEqualTo(""
204         + "@java.lang.Override\n"
205         + "public java.lang.Integer call() throws java.lang.Exception {\n"
206         + "}\n");
207     exec = findFirst(methods, "compareTo");
208     method = MethodSpec.overriding(exec, classType, types).build();
209     assertThat(method.toString()).isEqualTo(""
210         + "@java.lang.Override\n"
211         + "public int compareTo(" + ExtendsOthers.class.getCanonicalName() + " arg0) {\n"
212         + "}\n");
213     exec = findFirst(methods, "fail");
214     method = MethodSpec.overriding(exec, classType, types).build();
215     assertThat(method.toString()).isEqualTo(""
216         + "@java.lang.Override\n"
217         + "public void fail() throws java.lang.IllegalStateException {\n"
218         + "}\n");
219   }
220 
overrideFinalClassMethod()221   @Test public void overrideFinalClassMethod() {
222     TypeElement classElement = getElement(FinalClass.class);
223     List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
224     try {
225       MethodSpec.overriding(findFirst(methods, "method"));
226       fail();
227     } catch (IllegalArgumentException expected) {
228       assertThat(expected).hasMessageThat().isEqualTo(
229           "Cannot override method on final class com.squareup.javapoet.MethodSpecTest.FinalClass");
230     }
231   }
232 
overrideInvalidModifiers()233   @Test public void overrideInvalidModifiers() {
234     TypeElement classElement = getElement(InvalidOverrideMethods.class);
235     List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement));
236     try {
237       MethodSpec.overriding(findFirst(methods, "finalMethod"));
238       fail();
239     } catch (IllegalArgumentException expected) {
240       assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [final]");
241     }
242     try {
243       MethodSpec.overriding(findFirst(methods, "privateMethod"));
244       fail();
245     } catch (IllegalArgumentException expected) {
246       assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [private]");
247     }
248     try {
249       MethodSpec.overriding(findFirst(methods, "staticMethod"));
250       fail();
251     } catch (IllegalArgumentException expected) {
252       assertThat(expected).hasMessageThat().isEqualTo("cannot override method with modifiers: [static]");
253     }
254   }
255 
equalsAndHashCode()256   @Test public void equalsAndHashCode() {
257     MethodSpec a = MethodSpec.constructorBuilder().build();
258     MethodSpec b = MethodSpec.constructorBuilder().build();
259     assertThat(a.equals(b)).isTrue();
260     assertThat(a.hashCode()).isEqualTo(b.hashCode());
261     a = MethodSpec.methodBuilder("taco").build();
262     b = MethodSpec.methodBuilder("taco").build();
263     assertThat(a.equals(b)).isTrue();
264     assertThat(a.hashCode()).isEqualTo(b.hashCode());
265     TypeElement classElement = getElement(Everything.class);
266     ExecutableElement methodElement = getOnlyElement(methodsIn(classElement.getEnclosedElements()));
267     a = MethodSpec.overriding(methodElement).build();
268     b = MethodSpec.overriding(methodElement).build();
269     assertThat(a.equals(b)).isTrue();
270     assertThat(a.hashCode()).isEqualTo(b.hashCode());
271   }
272 
duplicateExceptionsIgnored()273   @Test public void duplicateExceptionsIgnored() {
274     ClassName ioException = ClassName.get(IOException.class);
275     ClassName timeoutException = ClassName.get(TimeoutException.class);
276     MethodSpec methodSpec = MethodSpec.methodBuilder("duplicateExceptions")
277       .addException(ioException)
278       .addException(timeoutException)
279       .addException(timeoutException)
280       .addException(ioException)
281       .build();
282     assertThat(methodSpec.exceptions).isEqualTo(Arrays.asList(ioException, timeoutException));
283     assertThat(methodSpec.toBuilder().addException(ioException).build().exceptions)
284       .isEqualTo(Arrays.asList(ioException, timeoutException));
285   }
286 
nullIsNotAValidMethodName()287   @Test public void nullIsNotAValidMethodName() {
288     try {
289       MethodSpec.methodBuilder(null);
290       fail("NullPointerException expected");
291     } catch (NullPointerException e) {
292       assertThat(e.getMessage()).isEqualTo("name == null");
293     }
294   }
295 
addModifiersVarargsShouldNotBeNull()296   @Test public void addModifiersVarargsShouldNotBeNull() {
297     try {
298       MethodSpec.methodBuilder("taco")
299               .addModifiers((Modifier[]) null);
300       fail("NullPointerException expected");
301     } catch (NullPointerException e) {
302       assertThat(e.getMessage()).isEqualTo("modifiers == null");
303     }
304   }
305 }
306