// Copyright 2012 Google 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 com.google.enterprise.connector.notes;

import com.google.enterprise.connector.notes.client.NotesBase;
import com.google.enterprise.connector.spi.RepositoryException;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Helpers.
 */
class Util {
  private static final String CLASS_NAME = Util.class.getName();
  private static final Logger LOGGER = Logger.getLogger(CLASS_NAME);
  private static final String DEFAULT_ALGORITHM = "SHA1";
  private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();

  static void recycle(NotesBase obj) {
    if (null != obj) {
      try {
        obj.recycle();
      } catch (RepositoryException e) {
        LOGGER.logp(Level.WARNING, CLASS_NAME, "recycle(obj)",
            "Error calling recycle on Notes object: " + obj, e);
      }
    }
  }

  static void recycle(NotesBase obj, Vector vec) {
    if (null != obj && null != vec) {
      try {
        obj.recycle(vec);
      } catch (RepositoryException e) {
        LOGGER.logp(Level.WARNING, CLASS_NAME, "recycle(obj, vec)",
            "Error calling recycle on Notes object: " + obj
            + " with data: " + vec, e);
      }
    }
  }

  static void close(Statement stmt) {
    if (null != stmt) {
      try {
        stmt.close();
      } catch (SQLException e) {
        LOGGER.logp(Level.WARNING, CLASS_NAME, "close",
            "Error closing statement", e);
      }
    }
  }

  static void close(ResultSet rs) {
    if (null != rs) {
      try {
        rs.close();
      } catch (SQLException e) {
        LOGGER.logp(Level.WARNING, CLASS_NAME, "close",
            "Error closing result set", e);
      }
    }
  }
  
  static void executeStatements(Connection connection, boolean autoCommit,
      String... statements) throws SQLException {
    final String METHOD = "executeStatements";
    if (connection == null) {
      throw new SQLException("Database connection is null");
    }
    Statement stmt = null;
    try {
      stmt = connection.createStatement();
      connection.setAutoCommit(autoCommit);
      connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
      for (String statement : statements) {
        stmt.executeUpdate(statement);
        LOGGER.logp(Level.FINE, CLASS_NAME, METHOD, "Executed " + statement);
      }
      if (autoCommit == false) {
        try {
          connection.commit();
          LOGGER.logp(Level.FINE, CLASS_NAME, METHOD,
              "Committed all transactions successfully");
        } catch (SQLException sqle) {
          connection.rollback();
          LOGGER.logp(Level.FINE, CLASS_NAME, METHOD,
              "Rolled back all transactions");
          throw sqle;
        }
      }
    } catch (SQLException e) {
      LOGGER.logp(Level.FINE, CLASS_NAME, METHOD,
          "Failed to execute statements", e);
      throw e;
    } finally {
      if (stmt != null) {
        try {
          stmt.close();
        } catch (SQLException e) {
          LOGGER.logp(Level.WARNING, CLASS_NAME, METHOD, e.getMessage());
        }
      }
    }
  }

  static boolean isCanonical(String name) {
    if (name == null) {
      return false;
    }
    return name.toLowerCase().startsWith("cn=");
  }

  static boolean isAttachment(String url) {
    return url != null && url.toLowerCase().contains("/$file/");
  }

  static void invokeGC() {
    Runtime rt = Runtime.getRuntime();
    LOGGER.log(Level.FINEST, "Memory free [before GC invocation]: " +
        (rt.freeMemory() / 1024) + "kb" + ", Total: " +
        (rt.totalMemory() / 1024) + "kb");
    rt.gc();
    LOGGER.log(Level.FINEST, "Memory free [after GC invocation ]: " +
        (rt.freeMemory() / 1024) + "kb" + ", Total: " +
        (rt.totalMemory() / 1024) + "kb");
  }

  static String buildString(String...args) {
    StringBuilder buf = new StringBuilder();
    for (String arg : args) {
      buf.append(arg);
    }
    return buf.toString();
  }

  static String hash(String word) {
    try {
      MessageDigest digest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
      String hashStr = asHex(digest.digest(word.getBytes()));
      LOGGER.log(Level.FINEST, "Create a hash for {0} => {1}",
          new Object[] {word, hashStr});
      return hashStr;
    } catch (NoSuchAlgorithmException e) {
      LOGGER.log(Level.WARNING, "Unable to initialize " + DEFAULT_ALGORITHM
          + " message digest");
      return null;
    }
  }

  /**
   * Utility method to convert a byte[] to hex string.  This method is a copy of
   * Util.asHex method in DB Connector.
   *
   * @param buf
   * @return hex string.
   */
  public static String asHex(byte[] buf) {
    char[] chars = new char[2 * buf.length];
    for (int i = 0; i < buf.length; ++i) {
      chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4];
      chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F];
    }
    return new String(chars);
  }

  /**
   * Private constructor to prevent instantiation.
   */
  private Util() {
  }
}

@@ -630,16 +630,56 @@
 notes;%0A%0A
+import com.google.common.base.Charsets;%0A
 import c
@@ -796,16 +796,68 @@
 ception;
+%0Aimport com.google.enterprise.connector.util.Base16;
 %0A%0Aimport
@@ -1364,84 +1364,8 @@
 A1%22;
-%0A  private static final char%5B%5D HEX_CHARS = %220123456789abcdef%22.toCharArray();
 %0A%0A  
@@ -4890,31 +4890,27 @@
     
-String hashStr = asHex(
+byte%5B%5D hashBytes = 
 dige
@@ -4933,18 +4933,92 @@
 etBytes(
-))
+Charsets.UTF_8));%0A      String hashStr = Base16.lowerCase().encode(hashBytes
 );%0A     
@@ -5304,24 +5304,24 @@
 eturn null;%0A
+
     %7D%0A  %7D%0A%0A 
@@ -5323,465 +5323,8 @@
  %7D%0A%0A
-  /**%0A   * Utility method to convert a byte%5B%5D to hex string.  This method is a copy of%0A   * Util.asHex method in DB Connector.%0A   *%0A   * @param buf%0A   * @return hex string.%0A   */%0A  public static String asHex(byte%5B%5D buf) %7B%0A    char%5B%5D chars = new char%5B2 * buf.length%5D;%0A    for (int i = 0; i %3C buf.length; ++i) %7B%0A      chars%5B2 * i%5D = HEX_CHARS%5B(buf%5Bi%5D & 0xF0) %3E%3E%3E 4%5D;%0A      chars%5B2 * i + 1%5D = HEX_CHARS%5Bbuf%5Bi%5D & 0x0F%5D;%0A    %7D%0A    return new String(chars);%0A  %7D%0A%0A
   /*

Use new CM Base16 class for hex string encoding.