/**
   * PreparedStatement用にパターン別のコードを埋め込む
   *
   * @param ctClass クラス
   * @param method メソッド
   * @param bindValCode バインド変数取得用コード
   * @param explainCodeTemplate 実行計画取得用コード
   * @throws CannotCompileException コード埋め込みに失敗した場合
   */
  public static void convertPreparedMethod(
      final CtClass ctClass,
      final CtBehavior method,
      final String bindValCode,
      final String explainCodeTemplate)
      throws CannotCompileException {
    String className = ctClass.getName();
    String methodName = method.getName();
    className = className.substring(className.lastIndexOf('.') + 1);

    // setメソッドの引数の1番目がintのときのみ、実行計画取得用処理、バインド引数取得処理を追加する
    try {
      CtClass[] paramTypes;
      paramTypes = method.getParameterTypes();
      if (paramTypes.length >= 1 && "int".equals(paramTypes[0].getName())) {
        // 実行計画取得用PreparedStatementのsetXXXを適したメソッド名に変更する
        String explainCode =
            explainCodeTemplate.replaceAll(REPLACETARGET_OF_PLANPREPARED, methodName);

        // 前処理を埋め込む
        String key = "javelin.jdbc.instrument.JdbcJavelinConverter.ModifiedMethodLabel";
        String message = JdbcJavelinMessages.getMessage(key, className, methodName);
        SystemLogger.getInstance().info(message);

        method.insertBefore(bindValCode + explainCode);
      }
    } catch (NotFoundException e) {
      SystemLogger.getInstance().warn(e);
    }
  }
 private static void addSqlToFieldCon(final CtClass ctClass, final CtBehavior method)
     throws CannotCompileException {
   String addSqlCode =
       JdbcJavelinRecorder.class.getName()
           + ".postPrepareStatement($0, $1, $_, \""
           + method.getName()
           + "\");";
   method.insertAfter(addSqlCode);
   String key = "javelin.jdbc.instrument.JdbcJavelinConverter.ModifiedMethodLabel";
   String message = JdbcJavelinMessages.getMessage(key, ctClass.getName(), method.getName());
   SystemLogger.getInstance().info(message);
   if (SystemLogger.getInstance().isDebugEnabled()) {
     String tegKey = "javelin.jdbc.instrument.JdbcJavelinConverter.JDBCJavelinTag";
     String jdbcJavelinTag = JdbcJavelinMessages.getMessage(tegKey);
     String messageKey = "javelin.jdbc.instrument.JdbcJavelinConverter.SQLAddedLabel1";
     String logMessage = JdbcJavelinMessages.getMessage(messageKey, method.getName());
     SystemLogger.getInstance().debug(jdbcJavelinTag + logMessage);
   }
 }
  /**
   * PreparedStatement#close用にコードを埋め込む
   *
   * @param ctClass PreparedStatementを実装するクラス
   * @param method closeメソッド
   * @throws CannotCompileException コード埋め込みに失敗した場合
   */
  public static void convertPreparedMethodClose(final CtClass ctClass, final CtBehavior method)
      throws CannotCompileException {
    String className = ctClass.getName();
    className = className.substring(className.lastIndexOf('.') + 1);

    // 前処理を埋め込む
    String key = "javelin.jdbc.instrument.JdbcJavelinConverter.ModifiedMethodLabel";
    String message = JdbcJavelinMessages.getMessage(key, className, method.getName());
    SystemLogger.getInstance().info(message);
    method.insertAfter(BCI_METHOD_PLANFORPREPARED_CLOSE);
  }
 /**
  * 指定したクラスに指定したメソッドが存在するかどうかを調べます。
  *
  * @param targetClass メソッドの存在を調べるクラス
  * @param methodName メソッド名
  * @return メソッドが存在する場合は <code>true</code> 、存在しない場合は <code>false</code>
  */
 public static boolean hasBehavior(final CtClass targetClass, final String methodName) {
   boolean method = false;
   CtBehavior[] declaredBehaviors = targetClass.getDeclaredBehaviors();
   for (CtBehavior behavior : declaredBehaviors) {
     String behaviorName = behavior.getName();
     if (methodName.equals(behaviorName)) {
       method = true;
       break;
     }
   }
   return method;
 }
  /**
   * PreparedStatementのexecuteメソッドに、 バインド引数保存用ArrayList初期化処理を追加する。
   *
   * @param ctClass 変換対象のクラス。
   * @param method メソッド。
   * @throws CannotCompileException javassistがコンパイルに失敗した場合。
   */
  private static void convertExecuteMethod(final CtClass ctClass, final CtBehavior method)
      throws CannotCompileException {
    String className = ctClass.getName();
    className = className.substring(className.lastIndexOf('.') + 1);

    // 前処理を埋め込む
    String key = "javelin.jdbc.instrument.JdbcJavelinConverter.ModifiedMethodLabel";
    String message = JdbcJavelinMessages.getMessage(key, className, method.getName());
    SystemLogger.getInstance().info(message);
    method.insertAfter(
        "this.jdbcJavelinBindValIndex_ = 0;" + "this.flagForPlanStmt_ = false;", true);
  }
  /**
   * フィールドからSQLを削除する
   *
   * @param ctClass クラス
   * @param method メソッド
   * @throws CannotCompileException コンパイル不可時のエラー
   */
  public static void delSqlFromField(final CtClass ctClass, final CtBehavior method)
      throws CannotCompileException {
    // 実行しているSQL文を追加する用コードを追加する(Statementの方)
    String addSqlCode = "if(this != null) this.jdbcJavelinSql_.clear();";
    method.insertAfter(addSqlCode);

    if (SystemLogger.getInstance().isDebugEnabled()) {
      String tagKey = "javelin.jdbc.instrument.JdbcJavelinConverter.JDBCJavelinTag";
      String jdbcJavelinTag = JdbcJavelinMessages.getMessage(tagKey);
      String messageKey = "javelin.jdbc.instrument.JdbcJavelinConverter.SQLDeletedLabel3";
      String logMessage = JdbcJavelinMessages.getMessage(messageKey, method.getName());
      SystemLogger.getInstance().debug(jdbcJavelinTag + logMessage);
    }
  }
  /**
   * Statementを実装するクラスに計測コードを埋め込む。
   *
   * @param pool クラスプール
   * @param ctClass Statementを実装するクラス
   * @param method 埋め込み対象メソッド
   * @throws CannotCompileException コード埋め込みに失敗した場合
   */
  public static void convertStatementMethod(
      final ClassPool pool, final CtClass ctClass, final CtBehavior method)
      throws CannotCompileException {
    // StatsJavelinによる計測コードを埋め込む(Statementの方用)
    addRecordCode(ctClass, method);
    convertCatch(pool, ctClass, method);

    if (SystemLogger.getInstance().isDebugEnabled()) {
      String tegKey = "javelin.jdbc.instrument.JdbcJavelinConverter.JDBCJavelinTag";
      String jdbcJavelinTag = JdbcJavelinMessages.getMessage(tegKey);
      String messageKey = "javelin.jdbc.instrument.JdbcJavelinConverter.ConvertLabel";
      String message = JdbcJavelinMessages.getMessage(messageKey, method.getName());
      SystemLogger.getInstance().debug(jdbcJavelinTag + message);
    }
  }
  private static void addSqlToFieldStat(final CtClass ctClass, final CtBehavior method)
      throws CannotCompileException {
    try {
      CtClass[] parameterTypes = method.getParameterTypes();
      if (!(parameterTypes == null) && (parameterTypes.length > 0)) {
        // 実行しているSQL文を追加する用コードを追加する(StatementのaddBatchメソッド用)
        String addSqlCode = "if(this != null) this.jdbcJavelinSql_.add($1);";
        method.insertAfter(addSqlCode);

        if (SystemLogger.getInstance().isDebugEnabled()) {
          String tagKey = "javelin.jdbc.instrument.JdbcJavelinConverter.JDBCJavelinTag";
          String jdbcJavelinTag = JdbcJavelinMessages.getMessage(tagKey);
          String messageKey = "javelin.jdbc.instrument.JdbcJavelinConverter.SQLAddedLabel2";
          String logMessage = JdbcJavelinMessages.getMessage(messageKey, method.getName());
          SystemLogger.getInstance().debug(jdbcJavelinTag + logMessage);
        }
      }
    } catch (NotFoundException e) {
      SystemLogger.getInstance().warn("", e);
    }
  }
  /**
   * Statementのメソッドに対して計測コードを埋め込む。
   *
   * @param pool Statementを含むプール。
   * @param ctClass Statementを実装するクラス
   * @param method メソッド
   * @param inheritedStatement Statementを実装したクラスを変換する場合は <code>true</code>
   * @param inheritedPreparedStatement PreparedStatementを実装したクラスを変換する場合は <code>true</code>
   */
  public static void convertStatement(
      final ClassPool pool,
      final CtClass ctClass,
      final CtBehavior method,
      boolean inheritedStatement,
      boolean inheritedPreparedStatement) {
    try {
      // BCI対象クラス「java.sql.Connection」に対して、コード転換を行う
      String methodName = method.getName();

      // BCI対象クラス「java.sql.Statement」に対して、コード転換を行う
      if (inheritedStatement) {
        if (SystemLogger.getInstance().isDebugEnabled()) {
          SystemLogger.getInstance().debug("JDBC JAVELIN:-->Running ctMethodName:" + methodName);
        }

        if ("execute".equals(methodName)
            || "executeQuery".equals(methodName)
            || "executeUpdate".equals(methodName)
            || "executeBatch".equals(methodName)) {
          convertStatementMethod(pool, ctClass, method);
        } else if ("addBatch".equals(methodName)) {
          addSqlToFieldStat(ctClass, method);
        } else if ("clearBatch".equals(methodName)) {
          if (inheritedPreparedStatement == false) {
            delSqlFromField(ctClass, method);
          }
        }
      }

      // BCI対象クラス「java.sql.PreparedStatement」に対して、コード転換を行う
      if (inheritedPreparedStatement) {
        if (SystemLogger.getInstance().isDebugEnabled()) {
          SystemLogger.getInstance().debug("JDBC JAVELIN:-->Running ctMethodName:" + methodName);
        }
        if ("setString".equals(methodName) || "setObject".equals(methodName)) {
          // パターンAのsetter. 文字列長制限ラベルに具体的な設定値を入れる.
          long jdbcStringLimitLength = JdbcJavelinRecorder.getConfig().getJdbcStringLimitLength();
          String code =
              BCI_METHOD_A.replaceAll("BCI_METHOD_A_LENGTH", String.valueOf(jdbcStringLimitLength));
          convertPreparedMethod(ctClass, method, code, BCI_METHOD_PLANFORPREPARED_SETXXX);
        } else if ("setBigDecimal".equals(methodName)
            || "setDate".equals(methodName)
            || "setTime".equals(methodName)
            || "setTimestamp".equals(methodName)) {
          // パターンBのsetter
          convertPreparedMethod(ctClass, method, BCI_METHOD_B, BCI_METHOD_PLANFORPREPARED_SETXXX);
        } else if ("setBoolean".equals(methodName)
            || "setShort".equals(methodName)
            || "setInt".equals(methodName)
            || "setLong".equals(methodName)
            || "setFloat".equals(methodName)
            || "setDouble".equals(methodName)) {
          // パターンCのsetter
          convertPreparedMethod(ctClass, method, BCI_METHOD_C, BCI_METHOD_PLANFORPREPARED_SETXXX);
        } else if ("setByte".equals(methodName) || "setBytes".equals(methodName)) {
          // パターンDのsetter
          convertPreparedMethod(ctClass, method, BCI_METHOD_D, BCI_METHOD_PLANFORPREPARED_SETXXX);
        } else if ("setArray".equals(methodName)
            || "setBlob".equals(methodName)
            || "setClob".equals(methodName)
            || "setRef".equals(methodName)
            || "setURL".equals(methodName)) {
          // パターンEのsetter
          convertPreparedMethod(ctClass, method, BCI_METHOD_E, BCI_METHOD_PLANFORPREPARED_SETXXX);
        } else if ("setNull".equals(methodName)) {
          // パターンFのsetter
          convertPreparedMethod(ctClass, method, BCI_METHOD_F, BCI_METHOD_PLANFORPREPARED_SETXXX);
        } else if ("setAsciiStream".equals(methodName)
            || "setBinaryStream".equals(methodName)
            || "setUnicodeStream".equals(methodName)) {
          // TODO Java6.0 でインターフェースの仕様が変わったため、引数の数が3以外のものも対応が必要。
          if (method.getParameterTypes().length == ARGS) {
            // パターンGのsetter
            convertPreparedMethod(
                ctClass, method, BCI_METHOD_E, BCI_METHOD_PLANFORPREPARED_SETINPUTSTREAM);
          }
        } else if ("setCharacterStream".equals(methodName)) {
          // TODO Java6.0 でインターフェースの仕様が変わったため、引数の数が3以外のものも対応が必要。
          if (method.getParameterTypes().length == ARGS) {
            // パターンHのsetter
            convertPreparedMethod(
                ctClass, method, BCI_METHOD_E, BCI_METHOD_PLANFORPREPARED_SETREADER);
          }
        } else if ("addBatch".equals(methodName)) {
          convertPreparedMethodAddBatch(ctClass, method);
        } else if ("clearBatch".equals(methodName)) {
          convertPreparedMethodClearBatch(ctClass, method);
        } else if ("close".equals(methodName)) {
          convertPreparedMethodClose(ctClass, method);
        }
        if ("execute".equals(methodName)
            || "executeBatch".equals(methodName)
            || "executeQuery".equals(methodName)
            || "executeUpdate".equals(methodName)) {
          convertExecuteMethod(ctClass, method);
        }
      }
    } catch (Throwable ex) {
      SystemLogger.getInstance().warn(ex);
    }
  }
  /**
   * Statementのメソッドに対して計測コードを埋め込む。
   *
   * @param pool Statementを含むプール。
   * @param ctClass Statementを実装するクラス
   * @return 変換結果
   */
  public static CtClass convertConnection(final ClassPool pool, final CtClass ctClass) {
    CtClass jvnConnction;
    try {
      jvnConnction = pool.get(JdbcJavelinConnection.class.getCanonicalName());
      boolean hasInterface = hasInterface(ctClass, jvnConnction);
      if (hasInterface == false) {
        ctClass.addInterface(jvnConnction);
      }

      // すでにメソッドを追加している場合は処理を行わない
      if (hasBehavior(ctClass, "getJdbcJavelinProcessor")) {
        return ctClass;
      }

      CtField procField =
          CtField.make(
              "private " + DBProcessor.class.getCanonicalName() + " dbProcessor_;", ctClass);
      ctClass.addField(procField);
      CtMethod procGetMethod =
          CtMethod.make(
              "public "
                  + DBProcessor.class.getCanonicalName()
                  + " getJdbcJavelinProcessor(){ return dbProcessor_; }",
              ctClass);
      ctClass.addMethod(procGetMethod);
      CtMethod procSetMethod =
          CtMethod.make(
              "public void setJdbcJavelinProcessor("
                  + DBProcessor.class.getCanonicalName()
                  + " dbProcessor){ dbProcessor_ = dbProcessor; }",
              ctClass);
      ctClass.addMethod(procSetMethod);

      CtField urlField = CtField.make("private String jdbcUrl_;", ctClass);
      ctClass.addField(urlField);
      CtMethod urlMethod = CtMethod.make("public String getJdbcUrl(){ return jdbcUrl_; }", ctClass);
      ctClass.addMethod(urlMethod);
      CtMethod urlSetMethod =
          CtMethod.make(
              "public void " + "setJdbcUrl(String jdbcUrl)" + "{ jdbcUrl_ = jdbcUrl; }", ctClass);
      ctClass.addMethod(urlSetMethod);
    } catch (NotFoundException ex) {
      SystemLogger.getInstance().warn(ex);
      return null;
    } catch (CannotCompileException ex) {
      SystemLogger.getInstance().warn(ex);
      return null;
    }

    CtBehavior[] behaviors = ctClass.getDeclaredBehaviors();
    for (int index = 0; index < behaviors.length; index++) {
      CtBehavior method = behaviors[index];
      // メソッドの定義がない場合、あるいはpublicでない
      // (->インターフェースに定義されていない)場合は実行しない。
      final int MODIFIER = method.getModifiers();
      if (Modifier.isAbstract(MODIFIER) || !Modifier.isPublic(MODIFIER)) {
        continue;
      }
      // BCI対象クラス「java.sql.Connection」に対して、コード転換を行う
      String methodName = method.getName();
      try {
        if ("prepareStatement".equals(methodName)) {
          addSqlToFieldCon(ctClass, method);
        } else if ("prepareCall".equals(methodName)) {
          addSqlToFieldCon(ctClass, method);
        }
      } catch (CannotCompileException ex) {
        SystemLogger.getInstance().warn(ex);
      }
    }

    try {
      return ctClass;
    } catch (Exception ex) {
      SystemLogger.getInstance().warn(ex);
      return null;
    }
  }