• 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 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