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(javadoc); 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); 136 codeWriter.unindent(); 137 138 codeWriter.emit("}\n"); 139 } 140 } 141 hasModifier(Modifier modifier)142 public boolean hasModifier(Modifier modifier) { 143 return modifiers.contains(modifier); 144 } 145 isConstructor()146 public boolean isConstructor() { 147 return name.equals(CONSTRUCTOR); 148 } 149 equals(Object o)150 @Override public boolean equals(Object o) { 151 if (this == o) return true; 152 if (o == null) return false; 153 if (getClass() != o.getClass()) return false; 154 return toString().equals(o.toString()); 155 } 156 hashCode()157 @Override public int hashCode() { 158 return toString().hashCode(); 159 } 160 toString()161 @Override public String toString() { 162 StringBuilder out = new StringBuilder(); 163 try { 164 CodeWriter codeWriter = new CodeWriter(out); 165 emit(codeWriter, "Constructor", Collections.emptySet()); 166 return out.toString(); 167 } catch (IOException e) { 168 throw new AssertionError(); 169 } 170 } 171 methodBuilder(String name)172 public static Builder methodBuilder(String name) { 173 return new Builder(name); 174 } 175 constructorBuilder()176 public static Builder constructorBuilder() { 177 return new Builder(CONSTRUCTOR); 178 } 179 180 /** 181 * Returns a new method spec builder that overrides {@code method}. 182 * 183 * <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and 184 * throws declarations. An {@link Override} annotation will be added. 185 * 186 * <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and 187 * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately. 188 */ overriding(ExecutableElement method)189 public static Builder overriding(ExecutableElement method) { 190 checkNotNull(method, "method == null"); 191 192 Element enclosingClass = method.getEnclosingElement(); 193 if (enclosingClass.getModifiers().contains(Modifier.FINAL)) { 194 throw new IllegalArgumentException("Cannot override method on final class " + enclosingClass); 195 } 196 197 Set<Modifier> modifiers = method.getModifiers(); 198 if (modifiers.contains(Modifier.PRIVATE) 199 || modifiers.contains(Modifier.FINAL) 200 || modifiers.contains(Modifier.STATIC)) { 201 throw new IllegalArgumentException("cannot override method with modifiers: " + modifiers); 202 } 203 204 String methodName = method.getSimpleName().toString(); 205 MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName); 206 207 methodBuilder.addAnnotation(Override.class); 208 209 modifiers = new LinkedHashSet<>(modifiers); 210 modifiers.remove(Modifier.ABSTRACT); 211 modifiers.remove(Modifier.DEFAULT); 212 methodBuilder.addModifiers(modifiers); 213 214 for (TypeParameterElement typeParameterElement : method.getTypeParameters()) { 215 TypeVariable var = (TypeVariable) typeParameterElement.asType(); 216 methodBuilder.addTypeVariable(TypeVariableName.get(var)); 217 } 218 219 methodBuilder.returns(TypeName.get(method.getReturnType())); 220 methodBuilder.addParameters(ParameterSpec.parametersOf(method)); 221 methodBuilder.varargs(method.isVarArgs()); 222 223 for (TypeMirror thrownType : method.getThrownTypes()) { 224 methodBuilder.addException(TypeName.get(thrownType)); 225 } 226 227 return methodBuilder; 228 } 229 230 /** 231 * Returns a new method spec builder that overrides {@code method} as a member of {@code 232 * enclosing}. This will resolve type parameters: for example overriding {@link 233 * Comparable#compareTo} in a type that implements {@code Comparable<Movie>}, the {@code T} 234 * parameter will be resolved to {@code Movie}. 235 * 236 * <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and 237 * throws declarations. An {@link Override} annotation will be added. 238 * 239 * <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and 240 * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately. 241 */ overriding( ExecutableElement method, DeclaredType enclosing, Types types)242 public static Builder overriding( 243 ExecutableElement method, DeclaredType enclosing, Types types) { 244 ExecutableType executableType = (ExecutableType) types.asMemberOf(enclosing, method); 245 List<? extends TypeMirror> resolvedParameterTypes = executableType.getParameterTypes(); 246 List<? extends TypeMirror> resolvedThrownTypes = executableType.getThrownTypes(); 247 TypeMirror resolvedReturnType = executableType.getReturnType(); 248 249 Builder builder = overriding(method); 250 builder.returns(TypeName.get(resolvedReturnType)); 251 for (int i = 0, size = builder.parameters.size(); i < size; i++) { 252 ParameterSpec parameter = builder.parameters.get(i); 253 TypeName type = TypeName.get(resolvedParameterTypes.get(i)); 254 builder.parameters.set(i, parameter.toBuilder(type, parameter.name).build()); 255 } 256 builder.exceptions.clear(); 257 for (int i = 0, size = resolvedThrownTypes.size(); i < size; i++) { 258 builder.addException(TypeName.get(resolvedThrownTypes.get(i))); 259 } 260 261 return builder; 262 } 263 toBuilder()264 public Builder toBuilder() { 265 Builder builder = new Builder(name); 266 builder.javadoc.add(javadoc); 267 builder.annotations.addAll(annotations); 268 builder.modifiers.addAll(modifiers); 269 builder.typeVariables.addAll(typeVariables); 270 builder.returnType = returnType; 271 builder.parameters.addAll(parameters); 272 builder.exceptions.addAll(exceptions); 273 builder.code.add(code); 274 builder.varargs = varargs; 275 builder.defaultValue = defaultValue; 276 return builder; 277 } 278 279 public static final class Builder { 280 private final String name; 281 282 private final CodeBlock.Builder javadoc = CodeBlock.builder(); 283 private final List<AnnotationSpec> annotations = new ArrayList<>(); 284 private final List<Modifier> modifiers = new ArrayList<>(); 285 private List<TypeVariableName> typeVariables = new ArrayList<>(); 286 private TypeName returnType; 287 private final List<ParameterSpec> parameters = new ArrayList<>(); 288 private final Set<TypeName> exceptions = new LinkedHashSet<>(); 289 private final CodeBlock.Builder code = CodeBlock.builder(); 290 private boolean varargs; 291 private CodeBlock defaultValue; 292 Builder(String name)293 private Builder(String name) { 294 checkNotNull(name, "name == null"); 295 checkArgument(name.equals(CONSTRUCTOR) || SourceVersion.isName(name), 296 "not a valid name: %s", name); 297 this.name = name; 298 this.returnType = name.equals(CONSTRUCTOR) ? null : TypeName.VOID; 299 } 300 addJavadoc(String format, Object... args)301 public Builder addJavadoc(String format, Object... args) { 302 javadoc.add(format, args); 303 return this; 304 } 305 addJavadoc(CodeBlock block)306 public Builder addJavadoc(CodeBlock block) { 307 javadoc.add(block); 308 return this; 309 } 310 addAnnotations(Iterable<AnnotationSpec> annotationSpecs)311 public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) { 312 checkArgument(annotationSpecs != null, "annotationSpecs == null"); 313 for (AnnotationSpec annotationSpec : annotationSpecs) { 314 this.annotations.add(annotationSpec); 315 } 316 return this; 317 } 318 addAnnotation(AnnotationSpec annotationSpec)319 public Builder addAnnotation(AnnotationSpec annotationSpec) { 320 this.annotations.add(annotationSpec); 321 return this; 322 } 323 addAnnotation(ClassName annotation)324 public Builder addAnnotation(ClassName annotation) { 325 this.annotations.add(AnnotationSpec.builder(annotation).build()); 326 return this; 327 } 328 addAnnotation(Class<?> annotation)329 public Builder addAnnotation(Class<?> annotation) { 330 return addAnnotation(ClassName.get(annotation)); 331 } 332 addModifiers(Modifier... modifiers)333 public Builder addModifiers(Modifier... modifiers) { 334 checkNotNull(modifiers, "modifiers == null"); 335 Collections.addAll(this.modifiers, modifiers); 336 return this; 337 } 338 addModifiers(Iterable<Modifier> modifiers)339 public Builder addModifiers(Iterable<Modifier> modifiers) { 340 checkNotNull(modifiers, "modifiers == null"); 341 for (Modifier modifier : modifiers) { 342 this.modifiers.add(modifier); 343 } 344 return this; 345 } 346 addTypeVariables(Iterable<TypeVariableName> typeVariables)347 public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) { 348 checkArgument(typeVariables != null, "typeVariables == null"); 349 for (TypeVariableName typeVariable : typeVariables) { 350 this.typeVariables.add(typeVariable); 351 } 352 return this; 353 } 354 addTypeVariable(TypeVariableName typeVariable)355 public Builder addTypeVariable(TypeVariableName typeVariable) { 356 typeVariables.add(typeVariable); 357 return this; 358 } 359 returns(TypeName returnType)360 public Builder returns(TypeName returnType) { 361 checkState(!name.equals(CONSTRUCTOR), "constructor cannot have return type."); 362 this.returnType = returnType; 363 return this; 364 } 365 returns(Type returnType)366 public Builder returns(Type returnType) { 367 return returns(TypeName.get(returnType)); 368 } 369 addParameters(Iterable<ParameterSpec> parameterSpecs)370 public Builder addParameters(Iterable<ParameterSpec> parameterSpecs) { 371 checkArgument(parameterSpecs != null, "parameterSpecs == null"); 372 for (ParameterSpec parameterSpec : parameterSpecs) { 373 this.parameters.add(parameterSpec); 374 } 375 return this; 376 } 377 addParameter(ParameterSpec parameterSpec)378 public Builder addParameter(ParameterSpec parameterSpec) { 379 this.parameters.add(parameterSpec); 380 return this; 381 } 382 addParameter(TypeName type, String name, Modifier... modifiers)383 public Builder addParameter(TypeName type, String name, Modifier... modifiers) { 384 return addParameter(ParameterSpec.builder(type, name, modifiers).build()); 385 } 386 addParameter(Type type, String name, Modifier... modifiers)387 public Builder addParameter(Type type, String name, Modifier... modifiers) { 388 return addParameter(TypeName.get(type), name, modifiers); 389 } 390 varargs()391 public Builder varargs() { 392 return varargs(true); 393 } 394 varargs(boolean varargs)395 public Builder varargs(boolean varargs) { 396 this.varargs = varargs; 397 return this; 398 } 399 addExceptions(Iterable<? extends TypeName> exceptions)400 public Builder addExceptions(Iterable<? extends TypeName> exceptions) { 401 checkArgument(exceptions != null, "exceptions == null"); 402 for (TypeName exception : exceptions) { 403 this.exceptions.add(exception); 404 } 405 return this; 406 } 407 addException(TypeName exception)408 public Builder addException(TypeName exception) { 409 this.exceptions.add(exception); 410 return this; 411 } 412 addException(Type exception)413 public Builder addException(Type exception) { 414 return addException(TypeName.get(exception)); 415 } 416 addCode(String format, Object... args)417 public Builder addCode(String format, Object... args) { 418 code.add(format, args); 419 return this; 420 } 421 addNamedCode(String format, Map<String, ?> args)422 public Builder addNamedCode(String format, Map<String, ?> args) { 423 code.addNamed(format, args); 424 return this; 425 } 426 addCode(CodeBlock codeBlock)427 public Builder addCode(CodeBlock codeBlock) { 428 code.add(codeBlock); 429 return this; 430 } 431 addComment(String format, Object... args)432 public Builder addComment(String format, Object... args) { 433 code.add("// " + format + "\n", args); 434 return this; 435 } 436 defaultValue(String format, Object... args)437 public Builder defaultValue(String format, Object... args) { 438 return defaultValue(CodeBlock.of(format, args)); 439 } 440 defaultValue(CodeBlock codeBlock)441 public Builder defaultValue(CodeBlock codeBlock) { 442 checkState(this.defaultValue == null, "defaultValue was already set"); 443 this.defaultValue = checkNotNull(codeBlock, "codeBlock == null"); 444 return this; 445 } 446 447 /** 448 * @param controlFlow the control flow construct and its code, such as "if (foo == 5)". 449 * Shouldn't contain braces or newline characters. 450 */ beginControlFlow(String controlFlow, Object... args)451 public Builder beginControlFlow(String controlFlow, Object... args) { 452 code.beginControlFlow(controlFlow, args); 453 return this; 454 } 455 456 /** 457 * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)". 458 * Shouldn't contain braces or newline characters. 459 */ nextControlFlow(String controlFlow, Object... args)460 public Builder nextControlFlow(String controlFlow, Object... args) { 461 code.nextControlFlow(controlFlow, args); 462 return this; 463 } 464 endControlFlow()465 public Builder endControlFlow() { 466 code.endControlFlow(); 467 return this; 468 } 469 470 /** 471 * @param controlFlow the optional control flow construct and its code, such as 472 * "while(foo == 20)". Only used for "do/while" control flows. 473 */ endControlFlow(String controlFlow, Object... args)474 public Builder endControlFlow(String controlFlow, Object... args) { 475 code.endControlFlow(controlFlow, args); 476 return this; 477 } 478 addStatement(String format, Object... args)479 public Builder addStatement(String format, Object... args) { 480 code.addStatement(format, args); 481 return this; 482 } 483 addStatement(CodeBlock codeBlock)484 public Builder addStatement(CodeBlock codeBlock) { 485 code.addStatement(codeBlock); 486 return this; 487 } 488 build()489 public MethodSpec build() { 490 return new MethodSpec(this); 491 } 492 } 493 } 494