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

com.solidfire.jsvcgen.codegen.JavaCodeFormatter.scala Maven / Gradle / Ivy

The newest version!
/**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
  * to you 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 com.solidfire.jsvcgen.codegen

import com.solidfire.jsvcgen.codegen.Util._
import com.solidfire.jsvcgen.model._

class JavaCodeFormatter( options: CliConfig, serviceDefintion: ServiceDefinition ) {


  private val directTypeNames = options.typenameMapping.getOrElse(
    Map(
      "boolean" -> "Boolean",
      "integer" -> "Long",
      "number" -> "Double",
      "float" -> "Double",
      "string" -> "String",
      "hashtable" -> "java.util.Map",
      "UUID" -> "java.util.UUID"
    )
  )

  // Get all the types that are just aliases for other types. This is used in getTypeName because Java somehow still
  // does not have type aliases.
  protected val typeAliases: Map[String, TypeUse] =
  (for (typ <- serviceDefintion.types;
        alias <- typ.alias
        //; if !directTypeNames.contains(typ.name)
  ) yield (typ.name, alias)).toMap

  def getTypeName(src: String): String = {
    directTypeNames.get(src)
      .orElse( typeAliases.get( src ).map(getTypeName))//.map( ( alias: TypeUse ) => getTypeName( alias.typeName ) ) )
      .getOrElse(Util.camelCase(src, firstUpper = true))
  }

  def getTypeName(src: TypeDefinition): String = getTypeName(src.name)

  def getTypeName(src: TypeUse): String = src match {
    case TypeUse(name, false, false, None) => getTypeName(name)
    case TypeUse(name, false, true, None) => s"Optional<${getTypeName(name)}>"
    case TypeUse(name, true, false, None) => s"${getTypeName(name)}[]"
    case TypeUse(name, true, true, None) => s"Optional<${getTypeName(name)}[]>"
    case TypeUse(name, false, false, dictType) if name.toLowerCase == "dictionary" => s"java.util.Map"
    case TypeUse(name, false, true, dictType) if name.toLowerCase == "object" => s"Optional>"
    case TypeUse(name, false, false, dictType) if name.toLowerCase == "object" => s"java.util.Map"
    case _ => {
      println(src)
      throw new IllegalArgumentException(src.toString)
    }
  }

  /* def getTypeName( src: TypeUse ): String = src match {
   /ase TypeUse( name, false, isOptional, None ) => {
     if (isOptional)
       "Optional<" + getTypeName( name ) + ">"
     else
       getTypeName( name )
   }
   case TypeUse( name, true, isOptional, None ) => {
     if (isOptional)
       "Optional<" + getTypeName( name ) + "[]>"
     else
       getTypeName( name ) + "[]"
   }
   case TypeUse( name, false, false, dictType ) if name.toLowerCase == "dictionary" => s"java.util.Map"

   case TypeUse( name, false, isOptional, dictType ) if name.toLowerCase == "object" => {
     if(isOptional)
       s"Optional>"
     else
       s"java.util.Map"
   }
   case _ => {
     println(src)
     throw new IllegalArgumentException(src.toString)
   }
 } */

  def getTypeName(src: Option[ReturnInfo]): String = src match {
    case Some(info) => getTypeName(info.returnType)
    case None => "void"
  }

  def buildExtends(typeDefinition: TypeDefinition, options: CliConfig) = {
    typeDefinition.inherits.map {
      "extends " + _
    }
      .getOrElse(options.requestBase.map {
        "extends " + _
      }
        .getOrElse(""))
  }

  def addImplements(typeDefinition: TypeDefinition) = {
    typeDefinition.implements.map { i => {
      ", " + i.mkString(", ")
    }
    }.getOrElse("")
  }

  // GSON uses the field names as the JSON object keys
  def getFieldName(src: String): String = Util.camelCase(src, firstUpper = false)

  def getFieldName(src: Member): String = getFieldName(src.name)

  def getFieldName(src: Parameter): String = getFieldName(src.name)

  def getMemberAccessorName(src: String): String = "get" + Util.camelCase(src, firstUpper = true)

  def getMemberAccessorName(src: Member): String = getMemberAccessorName(src.name)

  def getMemberMutatorName(src: String): String = "set" + Util.camelCase(src, firstUpper = true)

  def getMemberMutatorName(src: Member): String = getMemberMutatorName(src.name)

  def getMethodName(src: String): String = Util.camelCase(src, firstUpper = false)

  def getMethodName(src: Method): String = getMethodName(src.name)

  def getConstructors(src: TypeDefinition): String = {
    val revisions = List(src.since.getOrElse("7.0")) ++: src.members.flatMap(member => member.since).distinct.sortWith((s1, s2) => s1.compareTo(s2) < 0)
    val revisionMembers: Map[String, List[Member]] = revisions.map((revision: String) => revision -> filterMembersByRevisions(revision, src.members)).toMap
    val constructors = revisionMembers.map({ case (k, v) => toConstructor(src, k, v) }).toList ++ List(toEmptyConstructor(src, revisions.min))

    Util.stringJoin(constructors, s"""\n""")
  }

  def toConstructor(src: TypeDefinition, revision: String, revSpecificMembers: List[Member]): String = {
    val typeName = getTypeName(src)
    val constructorParams = getParameterListForMembers(revSpecificMembers)
    val constructorFieldInitializersList = constructorFieldInitializers(src, revSpecificMembers)

    val sb = new StringBuilder

    sb ++= s"""    /**\n"""

    getClassDocumentation(src).map(s => sb ++= s"""     * $s\n""")

    if (src.members.nonEmpty) {
      src.members.filter(m => m.since.getOrElse("7.0").compareTo(revision) <= 0).map(m => sb ++= documentMemberAsParam(m))
    }
    sb ++= s"""     * @since $revision\n"""
    sb ++= s"""     **/\n"""

    sb ++= s"""    @Since(\"$revision\")\n    public $typeName($constructorParams) {\n$constructorFieldInitializersList\n    }\n"""

    sb.result
  }

  def toEmptyConstructor(src: TypeDefinition, minRevision: String): String = {
    val typeName = getTypeName(src)

    val sb = new StringBuilder

    // Add an empty constructor
    if (src.members.nonEmpty) {
      sb ++= s"""    \n"""
      sb ++= s"""    /**\n"""
      getClassDocumentation(src).map(s => sb ++= s"""     * $s\n""")

      sb ++= s"""     * Empty constructor to support serialization.\n"""
      sb ++= s"""     * @since $minRevision\n"""
      sb ++= s"""     **/\n"""

      sb ++= s"""    @Since(\"$minRevision\")\n    public $typeName() {}\n"""
    }

    sb.result
  }

  def documentMemberAsParam(member: Member): String = {
    val docFirstLine = member.documentation.getOrElse(new Documentation(List(""))).lines.head.replace(">>>", "".replace("<<<", ""))

    s"""     * @param ${Util.camelCase(member.name, false)}${if (member.typeUse.isOptional) " (optional) " else " [required] "}$docFirstLine\n"""
  }

  def constructorFieldInitializers(src: TypeDefinition, revSpecificMembers: List[Member]): String = {
    val fields = src.members.map(member => member -> getFieldName(member)).toMap

    val initializers = fields.map({
      case (k, v) => v -> (
        if (revSpecificMembers.contains(k)) {
          if (k.typeUse.isOptional && k.typeUse.isArray)
            s"""($v == null) ? Optional.<${getTypeName(k.typeUse.typeName)}[]>empty() : $v;"""
          else if (k.typeUse.isOptional && !k.typeUse.isArray)
            s"""($v == null) ? Optional.<${getTypeName(k.typeUse.typeName)}>empty() : $v;"""
          else
            s"""$v;"""
        } else if (k.typeUse.isOptional && k.typeUse.isArray) {
          s"""Optional.<${getTypeName(k.typeUse.typeName)}[]>empty();"""
        } else if (k.typeUse.isOptional && !k.typeUse.isArray) {
          s"""Optional.<${getTypeName(k.typeUse.typeName)}>empty();"""
        } else {
          "null;"
        })
    }).toList

    val initializedFields = initializers.map({ case (v, f) => s"""        this.$v = $f""" })

    Util.stringJoin(initializedFields, s"""\n""")
  }

  def filterMembersByRevisions(revision: String, members: List[Member]): List[Member] = {
    members.filter(p => p.since.isEmpty || p.since.get.compare(revision) <= 0)
  }

  def getParameterListForMembers(members: List[Member]): String =
    Util.stringJoin(for (member <- members) yield getTypeName(member.typeUse) + " " + getFieldName(member), ", ")

  def getParameterList(params: List[Parameter], isInterface: Boolean): String = {
    Util.stringJoin(
      params.map(param => (getTypeName(param.typeUse), getFieldName(param), param.since)).map(
        {
          case (typeName, fieldName, since) =>
            if (since.isDefined && !isInterface)
              s"""@Since(\"${since.get}\") $typeName $fieldName"""
            else
              s"""$typeName $fieldName"""
        })
      , ", "
    )
  }

  def getParameterUseList(params: List[Parameter]): String =
    Util.stringJoin(for (param <- params) yield getFieldName(param), ", ")

  def getClassDocumentation(typeDefinition: TypeDefinition): List[String] = {
    if (typeDefinition.documentation.isDefined)
      typeDefinition.documentation.get.lines.map(removeEscapeFlags)
    else
      getRequestResultDocumentationLines(typeDefinition).map(_.replace(">>>", "".replace("<<<", "")))
  }

  def getRequestResultDocumentationLines(typeDefinition: TypeDefinition): List[String] = {
    if (typeDefinition.name.endsWith("Request"))
      List( s"""The Request object for the "${typeDefinition.name.replaceFirst("Request", "")}" API Service call.""")
    else if (typeDefinition.name.endsWith("Result"))
      List( s"""The object returned by the "${typeDefinition.name.replaceFirst("Result", "")}" API Service call.""")
    else List("")
  }

  def getCodeDocumentation(lines: List[String], linePrefix: String, since: Option[String]): String = {
    val sb = new StringBuilder
    sb ++= s"""$linePrefix/**\n"""
    for (line <- lines.map(removeEscapeFlags)) {
      sb ++= s"""$linePrefix * $line\n"""
    }
    if (since.isDefined) {
      sb ++= s"""$linePrefix * @since ${since.get} \n"""
    }
    sb ++= s"""$linePrefix **/"""

    sb.result
  }

  def concatDocumentationLine(offset: Int, first: String, second: String) = {
    (first + s"""\n     *""" + (" " * (9 + offset)) + second).trim
  }


  val BlankLine = List(" ")

  def getCodeDocumentation(method: Method, serviceName: String, linePrefix: String, useRequestObject: Boolean): String = {
    val sb = new StringBuilder

    if (method.documentation.isDefined) {
      val lines =
        if (!useRequestObject) {
          List( s"""Convenience method for ${getMethodName(method)} """)
        } else {
          method.documentation.get.lines
        }

      val params: List[String] =
        if (!useRequestObject) {
          if (method.params.nonEmpty) {
            BlankLine ++
              method.params
                .filter(p => p.documentation.isDefined)
                .map(p => s"""@param ${p.name} ${p.documentation.get.lines.foldRight("")(concatDocumentationLine(p.name.length, _, _))}""")
          } else Nil
        } else {
          BlankLine ++ List( s"""@param request The request @see com.solidfire.element.api.${getTypeName(method.name)}Request """)
        }

      val returns: List[String] =
        BlankLine ++ List( s"""@return the response""") ++
          (
            if (!useRequestObject) {
              List( s"""@see com.solidfire.element.api.${getTypeName(serviceName)}#${getMethodName(method)}(${getTypeName(method.name)}Request) """)
            } else Nil
            )

      sb ++= getCodeDocumentation(lines ++ params ++ returns, linePrefix, method.since)
    }

    sb.result
  }

  def renderHashCode(typeDefinition: TypeDefinition): String = {
    val sb = new StringBuilder

    sb ++= s"""    @Override\n"""
    sb ++= s"""    public int hashCode() {\n"""
    if (typeDefinition.members.isEmpty) {
      sb ++= s"""        return this.getClass().hashCode();\n"""
    } else if (typeDefinition.members.length == 1) {
      val cast = if ("Object".equals(typeDefinition.members.head.typeUse.typeName)) "" else "(Object) "
      sb ++= s"""        return Objects.hash( $cast${typeDefinition.members.map((x: Member) => getFieldName(x)).mkString} );\n"""
    } else {
      sb ++= s"""        return Objects.hash( ${typeDefinition.members.map((x: Member) => getFieldName(x)).mkString(", ")} );\n"""
    }
    sb ++= s"""    }\n"""

    return sb.result
  }

  def getServiceMethod(method: Method, serviceName: String, isInterface: Boolean, useRequestObject: Boolean): String = {
    val sb = new StringBuilder

    if (isInterface && method.documentation.isDefined) {
      sb ++= s"""${getCodeDocumentation(method, serviceName, "    ", useRequestObject: Boolean)}\n"""
    }
    if (!isInterface) {
      sb ++= s"""    @Override\n"""
    }
    if (method.since.isDefined) {
      sb ++= s"""    @Since("${method.since.get}")\n"""
    }
    if (isInterface) {
      sb ++= s"""    """

    } else {
      sb ++= s"""    public """
    }
    sb ++= s"""${getTypeName(method.returnInfo)} ${getMethodName(method)}("""
    if (useRequestObject) {
      sb ++= s"""final ${getTypeName(method.name)}Request request"""
    } else {
      sb ++= s"""${getParameterList(method.params, isInterface)}"""
    }
    sb ++= s""")"""
    if (isInterface) {
      sb ++= s""";\n"""
    } else {
      sb ++= s""" {\n"""
      val hasValueAdaptor = method.returnInfo.get.adaptor.isDefined && method.returnInfo.get.adaptor.get.supports.contains("java")

      if (useRequestObject && hasValueAdaptor) {
        sb ++= s"""        return ${options.adaptorBase}.${Util.camelCase(method.returnInfo.get.adaptor.get.name, firstUpper = false)}(this, request);\n"""
      } else if (useRequestObject && !hasValueAdaptor) {
        sb ++= s"""        return super.sendRequest( "${method.name}", request, ${getTypeName(method.name)}Request.class, ${getTypeName(method.returnInfo).split("<")(0)}.class );\n"""
      } else if (!useRequestObject && hasValueAdaptor) {
        sb ++= s"""        final ${getTypeName(method.name)}Request request = new ${getTypeName(method.name)}Request( ${getParameterUseList(method.params)});\n"""
        sb ++= s"""\n"""
        sb ++= s"""        return ${options.adaptorBase}.${Util.camelCase(method.returnInfo.get.adaptor.get.name, firstUpper = false)}(this, request);\n"""
      } else if (!useRequestObject && !hasValueAdaptor) {
        sb ++= s"""        return this.${getMethodName(method)}( new ${getTypeName(method.name)}Request( ${getParameterUseList(method.params)}) );\n"""
      }
      sb ++= s"""    }\n"""
    }

    sb.result
  }

  def getRequestBuilder(typeDefinition: TypeDefinition): String = {
    val sb = new StringBuilder

    sb ++= s"""    public static Builder builder() {\n"""
    sb ++= s"""        return new Builder();\n"""
    sb ++= s"""    }\n"""
    sb ++= s"""\n"""

    sb ++= s"""    public final Builder asBuilder() {\n"""
    sb ++= s"""        return new Builder().buildFrom(this);\n"""
    sb ++= s"""    }\n"""
    sb ++= s"""\n"""

    sb ++= s"""    public static class Builder {\n"""
    for (member <- typeDefinition.members) {
      sb ++= s"""        private ${getTypeName(member.typeUse)} ${getFieldName(member)};\n"""
    }
    sb ++= s"""\n"""
    sb ++= s"""        private Builder() { }\n"""
    sb ++= s"""\n"""
    sb ++= s"""        public ${typeDefinition.name} build() {\n"""
    sb ++= s"""            return new ${typeDefinition.name} (\n"""
    sb ++= Util.stringJoin(typeDefinition.members.map(member => s"""                         this.${getFieldName(member)}"""), ",\n")
    sb ++= s"""            );\n"""
    sb ++= s"""        }\n\n"""

    sb ++= s"""        private ${typeDefinition.name}.Builder buildFrom(final ${typeDefinition.name} req) {\n"""
    sb ++= Util.stringJoin(typeDefinition.members.map(member => s"""            this.${getFieldName(member)} = req.${getFieldName(member)};"""), "\n")
    sb ++= s"""\n\n"""
    sb ++= s"""            return this;\n"""
    sb ++= s"""        }\n\n"""

    for (member <- typeDefinition.members) {
      if (member.typeUse.isOptional) {
        val optionalArrayBrackets = if (member.typeUse.isArray) "[]" else ""
        sb ++=
          s"""        public ${typeDefinition.name}.Builder optional${Util.camelCase(member.name, firstUpper = true)}(final ${
            getTypeName(member.typeUse.typeName)
          }$optionalArrayBrackets ${getFieldName(member)}) {\n"""
        sb ++=
          s"""            this.${getFieldName(member)} = (${getFieldName(member)} == null) ? Optional.<${
            getTypeName(member.typeUse.typeName)
          }$optionalArrayBrackets>empty() : Optional.of(${getFieldName(member)});\n"""
        sb ++= s"""            return this;\n"""
        sb ++= s"""        }\n\n"""
      } else {
        sb ++= s"""        public ${typeDefinition.name}.Builder ${member.name}(final ${getTypeName(member.typeUse)} ${getFieldName(member)}) {\n"""
        sb ++= s"""            this.${getFieldName(member)} = ${getFieldName(member)};\n"""
        sb ++= s"""            return this;\n"""
        sb ++= s"""        }\n\n"""
      }
    }

    sb ++= s"""    }\n"""

    sb.result
  }

  def getSetter(member: Member, accessModifier: String): String = {
    val sb = new StringBuilder
    if (member.typeUse.isOptional) {
      val optionalArrayBrackets = if (member.typeUse.isArray) "[]" else ""
      sb ++= s"""    ${accessModifier} void ${getMemberMutatorName(member)}(${getTypeName(member.typeUse.typeName)}$optionalArrayBrackets ${getFieldName(member)}) {\n"""
      sb ++=
        s"""        this.${getFieldName(member)} = (${getFieldName(member)} == null) ? Optional.<${
          getTypeName(member.typeUse.typeName)
        }$optionalArrayBrackets>empty() : Optional.of(${getFieldName(member)});\n"""
      sb ++= s"""    }\n\n"""
    } else {
      sb ++= s"""    ${accessModifier} void ${getMemberMutatorName(member)}(${getTypeName(member.typeUse)} ${getFieldName(member)}) {\n"""
      sb ++= s"""        this.${getFieldName(member)} = ${getFieldName(member)};\n"""
      sb ++= s"""    }\n\n"""
    }

    sb.toString()
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy