/**
   * Private helper method to get the name of the output message class for a FOperation object. The
   * method will return null, if the operation has no output message at all.
   *
   * @param operation FOperation object from WSDL parser
   * @return Output message class name or null
   */
  private String getOutputMessageName(final FOperation operation) {
    String result = null;

    // Operation has output message?
    if (null != operation.getOutputMessage()) {
      result =
          WorkerThreadGenerator.firstLetterCapital(
                  operation.getOutputMessage().getMessageAttribute().getLocalPart())
              + "Message";
    }

    return result;
  }
  /**
   * Create a file that contains a single worker thread class. All child worker classes are
   * generated on a per-operation basis for each and every RPC method defined in the WSDL document.
   *
   * <p>This method will trigger the class generation and add all required Java imports to the
   * source file.
   *
   * @param operation FOperation object from WSDL parser
   * @throws Exception Error during code generation
   */
  private void createWorkerThreadFile(final FOperation operation) throws Exception {
    String rpcMethodName = WorkerThreadGenerator.firstLetterCapital(operation.getOperationName());
    JSourceFile jsf =
        this.workspace
            .getJava()
            .getJSourceFile(this.threadWorkerPackageName, rpcMethodName + "Worker");

    // Add child worker thread class
    jsf.add(this.createWorkerThreadClass(operation));

    // Add required imports
    this.addRequiredImport(jsf, "org.slf4j.Logger");
    this.addRequiredImport(jsf, "org.slf4j.LoggerFactory");
    this.addRequiredImport(jsf, "org.atmosphere.websocket.WebSocket");

    this.addRequiredImport(
        jsf, this.serviceProviderPackageName + "." + this.serviceProviderClassName);
    this.addRequiredImport(jsf, this.packageName + "." + this.interfaceName);
    this.addRequiredImport(
        jsf, this.packageName + "." + JSONMarshallerGenerator.MARSHALLER_CLASS_NAME);

    // Import server only if required
    if (null != operation.getOutputMessage()) {
      this.addRequiredImport(jsf, this.packageName + "." + this.interfaceName);
    }

    // Operation has input message
    if (null != operation.getInputMessage()) {
      this.addRequiredImport(
          jsf, this.serviceProviderPackageName + "." + this.getInputMessageName(operation));
    }

    // Operation has output message
    if (null != operation.getOutputMessage()) {
      this.addRequiredImport(
          jsf, this.serviceProviderPackageName + "." + this.getOutputMessageName(operation));
    }
  }
  /**
   * Create code for method body of the run() method that is generated in each of the child thread
   * worker classes. This function will generate code to unmarshal a request message, call the RPC
   * method and finally marshal the response message (if RPC method has any output).
   *
   * <p>The code generation was moved to this method for better readability.
   *
   * @param operation FOperation object from WSDL parser
   * @param rpcMethodName Name of RPC method
   * @return Code for method body of run() method
   */
  private String createRunMethodBody(final FOperation operation, final String rpcMethodName) {
    String methodBody = "";

    // Create JSON marshaller object
    if (null != operation.getInputMessage() || null != operation.getOutputMessage()) {
      methodBody +=
          String.format(
              "// Create JSON marshaller\n" + "%s marshaller = new %s();\n\n",
              JSONMarshallerGenerator.MARSHALLER_CLASS_NAME,
              JSONMarshallerGenerator.MARSHALLER_CLASS_NAME);
    }

    // Operation has input message?
    if (null != operation.getInputMessage()) {
      String inputMessageClassName = this.getInputMessageName(operation);

      // Create code to convert JSON code to a bean object
      methodBody +=
          String.format(
              "// Unmarshal JSON code from request\n"
                  + "%s requestBeanObject = (%s)marshaller.jsonToInstance(%s.class, this.requestMessage.payload());\n\n",
              inputMessageClassName, inputMessageClassName, inputMessageClassName);
    }

    // Get name of output message (if any)
    String outputMessageClassName = "";
    if (null != operation.getOutputMessage()) {
      outputMessageClassName = this.getOutputMessageName(operation);
    }

    // Create code to call RPC method
    methodBody +=
        String.format(
            "// Call service operation\n"
                + "LOGGER.info(\"Processing '%s()' request...\");\n"
                + "%sthis.serviceProvider.%s(%s);",
            WorkerThreadGenerator.firstLetterLowercase(rpcMethodName),
            (null != operation.getOutputMessage()
                ? String.format("%s responseBeanObject = ", outputMessageClassName)
                : ""), // Method has return value?
            operation.getOperationName(),
            (null != operation.getInputMessage() ? "requestBeanObject" : "")); // Method has input?

    // Operation has output message?
    if (null != operation.getOutputMessage()) {
      // Create code to convert bean object to JSON code
      methodBody +=
          String.format(
              "\n\n"
                  + "// Marshal bean and create response message\n"
                  + "String jsonResponse = marshaller.instanceToJSON(responseBeanObject);\n"
                  + "%s responseMessage = new %s(this.requestMessage.uuid(), this.requestMessage.method(), jsonResponse);\n\n",
              this.messageClassFullName, this.messageClassFullName);

      // Create code to send response
      methodBody +=
          String.format(
              "// Send response to client\n"
                  + "LOGGER.info(\"Responding to '%s()' request...\");\n"
                  + "%s.sendMessage(this.webSocket, responseMessage.asString());",
              WorkerThreadGenerator.firstLetterLowercase(rpcMethodName), this.interfaceName);
    }

    // Surround code with try..catch-block
    methodBody =
        String.format(
            "try {\n"
                + WorkerThreadGenerator.indentCode(methodBody)
                + "}\n"
                + "catch (Exception e) {\n"
                + "\tString jsonResponse = String.format(\"{ \\\"Error\\\": \\\"%%s\\\" }\", e.getMessage());\n"
                + "\t%s responseMessage = new %s(this.requestMessage.uuid(), this.requestMessage.method(), jsonResponse);\n\n"
                + "\tLOGGER.error(\"Error: \" + e.getMessage());\n"
                + "\t%s.sendMessage(this.webSocket, responseMessage.asString());\n"
                + "}",
            this.messageClassFullName,
            this.messageClassFullName,
            this.interfaceName);

    return methodBody;
  }
  /**
   * Create Java class for a single child worker thread. The generated class will be derived from
   * the base worker thread class and only contains a run() method to start the thread.
   *
   * <p>Furthermore, the code includes an inner class that utilizes the Singleton and Builder design
   * pattern, to create new worker thread objects at runtime.
   *
   * @param operation FOperation object from WSDL parser
   * @return JClass object with child worker thread class
   * @throws Exception Error during code generation
   */
  private JClass createWorkerThreadClass(final FOperation operation) throws Exception {
    String rpcMethodName = WorkerThreadGenerator.firstLetterCapital(operation.getOperationName());
    String workerClassName = rpcMethodName + "Worker";

    // Create worker thread class
    JClass workerClass = JClass.factory.create(JModifier.PUBLIC, workerClassName);
    workerClass.setExtends(WORKER_THREAD_CLASS_NAME);
    workerClass.setComment(
        new JClassCommentImpl(String.format("The '%s' class.", workerClassName)));

    LOGGER.debug(String.format("Created '%s' class for child worker thread.", workerClassName));

    /**
     * *************************************************************** Create fields
     * ***************************************************************
     */
    JField logger =
        JField.factory.create(
            JModifier.PRIVATE | JModifier.STATIC | JModifier.FINAL,
            "Logger",
            "LOGGER",
            String.format("LoggerFactory.getLogger(%s.class)", workerClassName));
    logger.setComment(new JFieldCommentImpl("Logger object."));
    workerClass.add(logger);

    String builderClassName = workerClassName + "Builder";
    JField builder =
        JField.factory.create(
            JModifier.PUBLIC | JModifier.STATIC | JModifier.FINAL,
            builderClassName,
            "BUILDER",
            String.format("%s.getInstance()", builderClassName));
    builder.setComment(
        new JFieldCommentImpl(
            String.format("Builder instance to create new '%s' objects.", workerClassName)));
    workerClass.add(builder);

    /**
     * *************************************************************** Create constructor
     * ***************************************************************
     */
    JParameter serviceProvider =
        JParameter.factory.create(
            JModifier.FINAL, this.serviceProviderClassName, "serviceProvider");
    JParameter webSocket = JParameter.factory.create(JModifier.FINAL, "WebSocket", "webSocket");
    JParameter requestMessage =
        JParameter.factory.create(JModifier.FINAL, this.messageClassFullName, "requestMessage");

    JMethodSignature jms =
        JMethodSignature.factory.create(serviceProvider, webSocket, requestMessage);
    JConstructor constructor = JConstructor.factory.create(JModifier.PRIVATE, workerClassName, jms);
    constructor.setComment(
        new JConstructorCommentImpl(
            String.format("Constructor to create a new '%s' object.", workerClassName)));

    // Set method body
    String methodBody = "super(serviceProvider, webSocket, requestMessage);";
    constructor.getBody().setSource(methodBody);

    // Add constructor to class
    workerClass.add(constructor);

    /**
     * *************************************************************** Create run method
     * ***************************************************************
     */
    JMethod run = JMethod.factory.create(JModifier.PUBLIC, "void", "run");
    run.addAnnotation(JMethodAnnotationImpl.OVERRIDE);
    run.setComment(new JMethodCommentImpl("Run worker thread to handle request."));

    // Set method body
    run.getBody().setSource(this.createRunMethodBody(operation, rpcMethodName));

    // Add method to class
    workerClass.add(run);

    /**
     * *************************************************************** Add inner builder class
     * ***************************************************************
     */
    workerClass.add(this.createInnerBuilderClass(workerClassName));

    return workerClass;
  }