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 java.io.IOException; 19 import java.lang.reflect.Type; 20 import java.util.ArrayList; 21 import java.util.Collections; 22 import java.util.Iterator; 23 import java.util.LinkedHashSet; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.Set; 27 import javax.lang.model.SourceVersion; 28 import javax.lang.model.element.Element; 29 import javax.lang.model.element.ExecutableElement; 30 import javax.lang.model.element.Modifier; 31 import javax.lang.model.element.TypeParameterElement; 32 import javax.lang.model.type.DeclaredType; 33 import javax.lang.model.type.ExecutableType; 34 import javax.lang.model.type.TypeMirror; 35 import javax.lang.model.type.TypeVariable; 36 import javax.lang.model.util.Types; 37 38 import static com.squareup.javapoet.Util.checkArgument; 39 import static com.squareup.javapoet.Util.checkNotNull; 40 import static com.squareup.javapoet.Util.checkState; 41 42 /** A generated constructor or method declaration. */ 43 public final class MethodSpec { 44 static final String CONSTRUCTOR = "<init>"; 45 46 public final String name; 47 public final CodeBlock javadoc; 48 public final List<AnnotationSpec> annotations; 49 public final Set<Modifier> modifiers; 50 public final List<TypeVariableName> typeVariables; 51 public final TypeName returnType; 52 public final List<ParameterSpec> parameters; 53 public final boolean varargs; 54 public final List<TypeName> exceptions; 55 public final CodeBlock code; 56 public final CodeBlock defaultValue; 57 MethodSpec(Builder builder)58 private MethodSpec(Builder builder) { 59 CodeBlock code = builder.code.build(); 60 checkArgument(code.isEmpty() || !builder.modifiers.contains(Modifier.ABSTRACT), 61 "abstract method %s cannot have code", builder.name); 62 checkArgument(!builder.varargs || lastParameterIsArray(builder.parameters), 63 "last parameter of varargs method %s must be an array", builder.name); 64 65 this.name = checkNotNull(builder.name, "name == null"); 66 this.javadoc = builder.javadoc.build(); 67 this.annotations = Util.immutableList(builder.annotations); 68 this.modifiers = Util.immutableSet(builder.modifiers); 69 this.typeVariables = Util.immutableList(builder.typeVariables); 70 this.returnType = builder.returnType; 71 this.parameters = Util.immutableList(builder.parameters); 72 this.varargs = builder.varargs; 73 this.exceptions = Util.immutableList(builder.exceptions); 74 this.defaultValue = builder.defaultValue; 75 this.code = code; 76 } 77 lastParameterIsArray(List<ParameterSpec> parameters)78 private boolean lastParameterIsArray(List<ParameterSpec> parameters) { 79 return !parameters.isEmpty() 80 && TypeName.asArray((parameters.get(parameters.size() - 1).type)) != null; 81 } 82 emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers)83 void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers) 84 throws IOException { 85 codeWriter.emitJavadoc(javadocWithParameters()); 86 codeWriter.emitAnnotations(annotations, false); 87 codeWriter.emitModifiers(modifiers, implicitModifiers); 88 89 if (!typeVariables.isEmpty()) { 90 codeWriter.emitTypeVariables(typeVariables); 91 codeWriter.emit(" "); 92 } 93 94 if (isConstructor()) { 95 codeWriter.emit("$L($Z", enclosingName); 96 } else { 97 codeWriter.emit("$T $L($Z", returnType, name); 98 } 99 100 boolean firstParameter = true; 101 for (Iterator<ParameterSpec> i = parameters.iterator(); i.hasNext(); ) { 102 ParameterSpec parameter = i.next(); 103 if (!firstParameter) codeWriter.emit(",").emitWrappingSpace(); 104 parameter.emit(codeWriter, !i.hasNext() && varargs); 105 firstParameter = false; 106 } 107 108 codeWriter.emit(")"); 109 110 if (defaultValue != null && !defaultValue.isEmpty()) { 111 codeWriter.emit(" default "); 112 codeWriter.emit(defaultValue); 113 } 114 115 if (!exceptions.isEmpty()) { 116 codeWriter.emitWrappingSpace().emit("throws"); 117 boolean firstException = true; 118 for (TypeName exception : exceptions) { 119 if (!firstException) codeWriter.emit(","); 120 codeWriter.emitWrappingSpace().emit("$T", exception); 121 firstException = false; 122 } 123 } 124 125 if (hasModifier(Modifier.ABSTRACT)) { 126 codeWriter.emit(";\n"); 127 } else if (hasModifier(Modifier.NATIVE)) { 128 // Code is allowed to support stuff like GWT JSNI. 129 codeWriter.emit(code); 130 codeWriter.emit(";\n"); 131 } else { 132 codeWriter.emit(" {\n"); 133 134 codeWriter.indent(); 135 codeWriter.emit(code, true); 136 codeWriter.unindent(); 137 138 codeWriter.emit("}\n"); 139 } 140 codeWriter.popTypeVariables(typeVariables); 141 } 142 javadocWithParameters()143 private CodeBlock javadocWithParameters() { 144 CodeBlock.Builder builder = javadoc.toBuilder(); 145 boolean emitTagNewline = true; 146 for (ParameterSpec parameterSpec : parameters) { 147 if (!parameterSpec.javadoc.isEmpty()) { 148 // Emit a new line before @param section only if the method javadoc is present. 149 if (emitTagNewline && !javadoc.isEmpty()) builder.add("\n"); 150 emitTagNewline = false; 151 builder.add("@param $L $L", parameterSpec.name, parameterSpec.javadoc); 152 } 153 } 154 return builder.build(); 155 } 156 hasModifier(Modifier modifier)157 public boolean hasModifier(Modifier modifier) { 158 return modifiers.contains(modifier); 159 } 160 isConstructor()161 public boolean isConstructor() { 162 return name.equals(CONSTRUCTOR); 163 } 164 equals(Object o)165 @Override public boolean equals(Object o) { 166 if (this == o) return true; 167 if (o == null) return false; 168 if (getClass() != o.getClass()) return false; 169 return toString().equals(o.toString()); 170 } 171 hashCode()172 @Override public int hashCode() { 173 return toString().hashCode(); 174 } 175 toString()176 @Override public String toString() { 177 StringBuilder out = new StringBuilder(); 178 try { 179 CodeWriter codeWriter = new CodeWriter(out); 180 emit(codeWriter, "Constructor", Collections.emptySet()); 181 return out.toString(); 182 } catch (IOException e) { 183 throw new AssertionError(); 184 } 185 } 186 methodBuilder(String name)187 public static Builder methodBuilder(String name) { 188 return new Builder(name); 189 } 190 constructorBuilder()191 public static Builder constructorBuilder() { 192 return new Builder(CONSTRUCTOR); 193 } 194 195 /** 196 * Returns a new method spec builder that overrides {@code method}. 197 * 198 * <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and 199 * throws declarations. An {@link Override} annotation will be added. 200 * 201 * <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and 202 * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately. 203 */ overriding(ExecutableElement method)204 public static Builder overriding(ExecutableElement method) { 205 checkNotNull(method, "method == null"); 206 207 Element enclosingClass = method.getEnclosingElement(); 208 if (enclosingClass.getModifiers().contains(Modifier.FINAL)) { 209 throw new IllegalArgumentException("Cannot override method on final class " + enclosingClass); 210 } 211 212 Set<Modifier> modifiers = method.getModifiers(); 213 if (modifiers.contains(Modifier.PRIVATE) 214 || modifiers.contains(Modifier.FINAL) 215 || modifiers.contains(Modifier.STATIC)) { 216 throw new IllegalArgumentException("cannot override method with modifiers: " + modifiers); 217 } 218 219 String methodName = method.getSimpleName().toString(); 220 MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName); 221 222 methodBuilder.addAnnotation(Override.class); 223 224 modifiers = new LinkedHashSet<>(modifiers); 225 modifiers.remove(Modifier.ABSTRACT); 226 modifiers.remove(Modifier.DEFAULT); 227 methodBuilder.addModifiers(modifiers); 228 229 for (TypeParameterElement typeParameterElement : method.getTypeParameters()) { 230 TypeVariable var = (TypeVariable) typeParameterElement.asType(); 231 methodBuilder.addTypeVariable(TypeVariableName.get(var)); 232 } 233 234 methodBuilder.returns(TypeName.get(method.getReturnType())); 235 methodBuilder.addParameters(ParameterSpec.parametersOf(method)); 236 methodBuilder.varargs(method.isVarArgs()); 237 238 for (TypeMirror thrownType : method.getThrownTypes()) { 239 methodBuilder.addException(TypeName.get(thrownType)); 240 } 241 242 return methodBuilder; 243 } 244 245 /** 246 * Returns a new method spec builder that overrides {@code method} as a member of {@code 247 * enclosing}. This will resolve type parameters: for example overriding {@link 248 * Comparable#compareTo} in a type that implements {@code Comparable<Movie>}, the {@code T} 249 * parameter will be resolved to {@code Movie}. 250 * 251 * <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and 252 * throws declarations. An {@link Override} annotation will be added. 253 * 254 * <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and 255 * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately. 256 */ overriding( ExecutableElement method, DeclaredType enclosing, Types types)257 public static Builder overriding( 258 ExecutableElement method, DeclaredType enclosing, Types types) { 259 ExecutableType executableType = (ExecutableType) types.asMemberOf(enclosing, method); 260 List<? extends TypeMirror> resolvedParameterTypes = executableType.getParameterTypes(); 261 List<? extends TypeMirror> resolvedThrownTypes = executableType.getThrownTypes(); 262 TypeMirror resolvedReturnType = executableType.getReturnType(); 263 264 Builder builder = overriding(method); 265 builder.returns(TypeName.get(resolvedReturnType)); 266 for (int i = 0, size = builder.parameters.size(); i < size; i++) { 267 ParameterSpec parameter = builder.parameters.get(i); 268 TypeName type = TypeName.get(resolvedParameterTypes.get(i)); 269 builder.parameters.set(i, parameter.toBuilder(type, parameter.name).build()); 270 } 271 builder.exceptions.clear(); 272 for (int i = 0, size = resolvedThrownTypes.size(); i < size; i++) { 273 builder.addException(TypeName.get(resolvedThrownTypes.get(i))); 274 } 275 276 return builder; 277 } 278 toBuilder()279 public Builder toBuilder() { 280 Builder builder = new Builder(name); 281 builder.javadoc.add(javadoc); 282 builder.annotations.addAll(annotations); 283 builder.modifiers.addAll(modifiers); 284 builder.typeVariables.addAll(typeVariables); 285 builder.returnType = returnType; 286 builder.parameters.addAll(parameters); 287 builder.exceptions.addAll(exceptions); 288 builder.code.add(code); 289 builder.varargs = varargs; 290 builder.defaultValue = defaultValue; 291 return builder; 292 } 293 294 public static final class Builder { 295 private String name; 296 297 private final CodeBlock.Builder javadoc = CodeBlock.builder(); 298 private TypeName returnType; 299 private final Set<TypeName> exceptions = new LinkedHashSet<>(); 300 private final CodeBlock.Builder code = CodeBlock.builder(); 301 private boolean varargs; 302 private CodeBlock defaultValue; 303 304 public final List<TypeVariableName> typeVariables = new ArrayList<>(); 305 public final List<AnnotationSpec> annotations = new ArrayList<>(); 306 public final List<Modifier> modifiers = new ArrayList<>(); 307 public final List<ParameterSpec> parameters = new ArrayList<>(); 308 Builder(String name)309 private Builder(String name) { 310 setName(name); 311 } 312 setName(String name)313 public Builder setName(String name) { 314 checkNotNull(name, "name == null"); 315 checkArgument(name.equals(CONSTRUCTOR) || SourceVersion.isName(name), 316 "not a valid name: %s", name); 317 this.name = name; 318 this.returnType = name.equals(CONSTRUCTOR) ? null : TypeName.VOID; 319 return this; 320 } 321 addJavadoc(String format, Object... args)322 public Builder addJavadoc(String format, Object... args) { 323 javadoc.add(format, args); 324 return this; 325 } 326 addJavadoc(CodeBlock block)327 public Builder addJavadoc(CodeBlock block) { 328 javadoc.add(block); 329 return this; 330 } 331 addAnnotations(Iterable<AnnotationSpec> annotationSpecs)332 public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) { 333 checkArgument(annotationSpecs != null, "annotationSpecs == null"); 334 for (AnnotationSpec annotationSpec : annotationSpecs) { 335 this.annotations.add(annotationSpec); 336 } 337 return this; 338 } 339 addAnnotation(AnnotationSpec annotationSpec)340 public Builder addAnnotation(AnnotationSpec annotationSpec) { 341 this.annotations.add(annotationSpec); 342 return this; 343 } 344 addAnnotation(ClassName annotation)345 public Builder addAnnotation(ClassName annotation) { 346 this.annotations.add(AnnotationSpec.builder(annotation).build()); 347 return this; 348 } 349 addAnnotation(Class<?> annotation)350 public Builder addAnnotation(Class<?> annotation) { 351 return addAnnotation(ClassName.get(annotation)); 352 } 353 addModifiers(Modifier... modifiers)354 public Builder addModifiers(Modifier... modifiers) { 355 checkNotNull(modifiers, "modifiers == null"); 356 Collections.addAll(this.modifiers, modifiers); 357 return this; 358 } 359 addModifiers(Iterable<Modifier> modifiers)360 public Builder addModifiers(Iterable<Modifier> modifiers) { 361 checkNotNull(modifiers, "modifiers == null"); 362 for (Modifier modifier : modifiers) { 363 this.modifiers.add(modifier); 364 } 365 return this; 366 } 367 addTypeVariables(Iterable<TypeVariableName> typeVariables)368 public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) { 369 checkArgument(typeVariables != null, "typeVariables == null"); 370 for (TypeVariableName typeVariable : typeVariables) { 371 this.typeVariables.add(typeVariable); 372 } 373 return this; 374 } 375 addTypeVariable(TypeVariableName typeVariable)376 public Builder addTypeVariable(TypeVariableName typeVariable) { 377 typeVariables.add(typeVariable); 378 return this; 379 } 380 returns(TypeName returnType)381 public Builder returns(TypeName returnType) { 382 checkState(!name.equals(CONSTRUCTOR), "constructor cannot have return type."); 383 this.returnType = returnType; 384 return this; 385 } 386 returns(Type returnType)387 public Builder returns(Type returnType) { 388 return returns(TypeName.get(returnType)); 389 } 390 addParameters(Iterable<ParameterSpec> parameterSpecs)391 public Builder addParameters(Iterable<ParameterSpec> parameterSpecs) { 392 checkArgument(parameterSpecs != null, "parameterSpecs == null"); 393 for (ParameterSpec parameterSpec : parameterSpecs) { 394 this.parameters.add(parameterSpec); 395 } 396 return this; 397 } 398 addParameter(ParameterSpec parameterSpec)399 public Builder addParameter(ParameterSpec parameterSpec) { 400 this.parameters.add(parameterSpec); 401 return this; 402 } 403 addParameter(TypeName type, String name, Modifier... modifiers)404 public Builder addParameter(TypeName type, String name, Modifier... modifiers) { 405 return addParameter(ParameterSpec.builder(type, name, modifiers).build()); 406 } 407 addParameter(Type type, String name, Modifier... modifiers)408 public Builder addParameter(Type type, String name, Modifier... modifiers) { 409 return addParameter(TypeName.get(type), name, modifiers); 410 } 411 varargs()412 public Builder varargs() { 413 return varargs(true); 414 } 415 varargs(boolean varargs)416 public Builder varargs(boolean varargs) { 417 this.varargs = varargs; 418 return this; 419 } 420 addExceptions(Iterable<? extends TypeName> exceptions)421 public Builder addExceptions(Iterable<? extends TypeName> exceptions) { 422 checkArgument(exceptions != null, "exceptions == null"); 423 for (TypeName exception : exceptions) { 424 this.exceptions.add(exception); 425 } 426 return this; 427 } 428 addException(TypeName exception)429 public Builder addException(TypeName exception) { 430 this.exceptions.add(exception); 431 return this; 432 } 433 addException(Type exception)434 public Builder addException(Type exception) { 435 return addException(TypeName.get(exception)); 436 } 437 addCode(String format, Object... args)438 public Builder addCode(String format, Object... args) { 439 code.add(format, args); 440 return this; 441 } 442 addNamedCode(String format, Map<String, ?> args)443 public Builder addNamedCode(String format, Map<String, ?> args) { 444 code.addNamed(format, args); 445 return this; 446 } 447 addCode(CodeBlock codeBlock)448 public Builder addCode(CodeBlock codeBlock) { 449 code.add(codeBlock); 450 return this; 451 } 452 addComment(String format, Object... args)453 public Builder addComment(String format, Object... args) { 454 code.add("// " + format + "\n", args); 455 return this; 456 } 457 defaultValue(String format, Object... args)458 public Builder defaultValue(String format, Object... args) { 459 return defaultValue(CodeBlock.of(format, args)); 460 } 461 defaultValue(CodeBlock codeBlock)462 public Builder defaultValue(CodeBlock codeBlock) { 463 checkState(this.defaultValue == null, "defaultValue was already set"); 464 this.defaultValue = checkNotNull(codeBlock, "codeBlock == null"); 465 return this; 466 } 467 468 /** 469 * @param controlFlow the control flow construct and its code, such as "if (foo == 5)". 470 * Shouldn't contain braces or newline characters. 471 */ beginControlFlow(String controlFlow, Object... args)472 public Builder beginControlFlow(String controlFlow, Object... args) { 473 code.beginControlFlow(controlFlow, args); 474 return this; 475 } 476 477 /** 478 * @param codeBlock the control flow construct and its code, such as "if (foo == 5)". 479 * Shouldn't contain braces or newline characters. 480 */ beginControlFlow(CodeBlock codeBlock)481 public Builder beginControlFlow(CodeBlock codeBlock) { 482 return beginControlFlow("$L", codeBlock); 483 } 484 485 /** 486 * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)". 487 * Shouldn't contain braces or newline characters. 488 */ nextControlFlow(String controlFlow, Object... args)489 public Builder nextControlFlow(String controlFlow, Object... args) { 490 code.nextControlFlow(controlFlow, args); 491 return this; 492 } 493 494 /** 495 * @param codeBlock the control flow construct and its code, such as "else if (foo == 10)". 496 * Shouldn't contain braces or newline characters. 497 */ nextControlFlow(CodeBlock codeBlock)498 public Builder nextControlFlow(CodeBlock codeBlock) { 499 return nextControlFlow("$L", codeBlock); 500 } 501 endControlFlow()502 public Builder endControlFlow() { 503 code.endControlFlow(); 504 return this; 505 } 506 507 /** 508 * @param controlFlow the optional control flow construct and its code, such as 509 * "while(foo == 20)". Only used for "do/while" control flows. 510 */ endControlFlow(String controlFlow, Object... args)511 public Builder endControlFlow(String controlFlow, Object... args) { 512 code.endControlFlow(controlFlow, args); 513 return this; 514 } 515 516 /** 517 * @param codeBlock the optional control flow construct and its code, such as 518 * "while(foo == 20)". Only used for "do/while" control flows. 519 */ endControlFlow(CodeBlock codeBlock)520 public Builder endControlFlow(CodeBlock codeBlock) { 521 return endControlFlow("$L", codeBlock); 522 } 523 addStatement(String format, Object... args)524 public Builder addStatement(String format, Object... args) { 525 code.addStatement(format, args); 526 return this; 527 } 528 addStatement(CodeBlock codeBlock)529 public Builder addStatement(CodeBlock codeBlock) { 530 code.addStatement(codeBlock); 531 return this; 532 } 533 build()534 public MethodSpec build() { 535 return new MethodSpec(this); 536 } 537 } 538 } 539