content: /*
   * Copyright (c) 2009. All rights reserved.
   */
  package ro.isdc.wro.util;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  
  /**
   * StringUtils Utility class. This class contains Utility methods for
   * manipulating strings. Inspired from commons & spring.
   *
   * @author Alex Objelean
   * @created Created on Feb 27, 2009
   */
  public final class StringUtils {
    /**
     * Folder separator.
     */
    private static final String FOLDER_SEPARATOR = "/";
  
    /**
     * Top path separator.
     */
    private static final String TOP_PATH = "..";
  
    /**
     * Current path character.
     */
    private static final String CURRENT_PATH = ".";
  
    /**
     * Windows folder separator.
     */
    private static final String WINDOWS_FOLDER_SEPARATOR = "\\";
  
    /**
     * Private constructor to prevent instantiation.
     */
    private StringUtils() {}
  
    /**
     * Normalize the path by suppressing sequences like "path/.." and inner simple
     * dots.
     * <p>
     * The result is convenient for path comparison. For other uses, notice that
     * Windows separators ("\") are replaced by simple slashes.
     *
     * @param path
     *          the original path
     * @return the normalized path
     */
    public static String cleanPath(final String path) {
      if (path == null) {
        return null;
      }
      String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
  
      // Strip prefix from path to analyze, to not treat it as part of the
      // first path element. This is necessary to correctly parse paths like
      // "file:core/../core/io/Resource.class", where the ".." should just
      // strip the first "core" directory while keeping the "file:" prefix.
      final int prefixIndex = pathToUse.indexOf(":");
      String prefix = "";
      if (prefixIndex != -1) {
        prefix = pathToUse.substring(0, prefixIndex + 1);
        pathToUse = pathToUse.substring(prefixIndex + 1);
      }
      if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
        prefix = prefix + FOLDER_SEPARATOR;
        pathToUse = pathToUse.substring(1);
      }
  
      final String[] pathArray = delimitedListToStringArray(pathToUse,
          FOLDER_SEPARATOR);
      final List<String> pathElements = new LinkedList<String>();
      int tops = 0;
  
      for (int i = pathArray.length - 1; i >= 0; i--) {
        final String element = pathArray[i];
        if (CURRENT_PATH.equals(element)) {
          // Points to current directory - drop it.
        } else if (TOP_PATH.equals(element)) {
          // Registering top path found.
          tops++;
        } else {
          if (tops > 0) {
            // Merging path element with element corresponding to top path.
            tops--;
          } else {
            // Normal path element found.
            pathElements.add(0, element);
          }
        }
      }
  
      // Remaining top paths need to be retained.
      for (int i = 0; i < tops; i++) {
        pathElements.add(0, TOP_PATH);
      }
  
      return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
    }
  
    /**
     * Replace all occurrences of a substring within a string with another string.
     *
     * @param inString
     *          String to examine
     * @param oldPattern
     *          String to replace
     * @param newPattern
     *          String to insert
     * @return a String with the replacements
     */
    private static String replace(final String inString, final String oldPattern,
        final String newPattern) {
      if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {
        return inString;
      }
      final StringBuffer sbuf = new StringBuffer();
      // output StringBuffer we'll build up
      int pos = 0; // our position in the old string
      int index = inString.indexOf(oldPattern);
      // the index of an occurrence we've found, or -1
      final int patLen = oldPattern.length();
      while (index >= 0) {
        sbuf.append(inString.substring(pos, index));
        sbuf.append(newPattern);
        pos = index + patLen;
        index = inString.indexOf(oldPattern, pos);
      }
      sbuf.append(inString.substring(pos));
      // remember to append any characters to the right of a match
      return sbuf.toString();
    }
  
    /**
     * Convenience method to return a Collection as a delimited (e.g. CSV) String.
     * E.g. useful for <code>toString()</code> implementations.
     *
     * @param coll
     *          the Collection to display
     * @param delim
     *          the delimiter to use (probably a ",")
     * @return the delimited String
     */
    private static String collectionToDelimitedString(final Collection<String> coll,
        final String delim) {
      return collectionToDelimitedString(coll, delim, "", "");
    }
  
    /**
     * Convenience method to return a Collection as a delimited (e.g. CSV) String.
     * E.g. useful for <code>toString()</code> implementations.
     *
     * @param coll
     *          the Collection to display
     * @param delim
     *          the delimiter to use (probably a ",")
     * @param prefix
     *          the String to start each element with
     * @param suffix
     *          the String to end each element with
     * @return the delimited String
     */
    private static String collectionToDelimitedString(final Collection<String> coll,
        final String delim, final String prefix, final String suffix) {
      if (coll == null || coll.isEmpty()) {
        return "";
      }
      final StringBuffer sb = new StringBuffer();
      final Iterator<String> it = coll.iterator();
      while (it.hasNext()) {
        sb.append(prefix).append(it.next()).append(suffix);
        if (it.hasNext()) {
          sb.append(delim);
        }
      }
      return sb.toString();
    }
  
    /**
     * Take a String which is a delimited list and convert it to a String array.
     * <p>
     * A single delimiter can consists of more than one character: It will still
     * be considered as single delimiter string, rather than as bunch of potential
     * delimiter characters - in contrast to <code>tokenizeToStringArray</code>.
     *
     * @param str
     *          the input String
     * @param delimiter
     *          the delimiter between elements (this is a single delimiter, rather
     *          than a bunch individual delimiter characters)
     * @return an array of the tokens in the list
     * @see #tokenizeToStringArray
     */
    private static String[] delimitedListToStringArray(final String str,
        final String delimiter) {
      return delimitedListToStringArray(str, delimiter, null);
    }
  
    /**
     * Take a String which is a delimited list and convert it to a String array.
     * <p>
     * A single delimiter can consists of more than one character: It will still
     * be considered as single delimiter string, rather than as bunch of potential
     * delimiter characters - in contrast to <code>tokenizeToStringArray</code>.
     *
     * @param str
     *          the input String
     * @param delimiter
     *          the delimiter between elements (this is a single delimiter, rather
     *          than a bunch individual delimiter characters)
     * @param charsToDelete
     *          a set of characters to delete. Useful for deleting unwanted line
     *          breaks: e.g. "\r\n\f" will delete all new lines and line feeds in
     *          a String.
     * @return an array of the tokens in the list
     * @see #tokenizeToStringArray
     */
    private static String[] delimitedListToStringArray(final String str,
        final String delimiter, final String charsToDelete) {
      if (str == null) {
        return new String[0];
      }
      if (delimiter == null) {
        return new String[] { str };
      }
      final List<String> result = new ArrayList<String>();
      if ("".equals(delimiter)) {
        for (int i = 0; i < str.length(); i++) {
          result.add(deleteAny(str.substring(i, i + 1), charsToDelete));
        }
      } else {
        int pos = 0;
        int delPos = 0;
        while ((delPos = str.indexOf(delimiter, pos)) != -1) {
          result.add(deleteAny(str.substring(pos, delPos), charsToDelete));
          pos = delPos + delimiter.length();
        }
        if (str.length() > 0 && pos <= str.length()) {
          // Add rest of String, but not in case of empty input.
          result.add(deleteAny(str.substring(pos), charsToDelete));
        }
      }
      return toStringArray(result);
    }
  
    /**
     * Copy the given Collection into a String array. The Collection must contain
     * String elements only.
     *
     * @param collection
     *          the Collection to copy
     * @return the String array (<code>null</code> if the passed-in Collection
     *         was <code>null</code>)
     */
    private static String[] toStringArray(final Collection<?> collection) {
      if (collection == null) {
        return null;
      }
      return collection.toArray(new String[collection.size()]);
    }
  
    /**
     * Delete any character in a given String.
     *
     * @param inString
     *          the original String
     * @param charsToDelete
     *          a set of characters to delete. E.g. "az\n" will delete 'a's, 'z's
     *          and new lines.
     * @return the resulting String
     */
    private static String deleteAny(final String inString,
        final String charsToDelete) {
      if (!hasLength(inString) || !hasLength(charsToDelete)) {
        return inString;
      }
      final StringBuffer out = new StringBuffer();
      for (int i = 0; i < inString.length(); i++) {
        final char c = inString.charAt(i);
        if (charsToDelete.indexOf(c) == -1) {
          out.append(c);
        }
      }
      return out.toString();
    }
  
    /**
     * Check that the given CharSequence is neither <code>null</code> nor of
     * length 0. Note: Will return <code>true</code> for a CharSequence that
     * purely consists of whitespace.
     * <p>
     *
     * <pre>
     * StringUtils.hasLength(null) = false
     * StringUtils.hasLength(&quot;&quot;) = false
     * StringUtils.hasLength(&quot; &quot;) = true
     * StringUtils.hasLength(&quot;Hello&quot;) = true
     * </pre>
     *
     * @param str
     *          the CharSequence to check (may be <code>null</code>)
     * @return <code>true</code> if the CharSequence is not null and has length
     * @see #hasText(String)
     */
    private static boolean hasLength(final CharSequence str) {
      return (str != null && str.length() > 0);
    }
  
    /**
     * Check that the given String is neither <code>null</code> nor of length 0.
     * Note: Will return <code>true</code> for a String that purely consists of
     * whitespace.
     *
     * @param str
     *          the String to check (may be <code>null</code>)
     * @return <code>true</code> if the String is not null and has length
     * @see #hasLength(CharSequence)
     */
    private static boolean hasLength(final String str) {
      return hasLength((CharSequence) str);
    }
  }
  