/*
 * jNPad v0.3 - jNPad's an Simple Text Editor written in Java
 *
 * Copyright (C) 2014-2017  rgs
 *
 * Require JDK 1.6 (or later)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * Info, Questions, Suggestions & Bugs Report to rgsevero@gmail.com
 */

package jnpad.util;

import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

/**
 * The Class Utilities.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public final class Utilities {
  /** no instances */
  private Utilities() {
    super();
  }

  /**
   * Now.
   *
   * @return the date
   */
  public static Date now() {
    return new Date();
  }

  // --- System ----------------------------------------------------------------
  /** The Constant EXIT_SUCCESS. */
  private final static int EXIT_SUCCESS = 0;

  /** The Constant EXIT_FAILURE. */
  private final static int EXIT_FAILURE = -1;

  /**
   * Exit failure.
   */
  public static void exitFailure() {
    System.exit(EXIT_FAILURE);
  }

  /**
   * Exit success.
   */
  public static void exitSuccess() {
    System.exit(EXIT_SUCCESS);
  }

  /**
   * GC.
   */
  public static void gc() {
    System.runFinalization();
    Runtime.getRuntime().runFinalization();
    System.gc();
    Runtime.getRuntime().gc();
    try {
      // give the gc time.
      Thread.sleep(100);
    }
    catch (InterruptedException ie) {
      //ignored
    }
  }

  /**
   * Gc_.
   * 
   * @since 0.3
   */
  public static void gc_() {
    Thread thread = new Thread(new Runnable() {
      public void run() {
        gc();
      }
    }, "Perform Garbage Collection"); //$NON-NLS-1$
    thread.setPriority(Thread.MIN_PRIORITY);
    thread.setDaemon(true);
    thread.start();
  }
  
  /**
   * Restart application.
   *
   * @param runBeforeRestart the run before restart
   * @throws IOException Signals that an I/O exception has occurred.
   * @since 0.3
   */
  @SuppressWarnings("nls")
  public static void restartApplication(Runnable runBeforeRestart) throws IOException {
    try {
      // java binary
      String java = System.getProperty("java.home") + "/bin/java";
      // vm arguments
      List<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
      StringBuilder vmArgsOneLine = new StringBuilder();
      for (String arg : vmArguments) {
        // if it's the agent argument : we ignore it otherwise the
        // address of the old application and the new one will be in conflict
        if (!arg.contains("-agentlib")) {
          vmArgsOneLine.append(arg);
          vmArgsOneLine.append(" ");
        }
      }
      // init the command to execute, add the vm args
      final StringBuffer cmd = new StringBuffer("\"" + java + "\" " + vmArgsOneLine);
      // program main and program arguments (be careful a sun property. might not be supported by all JVM) 
      String[] mainCommand = System.getProperty("sun.java.command").split(" ");
      // program main is a jar
      if (mainCommand[0].endsWith(".jar")) {
        // if it's a jar, add -jar mainJar
        cmd.append("-jar " + new File(mainCommand[0]).getPath());
      }
      else {
        // else it's a .class, add the classpath and mainClass
        cmd.append("-cp \"" + System.getProperty("java.class.path") + "\" " + mainCommand[0]);
      }
      // finally add program arguments
      for (int i = 1; i < mainCommand.length; i++) {
        cmd.append(" ");
        cmd.append(mainCommand[i]);
      }
      // execute the command in a shutdown hook, to be sure that all the
      // resources have been disposed before restarting the application
      Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override
        public void run() {
          try {
            Runtime.getRuntime().exec(cmd.toString());
          }
          catch (IOException ex) {
            ex.printStackTrace();
          }
        }
      });
      // execute some custom code before restarting
      if (runBeforeRestart != null) {
        runBeforeRestart.run();
      }
      // exit
      exitSuccess();
    }
    catch (Exception ex) {
      // something went wrong
      throw new IOException("Error while trying to restart the application", ex);
    }
  }
  
  /**
   * Restart application.
   *
   * @param jar the jar
   * @throws IOException Signals that an I/O exception has occurred.
   * @since 0.3
   */
  public static void restartApplication2(String jar) throws IOException {
    Runtime.getRuntime().exec("java -jar ".concat(jar)); //$NON-NLS-1$
    exitSuccess();
  }
  // ---------------------------------------------------------------------------

  // --- List ------------------------------------------------------------------
  /**
   * Expand list size.
   *
   * @param list the list
   * @param newsize the newsize
   */
  public static void expandListSize(List<?> list, int newsize) {
    for (int i = list.size(); i < newsize; ++i) {
      list.add(null);
    }
  }

  /**
   * Restrict list size.
   *
   * @param list the list
   * @param newsize the newsize
   */
  public static void restrictListSize(List<?> list, int newsize) {
    for (int i = list.size() - 1; i >= newsize; --i) {
      list.remove(i);
    }
  }

  /**
   * Sets the list size.
   *
   * @param list the list
   * @param newsize the newsize
   */
  public static void setListSize(List<?> list, int newsize) {
    if (newsize > list.size()) {
      expandListSize(list, newsize);
    }
    else if (newsize < list.size()) {
      restrictListSize(list, newsize);
    }
  }

  /**
   * Checks if is empty.
   *
   * @param list the list
   * @return true, if is empty
   */
  public static boolean isEmptyList(final List<?> list) {
    return list == null || list.isEmpty();
  }

  /**
   * Checks if is not empty.
   *
   * @param list the list
   * @return true, if is not empty
   */
  public static boolean isNotEmptyList(final List<?> list) {
    return !isEmptyList(list);
  }
  // ---------------------------------------------------------------------------

  // --- String ----------------------------------------------------------------
  /** Space string */
  public static final String SPACE_STRING    = " ";   //$NON-NLS-1$

  /** Space */
  public static final char   SPACE           = ' ';

  /** Empty string */
  public static final String EMPTY_STRING    = "";    //$NON-NLS-1$

  /** Line feed */
  public static final String LF_STRING       = "\n";  //$NON-NLS-1$

  /** Line feed */
  public static final char   LF              = '\n';

  /** Carriage return */
  public static final String CR_STRING       = "\r";  //$NON-NLS-1$

  /** Carriage return */
  public static final char   CR              = '\r';

  /** Carriage return followed by line feed */
  public static final String CRLF            = "\r\n"; //$NON-NLS-1$

  /** Tabulator */
  public static final String TAB_STRING      = "\t";  //$NON-NLS-1$

  /** Tabulator */
  public static final char   TAB             = '\t';

  /** Comma */
  public static final String COMMA_STRING    = ",";   //$NON-NLS-1$
  
  /** The Constant INDEX_NOT_FOUND. */
  public static final int    INDEX_NOT_FOUND = -1;

  /**
   * Removes the new line.
   *
   * @param s the s
   * @return the string
   */
  public static String removeNewLine(String s) {
    if (s.length() == 0) {
      return s;
    }
    if (s.charAt(s.length() - 1) == LF) {
      s = s.substring(0, s.length() - 1);
    }
    if (s.charAt(s.length() - 1) == CR) {
      s = s.substring(0, s.length() - 1);
    }
    return s;
  }

  /**
   * Spaces.
   *
   * @param count the count
   * @return the string
   */
  public static String spaces(final int count) {
    if (count <= 0) {
      return EMPTY_STRING;
    }
    StringBuilder sb = new StringBuilder(count);
    for (int i = 0; i < count; i++) {
      sb.append(SPACE);
    }
    return sb.toString();
  }

  /**
   * Count lines.
   *
   * @param s the string
   * @return the nros of lines
   */
  public static int countLines(final String s) {
    int count = 0;
    for (int i = s.length(); i-- > 0; ) {
      if (s.charAt(i) == LF) {
        ++count;
      }
    }
    return count;
  }

  /**
   * Count matches.
   *
   * @param s String
   * @param sub String
   * @return int
   */
  public static int countMatches(final String s, final String sub) {
    if (isEmptyString(s) || isEmptyString(sub)) {
      return 0;
    }
    int count = 0;
    for (int idx = 0; (idx = s.indexOf(sub, idx)) != INDEX_NOT_FOUND; idx += sub.length()) {
      count++;
    }
    return count;
  }

  /**
   * Checks if is empty string.
   *
   * @param s String
   * @return true, if is empty string
   */
  public static boolean isEmptyString(final String s) {
    return s == null || s.length() == 0;
  }

  /**
   * Checks if is not empty string.
   *
   * @param s String
   * @return true, if is not empty string
   */
  public static boolean isNotEmptyString(final String s) {
    return !isEmptyString(s);
  }

  /**
   * Checks if is blank string.
   *
   * @param s String
   * @return true, if is blank string
   */
  public static boolean isBlankString(final String s) {
    int len;
    if (s == null || (len = s.length()) == 0) {
      return true;
    }
    for (int i = 0; i < len; i++) {
      if (!Character.isWhitespace(s.charAt(i))) {
        return false;
      }
    }
    return true;
  }

  /**
   * Checks if is not blank string.
   *
   * @param s String
   * @return true, if is not blank string
   */
  public static boolean isNotBlankString(final String s) {
    return !isBlankString(s);
  }

  /**
   * Default string.
   *
   * @param s String
   * @return String
   */
  public static String defaultString(final String s) {
    return s != null ? s : EMPTY_STRING;
  }

  /**
   * Default string.
   *
   * @param s String
   * @param defaultStr String
   * @return String
   */
  public static String defaultString(final String s, final String defaultStr) {
    return s != null ? s : defaultStr;
  }

  /**
   * Default if empty.
   *
   * @param s String
   * @param defaultStr String
   * @return String
   */
  public static String defaultIfEmpty(final String s, final String defaultStr) {
    return isEmptyString(s) ? defaultStr : s;
  }

  /**
   * Checks if is whole word.
   *
   * @param searchIn CharSequence
   * @param offset int
   * @param len int
   * @return true, if is whole word
   */
  private static boolean isWholeWord(final CharSequence searchIn, final int offset, final int len) {
    boolean wsBefore, wsAfter;

    try {
      wsBefore = !Character.isUnicodeIdentifierPart(searchIn.charAt(offset - 1));
    }
    catch (IndexOutOfBoundsException e) {
      wsBefore = true;
    }

    try {
      wsAfter = !Character.isUnicodeIdentifierPart(searchIn.charAt(offset + len));
    }
    catch (IndexOutOfBoundsException e) {
      wsAfter = true;
    }

    return wsBefore && wsAfter;
  }

  /**
   * Checks if is whole word.
   *
   * @param searchIn CharSequence
   * @param offset int
   * @param len int
   * @param noWordSep String
   * @return true, if is whole word
   */
  private static boolean isWholeWord(final CharSequence searchIn, final int offset, final int len, String noWordSep) {
    boolean wsBefore, wsAfter;
    char ch;

    if (noWordSep == null) {
      noWordSep = EMPTY_STRING;
    }

    try {
      ch = searchIn.charAt(offset - 1);
      wsBefore = (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == INDEX_NOT_FOUND);
    }
    catch (IndexOutOfBoundsException e) {
      wsBefore = true;
    }

    try {
      ch = searchIn.charAt(offset + len);
      wsAfter = (!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == INDEX_NOT_FOUND);
    }
    catch (IndexOutOfBoundsException e) {
      wsAfter = true;
    }

    return wsBefore && wsAfter;
  }

  /**
   * Index of.
   *
   * @param s String
   * @param searchStr String
   * @return int
   */
  public static int indexOf(String s, String searchStr) {
    if (s == null || searchStr == null)
      return INDEX_NOT_FOUND;
    return s.indexOf(searchStr);
  }

  /**
   * Index of.
   *
   * @param s String
   * @param searchStr String
   * @param startPos int
   * @return int
   */
  public static int indexOf(String s, String searchStr, int startPos) {
    if (s == null || searchStr == null)
      return INDEX_NOT_FOUND;
    if (searchStr.length() == 0 && startPos >= s.length())
      return s.length();
    return s.indexOf(searchStr, startPos);
  }
  
  /**
   * Index of.
   *
   * @param s String
   * @param searchStr String
   * @param ignoreCase boolean
   * @param wholeWord boolean
   * @return int
   */
  public static int indexOf(final String s, final String searchStr, final boolean ignoreCase, final boolean wholeWord) {
    return indexOf(s, searchStr, 0, ignoreCase, wholeWord);
  }
 
  /**
   * Index of.
   *
   * @param s String
   * @param searchStr String
   * @param startPos int
   * @param ignoreCase boolean
   * @param wholeWord boolean
   * @return int
   */
  public static int indexOf(final String s, final String searchStr, final int startPos, final boolean ignoreCase, final boolean wholeWord) {
    if (s == null || searchStr == null) {
      return INDEX_NOT_FOUND;
    }
    if (searchStr.length() == 0 && startPos >= s.length()) {
      return s.length();
    }

    final String searchIn = ignoreCase ? s.toLowerCase() : s;
    final String searchFor = ignoreCase ? searchStr.toLowerCase() : searchStr;

    if (wholeWord) {
      int len = searchFor.length();
      int temp = startPos;
      int tempChange = 1;
      while (true) {
        temp = searchIn.indexOf(searchFor, temp);
        if (temp != INDEX_NOT_FOUND) {
          if (isWholeWord(searchIn, temp, len)) {
            return temp;
          }
          temp += tempChange;
          continue;
        }
        return temp; // Always INDEX_NOT_FOUND (-1)
      }
    }
    return searchIn.indexOf(searchFor, startPos);
  }

  /**
   * Index of.
   *
   * @param s String
   * @param searchStr String
   * @param ignoreCase boolean
   * @param wholeWord boolean
   * @param noWordSep String
   * @return int
   */
  public static int indexOf(final String s, final String searchStr, final boolean ignoreCase, final boolean wholeWord, String noWordSep) {
    return indexOf(s, searchStr, 0, ignoreCase, wholeWord, noWordSep);
  }

  /**
   * Index of.
   *
   * @param s String
   * @param searchStr String
   * @param startPos int
   * @param ignoreCase boolean
   * @param wholeWord boolean
   * @param noWordSep String
   * @return int
   */
  public static int indexOf(final String s, final String searchStr, final int startPos, final boolean ignoreCase, final boolean wholeWord, String noWordSep) {
    if (s == null || searchStr == null) {
      return INDEX_NOT_FOUND;
    }
    if (searchStr.length() == 0 && startPos >= s.length()) {
      return s.length();
    }

    final String searchIn = ignoreCase ? s.toLowerCase() : s;
    final String searchFor = ignoreCase ? searchStr.toLowerCase() : searchStr;

    if (wholeWord) {
      int len = searchFor.length();
      int temp = startPos;
      int tempChange = 1;
      while (true) {
        temp = searchIn.indexOf(searchFor, temp);
        if (temp != INDEX_NOT_FOUND) {
          if (isWholeWord(searchIn, temp, len, noWordSep)) {
            return temp;
          }
          temp += tempChange;
          continue;
        }
        return temp; // Always INDEX_NOT_FOUND (-1)
      }
    }
    return searchIn.indexOf(searchFor, startPos);
  }

  /**
   * Last index of.
   *
   * @param s String
   * @param searchStr String
   * @return int
   */
  public static int lastIndexOf(String s, String searchStr) {
    if (s == null || searchStr == null)
      return INDEX_NOT_FOUND;
    return s.lastIndexOf(searchStr);
  }

  /**
   * Last index of.
   *
   * @param s String
   * @param searchStr String
   * @param startPos int
   * @return int
   */
  public static int lastIndexOf(String s, String searchStr, int startPos) {
    if (s == null || searchStr == null)
      return INDEX_NOT_FOUND;
    return s.lastIndexOf(searchStr, startPos);
  }
  
  /**
   * Last index of.
   *
   * @param s String
   * @param searchStr String
   * @param ignoreCase boolean
   * @param wholeWord boolean
   * @return int
   */
  public static int lastIndexOf(final String s, final String searchStr, final boolean ignoreCase, final boolean wholeWord) {
    return lastIndexOf(s, searchStr, s.length(), ignoreCase, wholeWord);
  }

  /**
   * Last index of.
   *
   * @param s String
   * @param searchStr String
   * @param startPos int
   * @param ignoreCase boolean
   * @param wholeWord boolean
   * @return int
   */
  public static int lastIndexOf(final String s, final String searchStr, final int startPos, final boolean ignoreCase, final boolean wholeWord) {
    if (s == null || searchStr == null) {
      return INDEX_NOT_FOUND;
    }

    final String searchIn = ignoreCase ? s.toLowerCase() : s;
    final String searchFor = ignoreCase ? searchStr.toLowerCase() : searchStr;

    if (wholeWord) {
      int len = searchFor.length();
      int temp = searchIn.length();
      int tempChange = -1;
      while (true) {
        temp = searchIn.lastIndexOf(searchFor, temp);
        if (temp != INDEX_NOT_FOUND) {
          if (isWholeWord(searchIn, temp, len)) {
            return temp;
          }
          temp += tempChange;
          continue;
        }
        return temp; // Always INDEX_NOT_FOUND (-1)
      }
    }
    return searchIn.lastIndexOf(searchFor, startPos);
  }

  /**
   * Last index of.
   *
   * @param s String
   * @param searchStr String
   * @param ignoreCase boolean
   * @param wholeWord boolean
   * @param noWordSep String
   * @return int
   */
  public static int lastIndexOf(final String s, final String searchStr, final boolean ignoreCase, final boolean wholeWord, String noWordSep) {
    return lastIndexOf(s, searchStr, s.length(), ignoreCase, wholeWord, noWordSep);
  }

  /**
   * Last index of.
   *
   * @param s String
   * @param searchStr String
   * @param startPos int
   * @param ignoreCase boolean
   * @param wholeWord boolean
   * @param noWordSep String
   * @return int
   */
  public static int lastIndexOf(final String s, final String searchStr, final int startPos, final boolean ignoreCase, final boolean wholeWord, String noWordSep) {
    if (s == null || searchStr == null) {
      return INDEX_NOT_FOUND;
    }

    final String searchIn = ignoreCase ? s.toLowerCase() : s;
    final String searchFor = ignoreCase ? searchStr.toLowerCase() : searchStr;

    if (wholeWord) {
      int len = searchFor.length();
      int temp = searchIn.length();
      int tempChange = -1;
      while (true) {
        temp = searchIn.lastIndexOf(searchFor, temp);
        if (temp != INDEX_NOT_FOUND) {
          if (isWholeWord(searchIn, temp, len, noWordSep)) {
            return temp;
          }
          temp += tempChange;
          continue;
        }
        return temp; // Always INDEX_NOT_FOUND (-1)
      }
    }
    return searchIn.lastIndexOf(searchFor, startPos);
  }
  
  /**
   * Gets the line separator.
   *
   * @return the line separator
   */
  public static String getLineSeparator() {
    try {
      return System.getProperty("line.separator", LF_STRING); //$NON-NLS-1$
    }
    catch (Exception e) {
      return LF_STRING;
    }
  }

  /**
   * Convert to unix line endings.
   *
   * @param s the string
   * @return the string
   */
  public static String convertToUnixLineEndings(String s) {
    return convertLineEndings(s, LF_STRING);
  }

  /**
   * Convert to platform line endings.
   *
   * @param s the string
   * @return the string
   */
  public static String convertToPlatformLineEndings(String s) {
    return convertLineEndings(s, getLineSeparator());
  }

  /**
   * Convert line endings.
   *
   * @param s String
   * @param separator String
   * @return the string
   */
  public static String convertLineEndings(String s, String separator) {
    String result;
    int i;
    if (s != null && (i = s.length()) > 0 && separator != null) {
      StringBuilder sb = new StringBuilder(i);
      int j = 0;
      for (int k = 0; k < i; k++) {
        char c = s.charAt(k);
        if (c != CR && c != LF) {
          continue;
        }
        sb.append(s.substring(j, k));
        sb.append(separator);
        if (c == CR && k < i - 1 && s.charAt(k + 1) == LF) {
          k++;
        }
        j = k + 1;
      }

      if (j < i) {
        sb.append(s.substring(j));
      }
      result = sb.toString();
    }
    else {
      result = s;
    }
    return result;
  }

  /**
   * Chars equal.
   *
   * @param a char
   * @param b char
   * @param ignoreCase boolean
   * @return true, if successful
   */
  public static boolean charsEqual(char a, char b, boolean ignoreCase) {
    return ignoreCase ? charsEqualIgnoreCase(a, b) : a == b;
  }
  
  /**
   * Chars equal ignore case.
   *
   * @param a char
   * @param b char
   * @return true, if successful
   */
  public static boolean charsEqualIgnoreCase(final char a, final char b) {
    return (a == b) || (toUpperCase(a) == toUpperCase(b)) ||
        (toLowerCase(a) == toLowerCase(b));
  }

  /**
   * To upper case.
   *
   * @param a char
   * @return the char
   */
  public static char toUpperCase(final char a) {
    if (a < 'a') {
      return a;
    }
    if (a >= 'a' && a <= 'z') {
      return (char) (a + -32);
    }
    return Character.toUpperCase(a);
  }

  /**
   * To lower case.
   *
   * @param a char
   * @return the char
   */
  public static char toLowerCase(final char a) {
    if (a < 'A' || a >= 'a' && a <= 'z') {
      return a;
    }
    if (a >= 'A' && a <= 'Z') {
      return (char) (a + 32);
    }
    return Character.toLowerCase(a);
  }

  /**
   * To title string.
   *
   * @param s String
   * @return the string
   */
  public static String toTitleString(final String s) {
    if (isEmptyString(s)) {
      return s;
    }

    char[] array = s.toCharArray();
    int len = array.length;
    int idx;

    // busca la primera letra
    for (idx = 0; idx < len && !Character.isLetter(array[idx]); idx++);

    if (idx < len) {
      array[idx] = Character.toUpperCase(array[idx]);
    }

    for (; idx < len; idx++) {
      if (array[idx] == LF || array[idx] == '.') {
        // busca la primera letra después de un salto o punto
        for (; idx < len && !Character.isLetter(array[idx]); idx++);

        if (idx < len) {
          array[idx] = Character.toUpperCase(array[idx]);
        }
      }
    }

    return new String(array);
  }

  /**
   * Capitalize string.
   *
   * @param str String
   * @return the string
   */
  public static String capitalizeString(final String str) {
    return capitalizeString(str, null);
  }

  /**
   * Capitalize string.
   *
   * @param s String
   * @param delimiters char[]
   * @return the string
   */
  public static String capitalizeString(final String s, final char... delimiters) {
    final int delimLen = delimiters == null ? -1 : delimiters.length;
    if (isEmptyString(s) || delimLen == 0) {
      return s;
    }
    final char[] buffer = s.toCharArray();
    boolean capitalizeNext = true;
    for (int i = 0; i < buffer.length; i++) {
      final char ch = buffer[i];
      if (isDelimiter(ch, delimiters)) {
        capitalizeNext = true;
      }
      else if (capitalizeNext) {
        buffer[i] = Character.toTitleCase(ch);
        capitalizeNext = false;
      }
    }
    return new String(buffer);
  }

  /**
   * Capitalize string fully.
   *
   * @param s String
   * @return the string
   */
  public static String capitalizeStringFully(String s) {
    return capitalizeStringFully(s, null);
  }

  /**
   * Capitalize string fully.
   *
   * @param s String
   * @param delimiters char[]
   * @return the string
   */
  public static String capitalizeStringFully(String s, final char... delimiters) {
    final int delimLen = delimiters == null ? -1 : delimiters.length;
    if (isEmptyString(s) || delimLen == 0) {
      return s;
    }
    s = s.toLowerCase();
    return capitalizeString(s, delimiters);
  }

  /**
   * Uncapitalize string.
   *
   * @param s String
   * @return the string
   */
  public static String uncapitalizeString(final String s) {
    return uncapitalizeString(s, null);
  }

  /**
   * Uncapitalize string.
   *
   * @param s String
   * @param delimiters char[]
   * @return the string
   */
  public static String uncapitalizeString(final String s, final char... delimiters) {
    final int delimLen = delimiters == null ? -1 : delimiters.length;
    if (isEmptyString(s) || delimLen == 0) {
      return s;
    }
    final char[] buffer = s.toCharArray();
    boolean uncapitalizeNext = true;
    for (int i = 0; i < buffer.length; i++) {
      final char ch = buffer[i];
      if (isDelimiter(ch, delimiters)) {
        uncapitalizeNext = true;
      }
      else if (uncapitalizeNext) {
        buffer[i] = Character.toLowerCase(ch);
        uncapitalizeNext = false;
      }
    }
    return new String(buffer);
  }

  /**
   * Checks if is lower case.
   *
   * @param str the str
   * @return true, if is lower case
   * @since 0.3
   */
  public static boolean isLowerCase(final String str) {
    if (isEmptyString(str)) 
      return false;
    final int sz = str.length();
    for (int i = 0; i < sz; i++) {
      if (!Character.isLowerCase(str.charAt(i))) 
        return false;
    }
    return true;
  }

  /**
   * Checks if is lower case.
   *
   * @param str the str
   * @return true, if is lower case
   * @since 0.3
   */
  public static boolean isLowerCase2(final String str) {
    if (isEmptyString(str))
      return false;
    final int sz = str.length();
    for (int i = 0; i < sz; i++) {
      final char ch = str.charAt(i);
      if (Character.isLetter(ch) && !Character.isLowerCase(ch))
        return false;
    }
    return true;
  }
  
  /**
   * Checks if is upper case.
   *
   * @param str the str
   * @return true, if is upper case
   * @since 0.3
   */
  public static boolean isUpperCase(final String str) {
    if (isEmptyString(str))
      return false;
    final int sz = str.length();
    for (int i = 0; i < sz; i++) {
      if (!Character.isUpperCase(str.charAt(i)))
        return false;
    }
    return true;
  }

  /**
   * Checks if is upper case.
   *
   * @param str the str
   * @return true, if is upper case
   * @since 0.3
   */
  public static boolean isUpperCase2(final String str) {
    if (isEmptyString(str))
      return false;
    final int sz = str.length();
    for (int i = 0; i < sz; i++) {
      final char ch = str.charAt(i);
      if (Character.isLetter(ch) && !Character.isUpperCase(ch))
        return false;
    }
    return true;
  }

  /**
   * Checks if is title case.
   *
   * @param str the str
   * @return true, if is title case
   * @since 0.3
   */
  public static boolean isTitleCase(final String str) {
    if (isEmptyString(str))
      return false;

    char[] array = str.toCharArray();
    int len = array.length;
    int idx;

    // busca la primera letra
    for (idx = 0; idx < len && !Character.isLetter(array[idx]); idx++);

    return (idx < len) ? Character.isUpperCase(array[idx]) : false;
  }
  
  /**
   * Checks if is delimiter.
   *
   * @param ch char
   * @param delimiters char[]
   * @return true, if is delimiter
   */
  private static boolean isDelimiter(final char ch, final char[] delimiters) {
    if (delimiters == null) {
      return Character.isWhitespace(ch);
    }
    for (final char delimiter : delimiters) {
      if (ch == delimiter) {
        return true;
      }
    }
    return false;
  }

  /**
   * Fix size string.
   *
   * @param size the size
   * @param alignLeft the align left
   * @param filler the filler
   * @param value the value
   * @return the string
   */
  public static String fixSizeString(int size, boolean alignLeft, char filler, long value) {
    return fixSizeString(size, alignLeft, filler, String.valueOf(value));
  }

  /**
   * Fix size string.
   *
   * @param size the size
   * @param alignLeft the align left
   * @param s the s
   * @return the string
   */
  public static String fixSizeString(int size, boolean alignLeft, String s) {
    return fixSizeString(size, alignLeft, ' ', s);
  }

  /**
   * Fix size string.
   *
   * @param size the size
   * @param alignLeft the align left
   * @param filler the filler
   * @param s the s
   * @return the string
   */
  public static String fixSizeString(int size, boolean alignLeft, char filler, String s) {
    if (s.length() < size) {
      final StringBuilder sb = new StringBuilder(size);
      if (alignLeft) {
        sb.append(s);
      }
      for (int i = 0; i < size - s.length(); i++) {
        sb.append(filler);
      }
      if (!alignLeft) {
        sb.append(s);
      }
      return sb.toString();
    }
    return alignLeft ? s.substring(0, size) : s.substring(s.length() - size, s.length());
  }
  
  /**
   * Compare version numbers.
   *
   * @param v1 the version1
   * @param v2 the version2
   * @return the int
   * @since 0.3
   */
  @SuppressWarnings("nls")
  public static int compareVersionNumbers(String v1, String v2) {
    if (v1 == null && v2 == null) {
      return 0;
    }
    if (v1 == null) {
      return -1;
    }
    if (v2 == null) {
      return 1;
    }

    String[] part1 = v1.split("[\\.\\_\\-]");
    String[] part2 = v2.split("[\\.\\_\\-]");

    int idx = 0;
    for (; idx < part1.length && idx < part2.length; idx++) {
      String p1 = part1[idx];
      String p2 = part2[idx];

      int cmp;
      if (p1.matches("\\d+") && p2.matches("\\d+")) {
        cmp = new Integer(p1).compareTo(new Integer(p2));
      }
      else {
        cmp = part1[idx].compareTo(part2[idx]);
      }
      if (cmp != 0)
        return cmp;
    }

    if (part1.length == part2.length) {
      return 0;
    }
    else if (part1.length > idx) {
      return 1;
    }
    else {
      return -1;
    }
  }
  // ---------------------------------------------------------------------------

  // --- File ------------------------------------------------------------------
  /**
   * The Unix directory separator character.
   */
  public static final char    DIR_SEPARATOR_UNIX         = '/';

  /**
   * The Windows directory separator character.
   */
  public static final char    DIR_SEPARATOR_WINDOWS      = '\\';

  /**
   * The system directory separator character.
   */
  public static final char    DIR_SEPARATOR              = File.separatorChar;

  /**
   * The system directory separator String.
   */
  public static final String  DIR_SEPARATOR_STRING       = File.separator;

  /**
   * The extension separator character.
   */
  public static final char    EXTENSION_SEPARATOR        = '.';

  /**
   * The extension separator String.
   */
  public static final String  EXTENSION_SEPARATOR_STRING = Character.toString(EXTENSION_SEPARATOR);

  /**
   * The Unix line separator string.
   */
  public static final String  LINE_SEPARATOR_UNIX        = LF_STRING;

  /**
   * The Windows line separator string.
   */
  public static final String  LINE_SEPARATOR_WINDOWS     = CRLF;

  /**
   * The Mac line separator string.
   */
  public static final String  LINE_SEPARATOR_MAC         = CR_STRING;

  /**
   * The system line separator string.
   */
  public static final String  LINE_SEPARATOR             = getLineSeparator();

  /** The Constant FILE_SYSTEM_ROOTS. */
  private static final File[] FILE_SYSTEM_ROOTS          = listRoots();
  
  /**
   * File_system_roots.
   *
   * @return the file[]
   */
  public static final File[] file_system_roots() { //Keep FindBugs happy
    return copyOf(FILE_SYSTEM_ROOTS);
  }

  /**
   * Gets the user directory path.
   *
   * @return the user directory path
   */
  public static String getUserDirectoryPath() {
    return System.getProperty("user.home"); //$NON-NLS-1$
  }

  /**
   * Gets the user directory.
   *
   * @return the user directory
   */
  public static File getUserDirectory() {
    return new File(getUserDirectoryPath());
  }

  /**
   * List roots.
   *
   * @return the file[]
   */
  public static File[] listRoots() {
    return File.listRoots();
  }

  /**
   * Testea si el archivo es una raiz de sistema de archivos. A particular Java
   * platform may support zero or more hierarchically-organized file systems.
   * Each file system has a root directory from which all other files in that
   * file system can be reached. Windows platforms, for example, have a root
   * directory for each active drive; UNIX platforms have a single root
   * directory, namely "/". The set of available filesystem roots is affected by
   * various system-level operations such the insertion or ejection of removable
   * media and the disconnecting or unmounting of physical or virtual disk
   * drives.
   *
   * @param file File
   * @return boolean
   */
  public static boolean isFileSystemRoot(final File file) {
    for (File f : FILE_SYSTEM_ROOTS) {
      if (f.getPath().equals(file.getPath())) {
        return true;
      }
    }
    return false;
  }

  /**
   * Checks if is uRL.
   *
   * @param s the s
   * @return true, if is uRL
   */
  public static boolean isURL(final String s) {
    boolean result = false;
    File file = new File(s);
    URL url = null;
    if (!file.exists()) {
      try {
        url = new URL(s);
      }
      catch (Exception ex) {
        //ignored
      }
      if (url != null) {
        result = true;
      }
    }
    return result;
  }

  /**
   * Gets the file separator.
   *
   * @return the file separator
   */
  public static String getFileSeparator() {
    return File.separator;
  }

  /**
   * Gets the file separator char.
   *
   * @return the file separator char
   */
  public static char getFileSeparatorChar() {
    return File.separatorChar;
  }

  /**
   * Gets the file name.
   *
   * @param filename the filename
   * @return the file name
   */
  public static String getFileName(final String filename) {
    if (filename == null) {
      return null;
    }

    int index = indexOfLastFileSeparator(filename);

    return filename.substring(index + 1);
  }

  /**
   * Gets the file base name.
   *
   * @param filename the filename
   * @return the file base name
   */
  public static String getFileBaseName(final String filename) {
    return removeFileExtension(getFileName(filename));
  }

  /**
   * Gets the file extension.
   *
   * @param filename the filename
   * @return the file extension
   */
  public static String getFileExtension(final String filename) {
    if (filename == null) {
      return null;
    }

    int index = indexOfFileExtension(filename);
    if (index == INDEX_NOT_FOUND) {
      return EMPTY_STRING;
    }

    return filename.substring(index + 1);
  }

  /**
   * Index of last file separator.
   *
   * @param filename String
   * @return int
   */
  public static int indexOfLastFileSeparator(final String filename) {
    if (filename == null) {
      return INDEX_NOT_FOUND;
    }

    int lastUnixPos = filename.lastIndexOf(DIR_SEPARATOR_UNIX);
    int lastWindowsPos = filename.lastIndexOf(DIR_SEPARATOR_WINDOWS);

    return Math.max(lastUnixPos, lastWindowsPos);
  }

  /**
   * Index of file extension.
   *
   * @param filename String
   * @return int
   */
  public static int indexOfFileExtension(final String filename) {
    if (filename == null) {
      return INDEX_NOT_FOUND;
    }

    int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);
    int lastSeparator = indexOfLastFileSeparator(filename);

    return lastSeparator > extensionPos ? INDEX_NOT_FOUND : extensionPos;
  }

  /**
   * Removes the file extension.
   *
   * @param filename String
   * @return String
   */
  public static String removeFileExtension(final String filename) {
    if (filename == null) {
      return null;
    }

    int index = indexOfFileExtension(filename);
    if (index == INDEX_NOT_FOUND) {
      return filename;
    }

    return filename.substring(0, index);
  }

  /**
   * Checks if is file extension.
   *
   * @param filename String
   * @param extension String
   * @return true, if is file extension
   */
  public static boolean isFileExtension(final String filename, final String extension) {
    if (filename == null) {
      return false;
    }

    if (isEmptyString(extension)) {
      return indexOfFileExtension(filename) == INDEX_NOT_FOUND;
    }

    String fileExt = getFileExtension(filename);
    return fileExt.equals(extension);
  }

  /**
   * Checks if is file extension.
   *
   * @param filename String
   * @param extensions String[]
   * @return true, if is file extension
   */
  public static boolean isFileExtension(final String filename, final String[] extensions) {
    if (filename == null) {
      return false;
    }

    if (isEmpty(extensions)) {
      return indexOfFileExtension(filename) == INDEX_NOT_FOUND;
    }

    String fileExt = getFileExtension(filename);
    for (String extension : extensions) {
      if (fileExt.equals(extension)) {
        return true;
      }
    }

    return false;
  }

  /**
   * File separators to unix.
   *
   * @param path String
   * @return String
   */
  public static String fileSeparatorsToUnix(String path) {
    if (path == null || path.indexOf(DIR_SEPARATOR_WINDOWS) == INDEX_NOT_FOUND) {
      return path;
    }
    return path.replace(DIR_SEPARATOR_WINDOWS, DIR_SEPARATOR_UNIX);
  }

  /**
   * File separators to windows.
   *
   * @param path String
   * @return String
   */
  public static String fileSeparatorsToWindows(String path) {
    if (path == null || path.indexOf(DIR_SEPARATOR_UNIX) == INDEX_NOT_FOUND) {
      return path;
    }
    return path.replace(DIR_SEPARATOR_UNIX, DIR_SEPARATOR_WINDOWS);
  }

  /**
   * File separators to system.
   *
   * @param path String
   * @return String
   */
  public static String fileSeparatorsToSystem(String path) {
    if (path == null) {
      return null;
    }
    if (Platform.isWindows) {
      return fileSeparatorsToWindows(path);
    }
    return fileSeparatorsToUnix(path);
  }
  // ---------------------------------------------------------------------------

  // --- Object ----------------------------------------------------------------
  /**
   * Checks if is empty.
   *
   * @param <T> the generic type
   * @param arr the arr
   * @return true, if is empty
   */
  public static <T> boolean isEmpty(final T[] arr) {
    return arr == null || arr.length == 0;
  }

  /**
   * Checks if is not empty.
   *
   * @param <T> the generic type
   * @param arr the arr
   * @return true, if is not empty
   */
  public static <T> boolean isNotEmpty(final T[] arr) {
    return !isEmpty(arr);
  }

  /**
   * To string.
   *
   * @param <T> the generic type
   * @param arg the arg
   * @return the string
   */
  public static <T> String toString(final T arg) {
    return toString(arg, EMPTY_STRING);
  }
  
  /**
   * To string.
   *
   * @param <T> the generic type
   * @param arg the arg
   * @param nullStr the null str
   * @return the string
   */
  public static <T> String toString(final T arg, final String nullStr) {
    return arg == null ? nullStr : arg.toString();
  }

  /**
   * Equals.
   *
   * @param <T> the generic type
   * @param arg1 the arg1
   * @param arg2 the arg2
   * @return true, if successful
   */
  public static <T> boolean equals(T arg1, T arg2) {
    if (arg1 == arg2) {
      return true;
    }
    if (arg1 == null || arg2 == null) {
      return arg1 == arg2;
    }
    if (arg1 instanceof Object[] && arg2 instanceof Object[]) {
      return Arrays.equals((Object[]) arg1, (Object[]) arg2);
    }
    if (arg1 instanceof CharSequence && arg2 instanceof CharSequence) {
      return equals((CharSequence) arg1, (CharSequence) arg2, true);
    }
    return arg1.equals(arg2);
  }

  /**
   * Equals.
   *
   * @param <T> the generic type
   * @param arr1 the arr1
   * @param arr2 the arr2
   * @return true, if successful
   */
  public static <T> boolean equals(T[] arr1, T[] arr2) {
    if (arr1 == null || arr2 == null) {
      return arr1 == arr2;
    }
    return Arrays.equals(arr1, arr2);
  }

  /**
   * Equals.
   *
   * @param s1 CharSequence
   * @param s2 CharSequence
   * @return true, if successful
   */
  public static boolean equals(final CharSequence s1, final CharSequence s2) {
    return equals(s1, s2, true);
  }

  /**
   * Equals.
   *
   * @param s1 CharSequence
   * @param s2 CharSequence
   * @param caseSensitive boolean
   * @return true, if successful
   */
  public static boolean equals(final CharSequence s1, final CharSequence s2, final boolean caseSensitive) {
    if (s1 == s2) {
      return true;
    }
    if (s1 == null || s2 == null) {
      return false;
    }
    int to = 0;
    int po = 0;
    if (s1.length() != s2.length()) {
      return false;
    }
    for (int len = s1.length(); len-- > 0; ) {
      char c1 = s1.charAt(to++);
      char c2 = s2.charAt(po++);
      if (c1 != c2 &&
          (caseSensitive || !charsEqualIgnoreCase(c1, c2))) {
        return false;
      }
    }
    return true;
  }

  /**
   * Hash code.
   *
   * @param o Object
   * @return int
   */
  public static int hashCode(final Object o) {
    return (o instanceof Object[]) ? hashCode( (Object[]) o) :
        (o == null ? 0 : o.hashCode());
  }

  /**
   * Hash code.
   *
   * @param obj1 Object
   * @param obj2 Object
   * @return int
   */
  public static int hashCode(final Object obj1, final Object obj2) {
    return hashCode(obj1) ^ hashCode(obj2);
  }

  /**
   * Hash code.
   *
   * @param values Object[]
   * @return int
   * @see java.util.Arrays#hashCode(Object[])
   */
  public static int hashCode(final Object... values) {
    return Arrays.hashCode(values);
  }

  /**
   * Compare.
   *
   * @param arg1 the arg1
   * @param arg2 the arg2
   * @return the int
   * @since 0.3
   */
  public static int compare(byte arg1, byte arg2) {
    return arg1 < arg2 ? -1 : arg1 == arg2 ? 0 : 1;
  }

  /**
   * Compare.
   *
   * @param arg1 the arg1
   * @param arg2 the arg2
   * @return the int
   * @since 0.3
   */
  public static int compare(boolean arg1, boolean arg2) {
    return arg1 == arg2 ? 0 : arg1 ? 1 : -1;
  }

  /**
   * Compare.
   *
   * @param arg1 the arg1
   * @param arg2 the arg2
   * @return the int
   * @since 0.3
   */
  public static int compare(int arg1, int arg2) {
    return arg1 < arg2 ? -1 : arg1 == arg2 ? 0 : 1;
  }

  /**
   * Compare.
   *
   * @param arg1 the arg1
   * @param arg2 the arg2
   * @return the int
   * @since 0.3
   */
  public static int compare(long arg1, long arg2) {
    return arg1 < arg2 ? -1 : arg1 == arg2 ? 0 : 1;
  }

  /**
   * Compare.
   *
   * @param arg1 the arg1
   * @param arg2 the arg2
   * @return the int
   * @since 0.3
   */
  public static int compare(double arg1, double arg2) {
    return arg1 < arg2 ? -1 : arg1 == arg2 ? 0 : 1;
  }

  /**
   * Compare.
   *
   * @param arg1 the arg1
   * @param arg2 the arg2
   * @return the int
   * @since 0.3
   */
  public static int compare(byte[] arg1, byte[] arg2) {
    if (arg1 == arg2)
      return 0;
    if (arg1 == null)
      return 1;
    if (arg2 == null)
      return -1;

    if (arg1.length > arg2.length)
      return 1;
    if (arg1.length < arg2.length)
      return -1;

    for (int i = 0; i < arg1.length; i++) {
      if (arg1[i] > arg2[i])
        return 1;
      else if (arg1[i] < arg2[i])
        return -1;
    }
    return 0;
  }

  /**
   * Compare.
   *
   * @param <T> the generic type
   * @param arg1 the arg1
   * @param arg2 the arg2
   * @return the int
   * @since 0.3
   */
  public static <T extends Comparable<T>> int compare(final T arg1, final T arg2) {
    if (arg1 == null)
      return arg2 == null ? 0 : -1;
    if (arg2 == null)
      return 1;
    return arg1.compareTo(arg2);
  }

  /**
   * Compare.
   *
   * @param <T> the generic type
   * @param arg1 the arg1
   * @param arg2 the arg2
   * @param notNullComparator the not null comparator
   * @return the int
   * @since 0.3
   */
  public static <T> int compare(final T arg1, final T arg2, final Comparator<T> notNullComparator) {
    if (arg1 == null)
      return arg2 == null ? 0 : -1;
    if (arg2 == null)
      return 1;
    return notNullComparator.compare(arg1, arg2);
  }
  
  /**
   * Clone.
   *
   * @param <T> the generic type
   * @param array the array
   * @return the t[]
   */
  public static <T> T[] clone(final T[] array) {
    if (array == null) {
      return null;
    }
    return array.clone();
  }
  
  /**
   * Copy of.
   *
   * @param <T> the generic type
   * @param array the array
   * @return the t[]
   */
  public static <T> T[] copyOf(final T[] array) {
    if (array == null) {
      return null;
    }
    return Arrays.copyOf(array, array.length);
  }

  /**
   * Copy of.
   *
   * @param array the array
   * @return the char[]
   * @since 0.3
   */
  public static char[] copyOf(final char[] array) {
    if (array == null) {
      return null;
    }
    return Arrays.copyOf(array, array.length);
  }
  // ---------------------------------------------------------------------------

}
