/*
 * StringCutter class.
 *
 * Copyright (C) 2007 SATOH Takayuki All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package ts.util.text;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.LinkedList;

/**
 * 文字列の分割を行うクラス。
 * <br>
 * 文字列を区切り文字列で複数に分割するためのメソッドを用意する。
 *
 * @author  佐藤隆之.
 * @version $Revision: 1.1.1.1 $, $Date: 2010-10-16 00:03:53 $
 */
public class StringCutter
{
  /**
   * デフォルトコンストラクタ。
   */
  protected StringCutter()
  {}

  /**
   * 文字列を、指定された区切り文字列で複数に分割する。
   * <br>
   * {@link java.util.StringTokenizer StringTokenizer}とは異なり、区切り文字列
   * が連続して現れた場合でも、その間の空文字列を１要素として数える。
   * また、区切り文字列は必ず要素と要素の間に存在するものとして、文頭または文末
   * に区切り文字列が存在する場合は、先頭又は末尾に空文字列の要素を付ける。
   * <br>
   * 区切り文字が空文字列の場合は、例外をスローする。
   *
   * @param  string 分割される文字列。
   * @param  delimiter 区切り文字列。
   * @return 分割した文字列を格納したリスト。
   * @throws IllegalArgumentException 区切り文字列が空文字列の場合。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public static List<String> split(String string, String delimiter)
  {
    assert (string != null) : "@param:string is null.";
    assert (delimiter != null) : "@param:delimiter is null.";

    final int delimLen = StringOperation.length(delimiter);
    if (delimLen == 0) {
      throw new IllegalArgumentException("@param:delimiter is empty.");
    }

    List<String> lst = new LinkedList<String>();

    StringSequence seq = new StringSequence(string, -1);
    StringSequence bgn = seq.copy();
    while (seq.next().validIndex()) {
      if (seq.startsWith(delimiter)) {
        lst.add(bgn.followingString(seq));
        seq.next(delimLen-1);
        bgn = seq.copy();
      }
    }
    lst.add(bgn.followingString());
    return lst;
  }

  /**
   * 文字列を、指定された区切り文字列（大文字小文字は無視）で複数に分割する。
   * <br>
   * {@link java.util.StringTokenizer StringTokenizer}とは異なり、区切り文字列
   * が連続して現れた場合でも、その間の空文字列を１要素として数える。
   * また、区切り文字列は必ず要素と要素の間に存在するものとして、文頭または文末
   * に区切り文字列が存在する場合は、先頭又は末尾に空文字列の要素を付ける。
   * <br>
   * 区切り文字が空文字列の場合は、例外をスローする。
   *
   * @param  string 分割される文字列。
   * @param  delimiter 区切り文字列。
   * @return 分割した文字列を格納したリスト。
   * @throws IllegalArgumentException 区切り文字列が空文字列の場合。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public static List<String> splitIgnoreCase(String string, String delimiter)
  {
    assert (string != null) : "@param:string is null.";
    assert (delimiter != null) : "@param:delimiter is null.";

    final int delimLen = StringOperation.length(delimiter);
    if (delimLen == 0) {
      throw new IllegalArgumentException("@param:delimiter is empty.");
    }
    delimiter = delimiter.toUpperCase();

    List<String> lst = new LinkedList<String>();

    StringSequence seq = new StringSequence(string.toUpperCase(), -1);
    StringSequence bgn = new StringSequence(string, -1);
    while (seq.next().validIndex()) {
      if (seq.startsWith(delimiter)) {
        lst.add(bgn.followingString(seq));
        seq.next(delimLen - 1);
        bgn.next(seq.index() - bgn.index());
      }
    }
    lst.add(bgn.followingString());
    return lst;
  }

  /**
   * 文字列を、指定された区切り文字列で複数に分割する。
   * <br>
   * 文字列中に、指定された文字列配列のいずれかが現れたら、それを区切り文字列と
   * して分割する。
   * <br>
   * {@link java.util.StringTokenizer StringTokenizer}とは異なり、区切り文字列
   * が連続して現れた場合でも、その間の空文字列を１要素として数える。
   * また、区切り文字列は必ず要素と要素の間に存在するものとして、文頭または文末
   * に区切り文字が現れた場合は、先頭又は末尾に空文字列の要素を付ける。
   * <br>
   * 区切り文字列の配列の中に空文字列の要素が存在した場合は、例外をスローする。
   *
   * @param  string 分割される文字列。
   * @param  delimiters 区切り文字列の配列。
   * @return 分割した文字列を格納したリスト。
   * @throws IllegalArgumentException 区切り文字列の配列に空文字列の要素が存在
   *           した場合。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public static List<String> split(String string, String[] delimiters)
  {
    assert (string != null) : "@param:string is null.";
    assert (delimiters != null) : "@param:delimiters is null.";

    String[] delims = createNewArrayWithSorting(delimiters);

    List<String> lst = new LinkedList<String>();

    StringSequence seq = new StringSequence(string, -1);
    StringSequence bgn = seq.copy();
    while (seq.next().validIndex()) {
      String delim = findStarting(seq.substring(), delims);
      if (delim != null) {
        lst.add(bgn.followingString(seq));
        seq.next(StringOperation.length(delim) - 1);
        bgn = seq.copy();
      }
    }
    lst.add(bgn.followingString());
    return lst;
  }

  /**
   * 文字列を、指定された区切り文字列（大文字小文字は無視）で複数に分割する。
   * <br>
   * 文字列中に、指定された文字列配列のいずれかが現れたら、それを区切り文字と
   * して分割する。
   * <br>
   * {@link java.util.StringTokenizer StringTokenizer}とは異なり、区切り文字列
   * が連続して現れた場合でも、その間の空文字列を１要素として数える。
   * また、区切り文字列は必ず要素と要素の間に存在するものとして、文頭または文末
   * に区切り文字が現れた場合は、先頭又は末尾に空文字列の要素を付ける。
   * <br>
   * 区切り文字列の配列の中に空文字列の要素が存在した場合は、例外をスローする。
   *
   * @param  string 分割される文字列。
   * @param  delimiters 区切り文字列の配列。
   * @return 分割した文字列を格納したリスト。
   * @throws IllegalArgumentException 区切り文字列の配列に空文字列の要素が存在
   *           した場合。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public static List<String> splitIgnoreCase(String string, String[] delimiters)
  {
    assert (string != null) : "@param:string is null.";
    assert (delimiters != null) : "@param:delimiters is null.";

    String[] delims = createUpperCaseArrayWithSorting(delimiters);

    List<String> lst = new LinkedList<String>();

    StringSequence seq = new StringSequence(string.toUpperCase(), -1);
    StringSequence bgn = new StringSequence(string, -1);
    while (seq.next().validIndex()) {
      String delim = findStarting(seq.substring(), delims);
      if (delim != null) {
        lst.add(bgn.followingString(seq));
        seq.next(StringOperation.length(delim) - 1);
        bgn.next(seq.index() - bgn.index());
      }
    }
    lst.add(bgn.followingString());
    return lst;
  }

  /**
   * 引数の文字列配列の要素をソートした、新しい配列を作成する。
   * <br>
   * 指定されたプレフィックスの中に、ヌルや空文字列が存在した場合は例外をスロー
   * する。
   *
   * @param  strArr 文字列配列。
   * @return 新たに作成された文字列配列。
   * @throws IllegalArgumentException プレフィックスの配列の中にヌルや空文字列
   *           の要素が存在した場合。
   */
  private static String[] createNewArrayWithSorting(String[] strArr)
  {
    String[] newArr = new String[strArr.length];
    for (int i=0; i<strArr.length; i++) {
      if (strArr[i] == null || StringOperation.isEmpty(strArr[i])) {
        throw new IllegalArgumentException(
          "@param:strArr has elements which are null or empty string.");
      }
      newArr[i] = strArr[i];
    }
    Arrays.sort(newArr);
    return newArr;
  }

  /**
   * 引数の文字列配列の要素を大文字に変換してソートした、新しい配列を作成する。
   * <br>
   * 指定されたプレフィックスの中に、ヌルや空文字列が存在した場合は例外をスロー
   * する。
   *
   * @param  strArr 文字列配列。
   * @return 新たに作成された文字列配列。
   * @throws IllegalArgumentException プレフィックスの配列の中にヌルや空文字列
   *           の要素が存在した場合。
   */
  private static String[] createUpperCaseArrayWithSorting(String[] strArr)
  {
    String[] newArr = new String[strArr.length];
    for (int i=0; i<strArr.length; i++) {
      if (strArr[i] == null || StringOperation.isEmpty(strArr[i])) {
        throw new IllegalArgumentException(
          "@param:strArr has elements which are null or empty string.");
      }
      newArr[i] = strArr[i].toUpperCase();
    }
    Arrays.sort(newArr);
    return newArr;
  }

  /**
   * 引数の文字列が、指定された複数のプレフィックスのいずれかと先頭が一致して
   * いるかどうかを判定し、そのプレフィックスを返す。
   * <br>
   * いずれのプレフィックスも一致しなかった場合はヌルを返す。
   *
   * @param  string 文字列。
   * @param  prefixes プレフィックスの配列。
   * @return 先頭が一致したプレフィックス。
   */
  private static String findStarting(String string, String[] prefixes)
  {
    for (int i=prefixes.length - 1; i>=0; i--) {
      int len = Math.min(string.length(), prefixes[i].length());
      String substr = string.substring(0, len);
      int cmp = substr.compareTo(prefixes[i]);
      if (cmp == 0) {
        return prefixes[i];
      }
      else if (cmp > 0) {
        break;
      }
    }
    return null;
  }
}

