/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.commons.lang;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * <p>文字のセット。
 * {@primary A set of characters.}</p>
 *
 * <p>このインスタンスは不変(immutable)ですが、サブクラスのインスタンスはそうでない可能性があります。
 * {@primary Instances are immutable, but instances of subclasses may not be.}</p>
 *
 * @author Henri Yandell
 * @author Stephen Colebourne
 * @author Phil Steitz
 * @author Pete Gieser
 * @author Gary Gregory
 * @translator 日置 聡
 * @status firstdraft
 * @update 2003/09/05
 * @since 1.0
 * @version $Id: CharSet.java,v 1.1.1.1 2004/02/13 10:02:05 hioki Exp $
 */
public class CharSet implements Serializable {

    /**  Lang version 2.0 のシリアリゼーションロック。
     * {@primary Serialization lock, Lang version 2.0.} */
    private static final long serialVersionUID = 5947847346149275958L;

    /** 
     * 文字が指定されていない CharSet。{@primary A CharSet defining no characters. }
     * @since 2.0
     */
    public static final CharSet EMPTY = new CharSet((String) null);

    /** 
     * ASCIIのアルファベット文字 "a-zA-Z" を定義した CharSet。
     * {@primary A CharSet defining ASCII alphabetic characters "a-zA-Z".}
     * @since 2.0
     */
    public static final CharSet ASCII_ALPHA = new CharSet("a-zA-Z");

    /** 
     * ASCIIのアルファベット文字 "a-z" を定義した CharSet。
     * {@primary A CharSet defining ASCII alphabetic characters "a-z".}
     * @since 2.0
     */
    public static final CharSet ASCII_ALPHA_LOWER = new CharSet("a-z");

    /** 
     * ASCIIのアルファベット文字 "A-Z" を定義した CharSet。
     * {@primary A CharSet defining ASCII alphabetic characters "A-Z".}
     * @since 2.0
     */
    public static final CharSet ASCII_ALPHA_UPPER = new CharSet("A-Z");

    /** 
     * ASCIIの数字を表す文字 "0-9" を定義した CharSet。
     * {@primary A CharSet defining ASCII alphabetic characters "0-9".}
     * @since 2.0
     */
    public static final CharSet ASCII_NUMERIC = new CharSet("0-9");

    /**
     * ファクトリにて使用される一般的な用法の Map。
     * 必要があればサブクラスにて一般的なパターンを追加することができます。
     * {@primary A Map of the common cases used in the factory.
     * Subclasses can add more common patterns if desired.}
     * @since 2.0
     */
    protected static final Map COMMON = new HashMap();
    
    static {
        COMMON.put(null, EMPTY);
        COMMON.put("", EMPTY);
        COMMON.put("a-zA-Z", ASCII_ALPHA);
        COMMON.put("A-Za-z", ASCII_ALPHA);
        COMMON.put("a-z", ASCII_ALPHA_LOWER);
        COMMON.put("A-Z", ASCII_ALPHA_UPPER);
        COMMON.put("0-9", ASCII_NUMERIC);
    }

    /** CharRange オブジェクトのセット。 {@primary The set of CharRange objects.} */
    private Set set = new HashSet();

    //-----------------------------------------------------------------------
    /**
     * <p>特有の文法を使用して新規 CharSet を生成するファクトリメソッドです。
     * {@primary Factory method to create a new CharSet using a special syntax.}</p>
     *
     * <ul>
     *  <li> <code>null</code> または空文字列("") - 文字を含まないセット
     *   {@primary <code>null</code> or empty string ("")
     * - set containing no characters}</li>
     *  <li> "a" のような単一の文字 - 指定された文字のみをもつセット
     *    {@primary Single character, such as "a"
     *  - set containing just that character}</li>
     *  <li> "a-e" のような複数の文字 - 指定された文字の間に位置する文字をもつセット
     *    {@primary Multi character, such as "a-e"
     *  - set containing characters from one character to the other}</li>
     *  <li> "^a" または "^a-e" のような否定条件 - 指定された文字以外の全ての文字をもつセット
     *    {@primary Negated, such as "^a" or "^a-e"
     *  - set containing all characters except those defined}</li>
     *  <li> "abe-g" のような組み合わせ - 個々の条件に該当する全ての文字をもつセット
     *    {@primary Combinations, such as "abe-g"
     *  - set containing all the characters from the individual sets}</li>
     * </ul>
     *
     * <p>適用の順番は以下のようになります:
     * {@primary The matching order is:}</p>
     * <ol>
     *  <li> "^a-e" のような複数の文字範囲の否定条件
     *      {@primary Negated multi character range, such as "^a-e"}
     *  <li> "a-e" のような複数の文字範囲の通常の指定
     *      {@primary Ordinary multi character range, such as "a-e"}
     *  <li> "^a" のような単一の文字の否定条件
     *      {@primary Negated single character, such as "^a"}
     *  <li> "a" のような単一の文字の通常の指定
     *      {@primary Ordinary single character, such as "a"}
     * </ol>
     * <p>指定の適用は左から右に行われます。
     * 1度適用できる記述が見つかると次の文字から再び適用できる記述を探します。
     * {@primary Matching works left to right. Once a match is found the
     * search starts again from the next character.}</p>
     *
     * <p>同じ文法で記述された同じ範囲が2度見つかった場合には1つだけ保持します。
     * 従って "a-ca-c" の記述は "a-c" の範囲のみを生成します。
     * {@primary If the same range is defined twice using the same syntax, only
     * one range will be kept.
     * Thus, "a-ca-c" creates only one range of "a-c".}</p>
     *
     * <p>開始と終了の文字の順番が正しくない場合、逆に置き換えられます。
     * 従って <code>a-e</code> の指定は <code>e-a</code> と等しくなります。
     * "a-e" と "e-a" は同等なため、 "a-ee-a" の記述は1つの範囲のみを生成します。
     * {@primary If the start and end of a range are in the wrong order,
     * they are reversed. Thus "a-e" is the same as "e-a".
     * As a result, "a-ee-a" would create only one range,
     * as the "a-e" and "e-a" are the same.}</p>
     *
     * <p>表現される文字のセットは指定された(複数の)範囲の集合となります。
     * The set of characters represented is the union of the specified ranges.}</p>
     *
     * <p>このメソッドで返される全ての CharSet オブジェクトは不変(immutable)です。
     * {@primary All CharSet objects returned by this method will be immutable.}</p>
     *
     * @param setStr  セットを記述した String、null を許可します
     * {@primary the String describing the set, may be null}
     * @return a CharSet インスタンス
     * {@primary a CharSet instance}
     * @since 2.0
     */
    public static CharSet getInstance(String setStr) {
        Object set = COMMON.get(setStr);
        if (set != null) {
            return (CharSet) set;
        }
        return new CharSet(setStr);
    }

    //-----------------------------------------------------------------------
    /**
     * <p>セットの文法を使用して新規 CharSet を生成します。
     * {@primary Constructs a new CharSet using the set syntax.}</p>
     *
     * @param setStr  セットを定義する String、 null を許可します。
     * {@primary the String describing the set, may be null}
     * @since 2.0
     */
    protected CharSet(String setStr) {
        super();
        add(setStr);
    }

    /**
     * <p>セットの文法を使用して新規 CharSet を生成します。
     * 各文字列はセット内でマージされます。
     * {@primary Constructs a new CharSet using the set syntax.
     * Each string is merged in with the set.}</p>
     *
     * @param set  セットの初期化でマージされる文字列の配列
     * {@primary Strings to merge into the initial set}
     * @throws NullPointerException set が <code>null</code> だった場合
     * {@primary if set is <code>null</code>}
     */
    protected CharSet(String[] set) {
        super();
        int sz = set.length;
        for (int i = 0; i < sz; i++) {
            add(set[i]);
        }
    }

    //-----------------------------------------------------------------------
    /**
     * <p><code>CharSet</code> に定義文字列を追加します。
     * {@primary Add a set definition string to the <code>CharSet</code>.}</p>
     *
     * @param str  セットを定義する文字列
     * {@primary set definition string}
     */
    protected void add(String str) {
        if (str == null) {
            return;
        }

        int len = str.length();
        int pos = 0;
        while (pos < len) {
            int remainder = (len - pos);
            if (remainder >= 4 && str.charAt(pos) == '^' && str.charAt(pos + 2) == '-') {
                // negated range
                set.add(new CharRange(str.charAt(pos + 1), str.charAt(pos + 3), true));
                pos += 4;
            } else if (remainder >= 3 && str.charAt(pos + 1) == '-') {
                // range
                set.add(new CharRange(str.charAt(pos), str.charAt(pos + 2)));
                pos += 3;
            } else if (remainder >= 2 && str.charAt(pos) == '^') {
                // negated char
                set.add(new CharRange(str.charAt(pos + 1), true));
                pos += 2;
            } else {
                // char
                set.add(new CharRange(str.charAt(pos)));
                pos += 1;
            }
        }
    }

    //-----------------------------------------------------------------------
    /**
     * <p>内部の CharRange オブジェクトの配列を取得します。
     * {@primary Gets the internal set as an array of CharRange objects.}</p>
     *
     * @return 不変(immutable)な CharRange オブジェクトの配列
     * {@primary an array of immutable CharRange objects}
     * @since 2.0
     */
    public CharRange[] getCharRanges() {
        return (CharRange[]) set.toArray(new CharRange[set.size()]);
    }

    //-----------------------------------------------------------------------
    /**
     * <p>指定された文字 <code>ch</code> がこの <code>CharSet</code>
     * に含まれるかどうかを評価します。
     * {@primary Does the <code>CharSet</code> contain the specified
     * character <code>ch</code>.}</p>
     *
     * @param ch  チェック対象となる文字
     * {@primary the character to check for}
     * @return 指定された文字が文字セットに含まれる場合、<code>true</code>
     * {@primary <code>true</code> if the set contains the characters}
     */
    public boolean contains(char ch) {
        for (Iterator it = set.iterator(); it.hasNext();) {
            CharRange range = (CharRange) it.next();
            if (range.contains(ch)) {
                return true;
            }
        }
        return false;
    }

    // Basics
    //-----------------------------------------------------------------------
    /**
     * <p>2つの CharSet オブジェクトを比較し、同じ方法で同じ文字セットを示している場合に
     * true を返します。
     * {@primary Compares two CharSet objects, returning true if they represent
     * exactly the same set of characters defined in the same way.}</p>
     *
     * <p>このメソッドでは <code>abc</code> と <code>a-c</code> の2つの文字セットは等しいとみなされません。
     * {@primary The two sets <code>abc</code> and <code>a-c</code> are <i>not</i>
     * equal according to this method.}</p>
     *
     * @param obj  比較対照となるオブジェクト
     * {@primary the object to compare to}
     * @return true 等しい場合、true
     * {@primary true if equal}
     * @since 2.0
     */
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof CharSet == false) {
            return false;
        }
        CharSet other = (CharSet) obj;
        return (set.equals(other.set));
    }

    /**
     * <p>equals メソッドと互換性のあるハッシュコードを取得します。
     * {@primary Gets a hashCode compatable with the equals method.}</p>
     *
     * @return 適切なハッシュコード
     * {@primary a suitable hashCode}
     * @since 2.0
     */
    public int hashCode() {
        return 89 + set.hashCode();
    }

    /**
     * <p>このセットの文字列表現を取得します。
     * {@primary Gets a string representation of the set.}</p>
     *
     * @return このセットの文字列表現
     * {@primary string representation of the set}
     */
    public String toString() {
        return set.toString();
    }

}

