All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.javapoet.MethodSpec Maven / Gradle / Ivy

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.javapoet;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Types;

import static org.springframework.javapoet.Util.checkArgument;
import static org.springframework.javapoet.Util.checkNotNull;
import static org.springframework.javapoet.Util.checkState;

/** A generated constructor or method declaration. */
public final class MethodSpec {
  static final String CONSTRUCTOR = "";

  public final String name;
  public final CodeBlock javadoc;
  public final List annotations;
  public final Set modifiers;
  public final List typeVariables;
  public final TypeName returnType;
  public final List parameters;
  public final boolean varargs;
  public final List exceptions;
  public final CodeBlock code;
  public final CodeBlock defaultValue;

  private MethodSpec(Builder builder) {
    CodeBlock code = builder.code.build();
    checkArgument(code.isEmpty() || !builder.modifiers.contains(Modifier.ABSTRACT),
        "abstract method %s cannot have code", builder.name);
    checkArgument(!builder.varargs || lastParameterIsArray(builder.parameters),
        "last parameter of varargs method %s must be an array", builder.name);

    this.name = checkNotNull(builder.name, "name == null");
    this.javadoc = builder.javadoc.build();
    this.annotations = Util.immutableList(builder.annotations);
    this.modifiers = Util.immutableSet(builder.modifiers);
    this.typeVariables = Util.immutableList(builder.typeVariables);
    this.returnType = builder.returnType;
    this.parameters = Util.immutableList(builder.parameters);
    this.varargs = builder.varargs;
    this.exceptions = Util.immutableList(builder.exceptions);
    this.defaultValue = builder.defaultValue;
    this.code = code;
  }

  private boolean lastParameterIsArray(List parameters) {
    return !parameters.isEmpty()
        && TypeName.asArray((parameters.get(parameters.size() - 1).type)) != null;
  }

  void emit(CodeWriter codeWriter, String enclosingName, Set implicitModifiers)
      throws IOException {
    codeWriter.emitJavadoc(javadocWithParameters());
    codeWriter.emitAnnotations(annotations, false);
    codeWriter.emitModifiers(modifiers, implicitModifiers);

    if (!typeVariables.isEmpty()) {
      codeWriter.emitTypeVariables(typeVariables);
      codeWriter.emit(" ");
    }

    if (isConstructor()) {
      codeWriter.emit("$L($Z", enclosingName);
    } else {
      codeWriter.emit("$T $L($Z", returnType, name);
    }

    boolean firstParameter = true;
    for (Iterator i = parameters.iterator(); i.hasNext(); ) {
      ParameterSpec parameter = i.next();
      if (!firstParameter) codeWriter.emit(",").emitWrappingSpace();
      parameter.emit(codeWriter, !i.hasNext() && varargs);
      firstParameter = false;
    }

    codeWriter.emit(")");

    if (defaultValue != null && !defaultValue.isEmpty()) {
      codeWriter.emit(" default ");
      codeWriter.emit(defaultValue);
    }

    if (!exceptions.isEmpty()) {
      codeWriter.emitWrappingSpace().emit("throws");
      boolean firstException = true;
      for (TypeName exception : exceptions) {
        if (!firstException) codeWriter.emit(",");
        codeWriter.emitWrappingSpace().emit("$T", exception);
        firstException = false;
      }
    }

    if (hasModifier(Modifier.ABSTRACT)) {
      codeWriter.emit(";\n");
    } else if (hasModifier(Modifier.NATIVE)) {
      // Code is allowed to support stuff like GWT JSNI.
      codeWriter.emit(code);
      codeWriter.emit(";\n");
    } else {
      codeWriter.emit(" {\n");

      codeWriter.indent();
      codeWriter.emit(code, true);
      codeWriter.unindent();

      codeWriter.emit("}\n");
    }
    codeWriter.popTypeVariables(typeVariables);
  }

  private CodeBlock javadocWithParameters() {
    CodeBlock.Builder builder = javadoc.toBuilder();
    boolean emitTagNewline = true;
    for (ParameterSpec parameterSpec : parameters) {
      if (!parameterSpec.javadoc.isEmpty()) {
        // Emit a new line before @param section only if the method javadoc is present.
        if (emitTagNewline && !javadoc.isEmpty()) builder.add("\n");
        emitTagNewline = false;
        builder.add("@param $L $L", parameterSpec.name, parameterSpec.javadoc);
      }
    }
    return builder.build();
  }

  public boolean hasModifier(Modifier modifier) {
    return modifiers.contains(modifier);
  }

  public boolean isConstructor() {
    return name.equals(CONSTRUCTOR);
  }

  @Override public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null) return false;
    if (getClass() != o.getClass()) return false;
    return toString().equals(o.toString());
  }

  @Override public int hashCode() {
    return toString().hashCode();
  }

  @Override public String toString() {
    StringBuilder out = new StringBuilder();
    try {
      CodeWriter codeWriter = new CodeWriter(out);
      emit(codeWriter, "Constructor", Collections.emptySet());
      return out.toString();
    } catch (IOException e) {
      throw new AssertionError();
    }
  }

  public static Builder methodBuilder(String name) {
    return new Builder(name);
  }

  public static Builder constructorBuilder() {
    return new Builder(CONSTRUCTOR);
  }

  /**
   * Returns a new method spec builder that overrides {@code method}.
   *
   * 

This will copy its visibility modifiers, type parameters, return type, name, parameters, and * throws declarations. An {@link Override} annotation will be added. * *

Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately. */ public static Builder overriding(ExecutableElement method) { checkNotNull(method, "method == null"); Element enclosingClass = method.getEnclosingElement(); if (enclosingClass.getModifiers().contains(Modifier.FINAL)) { throw new IllegalArgumentException("Cannot override method on final class " + enclosingClass); } Set modifiers = method.getModifiers(); if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.FINAL) || modifiers.contains(Modifier.STATIC)) { throw new IllegalArgumentException("cannot override method with modifiers: " + modifiers); } String methodName = method.getSimpleName().toString(); MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName); methodBuilder.addAnnotation(Override.class); modifiers = new LinkedHashSet<>(modifiers); modifiers.remove(Modifier.ABSTRACT); modifiers.remove(Modifier.DEFAULT); methodBuilder.addModifiers(modifiers); for (TypeParameterElement typeParameterElement : method.getTypeParameters()) { TypeVariable var = (TypeVariable) typeParameterElement.asType(); methodBuilder.addTypeVariable(TypeVariableName.get(var)); } methodBuilder.returns(TypeName.get(method.getReturnType())); methodBuilder.addParameters(ParameterSpec.parametersOf(method)); methodBuilder.varargs(method.isVarArgs()); for (TypeMirror thrownType : method.getThrownTypes()) { methodBuilder.addException(TypeName.get(thrownType)); } return methodBuilder; } /** * Returns a new method spec builder that overrides {@code method} as a member of {@code * enclosing}. This will resolve type parameters: for example overriding {@link * Comparable#compareTo} in a type that implements {@code Comparable}, the {@code T} * parameter will be resolved to {@code Movie}. * *

This will copy its visibility modifiers, type parameters, return type, name, parameters, and * throws declarations. An {@link Override} annotation will be added. * *

Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and * parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately. */ public static Builder overriding( ExecutableElement method, DeclaredType enclosing, Types types) { ExecutableType executableType = (ExecutableType) types.asMemberOf(enclosing, method); List resolvedParameterTypes = executableType.getParameterTypes(); List resolvedThrownTypes = executableType.getThrownTypes(); TypeMirror resolvedReturnType = executableType.getReturnType(); Builder builder = overriding(method); builder.returns(TypeName.get(resolvedReturnType)); for (int i = 0, size = builder.parameters.size(); i < size; i++) { ParameterSpec parameter = builder.parameters.get(i); TypeName type = TypeName.get(resolvedParameterTypes.get(i)); builder.parameters.set(i, parameter.toBuilder(type, parameter.name).build()); } builder.exceptions.clear(); for (int i = 0, size = resolvedThrownTypes.size(); i < size; i++) { builder.addException(TypeName.get(resolvedThrownTypes.get(i))); } return builder; } public Builder toBuilder() { Builder builder = new Builder(name); builder.javadoc.add(javadoc); builder.annotations.addAll(annotations); builder.modifiers.addAll(modifiers); builder.typeVariables.addAll(typeVariables); builder.returnType = returnType; builder.parameters.addAll(parameters); builder.exceptions.addAll(exceptions); builder.code.add(code); builder.varargs = varargs; builder.defaultValue = defaultValue; return builder; } public static final class Builder { private String name; private final CodeBlock.Builder javadoc = CodeBlock.builder(); private TypeName returnType; private final Set exceptions = new LinkedHashSet<>(); private final CodeBlock.Builder code = CodeBlock.builder(); private boolean varargs; private CodeBlock defaultValue; public final List typeVariables = new ArrayList<>(); public final List annotations = new ArrayList<>(); public final List modifiers = new ArrayList<>(); public final List parameters = new ArrayList<>(); private Builder(String name) { setName(name); } public Builder setName(String name) { checkNotNull(name, "name == null"); checkArgument(name.equals(CONSTRUCTOR) || SourceVersion.isName(name), "not a valid name: %s", name); this.name = name; this.returnType = name.equals(CONSTRUCTOR) ? null : TypeName.VOID; return this; } public Builder addJavadoc(String format, Object... args) { javadoc.add(format, args); return this; } public Builder addJavadoc(CodeBlock block) { javadoc.add(block); return this; } public Builder addAnnotations(Iterable annotationSpecs) { checkArgument(annotationSpecs != null, "annotationSpecs == null"); for (AnnotationSpec annotationSpec : annotationSpecs) { this.annotations.add(annotationSpec); } return this; } public Builder addAnnotation(AnnotationSpec annotationSpec) { this.annotations.add(annotationSpec); return this; } public Builder addAnnotation(ClassName annotation) { this.annotations.add(AnnotationSpec.builder(annotation).build()); return this; } public Builder addAnnotation(Class annotation) { return addAnnotation(ClassName.get(annotation)); } public Builder addModifiers(Modifier... modifiers) { checkNotNull(modifiers, "modifiers == null"); Collections.addAll(this.modifiers, modifiers); return this; } public Builder addModifiers(Iterable modifiers) { checkNotNull(modifiers, "modifiers == null"); for (Modifier modifier : modifiers) { this.modifiers.add(modifier); } return this; } public Builder addTypeVariables(Iterable typeVariables) { checkArgument(typeVariables != null, "typeVariables == null"); for (TypeVariableName typeVariable : typeVariables) { this.typeVariables.add(typeVariable); } return this; } public Builder addTypeVariable(TypeVariableName typeVariable) { typeVariables.add(typeVariable); return this; } public Builder returns(TypeName returnType) { checkState(!name.equals(CONSTRUCTOR), "constructor cannot have return type."); this.returnType = returnType; return this; } public Builder returns(Type returnType) { return returns(TypeName.get(returnType)); } public Builder addParameters(Iterable parameterSpecs) { checkArgument(parameterSpecs != null, "parameterSpecs == null"); for (ParameterSpec parameterSpec : parameterSpecs) { this.parameters.add(parameterSpec); } return this; } public Builder addParameter(ParameterSpec parameterSpec) { this.parameters.add(parameterSpec); return this; } public Builder addParameter(TypeName type, String name, Modifier... modifiers) { return addParameter(ParameterSpec.builder(type, name, modifiers).build()); } public Builder addParameter(Type type, String name, Modifier... modifiers) { return addParameter(TypeName.get(type), name, modifiers); } public Builder varargs() { return varargs(true); } public Builder varargs(boolean varargs) { this.varargs = varargs; return this; } public Builder addExceptions(Iterable exceptions) { checkArgument(exceptions != null, "exceptions == null"); for (TypeName exception : exceptions) { this.exceptions.add(exception); } return this; } public Builder addException(TypeName exception) { this.exceptions.add(exception); return this; } public Builder addException(Type exception) { return addException(TypeName.get(exception)); } public Builder addCode(String format, Object... args) { code.add(format, args); return this; } public Builder addNamedCode(String format, Map args) { code.addNamed(format, args); return this; } public Builder addCode(CodeBlock codeBlock) { code.add(codeBlock); return this; } public Builder addComment(String format, Object... args) { code.add("// " + format + "\n", args); return this; } public Builder defaultValue(String format, Object... args) { return defaultValue(CodeBlock.of(format, args)); } public Builder defaultValue(CodeBlock codeBlock) { checkState(this.defaultValue == null, "defaultValue was already set"); this.defaultValue = checkNotNull(codeBlock, "codeBlock == null"); return this; } /** * @param controlFlow the control flow construct and its code, such as "if (foo == 5)". * Shouldn't contain braces or newline characters. */ public Builder beginControlFlow(String controlFlow, Object... args) { code.beginControlFlow(controlFlow, args); return this; } /** * @param codeBlock the control flow construct and its code, such as "if (foo == 5)". * Shouldn't contain braces or newline characters. */ public Builder beginControlFlow(CodeBlock codeBlock) { return beginControlFlow("$L", codeBlock); } /** * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)". * Shouldn't contain braces or newline characters. */ public Builder nextControlFlow(String controlFlow, Object... args) { code.nextControlFlow(controlFlow, args); return this; } /** * @param codeBlock the control flow construct and its code, such as "else if (foo == 10)". * Shouldn't contain braces or newline characters. */ public Builder nextControlFlow(CodeBlock codeBlock) { return nextControlFlow("$L", codeBlock); } public Builder endControlFlow() { code.endControlFlow(); return this; } /** * @param controlFlow the optional control flow construct and its code, such as * "while(foo == 20)". Only used for "do/while" control flows. */ public Builder endControlFlow(String controlFlow, Object... args) { code.endControlFlow(controlFlow, args); return this; } /** * @param codeBlock the optional control flow construct and its code, such as * "while(foo == 20)". Only used for "do/while" control flows. */ public Builder endControlFlow(CodeBlock codeBlock) { return endControlFlow("$L", codeBlock); } public Builder addStatement(String format, Object... args) { code.addStatement(format, args); return this; } public Builder addStatement(CodeBlock codeBlock) { code.addStatement(codeBlock); return this; } public MethodSpec build() { return new MethodSpec(this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy