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