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