// Path: src/util/src/main/java/org/lilycms/util/ArgumentValidator.java
// public class ArgumentValidator {
//     public static void notNull(Object object, String argName) {
//         if (object == null)
//             throw new IllegalArgumentException("Null argument: " + argName);
//     }
// }
// 
// Path: src/util/src/main/java/org/lilycms/util/LocaleHelper.java
// public class LocaleHelper {
//     public static Locale parseLocale(String localeString) {
//         StringTokenizer localeParser = new StringTokenizer(localeString, "-_");
// 
//         String lang = null, country = null, variant = null;
// 
//         if (localeParser.hasMoreTokens())
//             lang = localeParser.nextToken();
//         if (localeParser.hasMoreTokens())
//             country = localeParser.nextToken();
//         if (localeParser.hasMoreTokens())
//             variant = localeParser.nextToken();
// 
//         if (lang != null && country != null && variant != null)
//             return new Locale(lang, country, variant);
//         else if (lang != null && country != null)
//             return new Locale(lang, country);
//         else if (lang != null)
//             return new Locale(lang);
//         else
//             return new Locale("");
//     }
// 
//     public static String getString(Locale locale) {
//         return getString(locale, "_");
//     }
// 
//     public static String getString(Locale locale, String separator) {
//         boolean hasLanguage = !locale.getLanguage().equals("");
//         boolean hasCountry = !locale.getCountry().equals("");
//         boolean hasVariant = !locale.getVariant().equals("");
// 
//         if (hasLanguage && hasCountry && hasVariant)
//             return locale.getLanguage() + separator + locale.getCountry() + separator + locale.getVariant();
//         else if (hasLanguage && hasCountry)
//             return locale.getLanguage() + separator + locale.getCountry();
//         else if (hasLanguage)
//             return locale.getLanguage();
//         else
//             return "";
//     }
// }


import org.codehaus.jackson.node.ObjectNode;
import org.lilycms.util.ArgumentValidator;
import org.lilycms.util.LocaleHelper;
import java.io.UnsupportedEncodingException;
import java.text.Collator;
import java.text.Normalizer;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/*
 * Copyright 2010 Outerthought bvba
 *
 * 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 org.lilycms.hbaseindex;



/**
 * A string field in an index.
 *
 * <p>Strings can be of variable length, therefore the {#setLength} method returns -1.
 *
 * <p>Various options can be set for this field:
 *
 * <ul>
 *  <li>{@link #setByteEncodeMode}: the way a string should be mapped onto bytes: as utf8,
 *    by folding everything to ascii, or by using collation keys.
 *  <li>
 *  <li>{@link #setLocale}
 *  <li>{@link #setCaseSensitive}
 * </ul>
 */
public class StringIndexFieldDefinition extends IndexFieldDefinition {
    public enum ByteEncodeMode { UTF8, ASCII_FOLDING, COLLATOR }

    private Locale locale = Locale.US;
    private ByteEncodeMode byteEncodeMode = ByteEncodeMode.UTF8;
    private boolean caseSensitive = true;

    private static Map<ByteEncodeMode, StringEncoder> ENCODERS;
    static {
        ENCODERS = new HashMap<ByteEncodeMode, StringEncoder>();
        ENCODERS.put(ByteEncodeMode.UTF8, new Utf8StringEncoder());
        ENCODERS.put(ByteEncodeMode.ASCII_FOLDING, new AsciiFoldingStringEncoder());
        ENCODERS.put(ByteEncodeMode.COLLATOR, new CollatorStringEncoder());
    }

    /**
     * The end-of-field marker consists of all-zero-bits bytes. This is to achieve the effect
     * that shorter strings would always sort before those that contain an additional character.
     * Supposing a zero is an allowed byte within an encoded string, the further bytes should
     * again be zeros for the same purpose.
     *
     * <p>This makes the assumption that a sequence of zero bytes will not occur in an encoded
     * string (see also the toBytes method where this is checked, and where the same sequence is also hardcoded!)
     */
    private static final byte[] EOF_MARKER = new byte[] {0, 0, 0, 0};

    public StringIndexFieldDefinition(String name) {
        super(name, IndexValueType.STRING);
    }

    public StringIndexFieldDefinition(String name, ObjectNode jsonObject) {
        super(name, IndexValueType.STRING, jsonObject);

        if (jsonObject.get("locale") != null)
            this.locale = LocaleHelper.parseLocale(jsonObject.get("locale").getTextValue());
        if (jsonObject.get("byteEncodeMode") != null)
            this.byteEncodeMode = ByteEncodeMode.valueOf(jsonObject.get("byteEncodeMode").getTextValue());
        if (jsonObject.get("caseSensitive") != null)
            this.caseSensitive = jsonObject.get("caseSensitive").getBooleanValue();
    }

    public Locale getLocale() {
        return locale;
    }

    /**
     * The Locale to use. This locale is used to fold the case when
     * case sensitivity is not desired, and is used in case the
     * {@link ByteEncodeMode#COLLATOR} mode is selected.
     */
    public void setLocale(Locale locale) {
// Path: src/util/src/main/java/org/lilycms/util/ArgumentValidator.java
// public class ArgumentValidator {
//     public static void notNull(Object object, String argName) {
//         if (object == null)
//             throw new IllegalArgumentException("Null argument: " + argName);
//     }
// }
// 
// Path: src/util/src/main/java/org/lilycms/util/LocaleHelper.java
// public class LocaleHelper {
//     public static Locale parseLocale(String localeString) {
//         StringTokenizer localeParser = new StringTokenizer(localeString, "-_");
// 
//         String lang = null, country = null, variant = null;
// 
//         if (localeParser.hasMoreTokens())
//             lang = localeParser.nextToken();
//         if (localeParser.hasMoreTokens())
//             country = localeParser.nextToken();
//         if (localeParser.hasMoreTokens())
//             variant = localeParser.nextToken();
// 
//         if (lang != null && country != null && variant != null)
//             return new Locale(lang, country, variant);
//         else if (lang != null && country != null)
//             return new Locale(lang, country);
//         else if (lang != null)
//             return new Locale(lang);
//         else
//             return new Locale("");
//     }
// 
//     public static String getString(Locale locale) {
//         return getString(locale, "_");
//     }
// 
//     public static String getString(Locale locale, String separator) {
//         boolean hasLanguage = !locale.getLanguage().equals("");
//         boolean hasCountry = !locale.getCountry().equals("");
//         boolean hasVariant = !locale.getVariant().equals("");
// 
//         if (hasLanguage && hasCountry && hasVariant)
//             return locale.getLanguage() + separator + locale.getCountry() + separator + locale.getVariant();
//         else if (hasLanguage && hasCountry)
//             return locale.getLanguage() + separator + locale.getCountry();
//         else if (hasLanguage)
//             return locale.getLanguage();
//         else
//             return "";
//     }
// }


// Path: src/hbaseindex/src/main/java/org/lilycms/hbaseindex/StringIndexFieldDefinition.java
import org.codehaus.jackson.node.ObjectNode;
import org.lilycms.util.ArgumentValidator;
import org.lilycms.util.LocaleHelper;
import java.io.UnsupportedEncodingException;
import java.text.Collator;
import java.text.Normalizer;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/*
 * Copyright 2010 Outerthought bvba
 *
 * 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 org.lilycms.hbaseindex;



/**
 * A string field in an index.
 *
 * <p>Strings can be of variable length, therefore the {#setLength} method returns -1.
 *
 * <p>Various options can be set for this field:
 *
 * <ul>
 *  <li>{@link #setByteEncodeMode}: the way a string should be mapped onto bytes: as utf8,
 *    by folding everything to ascii, or by using collation keys.
 *  <li>
 *  <li>{@link #setLocale}
 *  <li>{@link #setCaseSensitive}
 * </ul>
 */
public class StringIndexFieldDefinition extends IndexFieldDefinition {
    public enum ByteEncodeMode { UTF8, ASCII_FOLDING, COLLATOR }

    private Locale locale = Locale.US;
    private ByteEncodeMode byteEncodeMode = ByteEncodeMode.UTF8;
    private boolean caseSensitive = true;

    private static Map<ByteEncodeMode, StringEncoder> ENCODERS;
    static {
        ENCODERS = new HashMap<ByteEncodeMode, StringEncoder>();
        ENCODERS.put(ByteEncodeMode.UTF8, new Utf8StringEncoder());
        ENCODERS.put(ByteEncodeMode.ASCII_FOLDING, new AsciiFoldingStringEncoder());
        ENCODERS.put(ByteEncodeMode.COLLATOR, new CollatorStringEncoder());
    }

    /**
     * The end-of-field marker consists of all-zero-bits bytes. This is to achieve the effect
     * that shorter strings would always sort before those that contain an additional character.
     * Supposing a zero is an allowed byte within an encoded string, the further bytes should
     * again be zeros for the same purpose.
     *
     * <p>This makes the assumption that a sequence of zero bytes will not occur in an encoded
     * string (see also the toBytes method where this is checked, and where the same sequence is also hardcoded!)
     */
    private static final byte[] EOF_MARKER = new byte[] {0, 0, 0, 0};

    public StringIndexFieldDefinition(String name) {
        super(name, IndexValueType.STRING);
    }

    public StringIndexFieldDefinition(String name, ObjectNode jsonObject) {
        super(name, IndexValueType.STRING, jsonObject);

        if (jsonObject.get("locale") != null)
            this.locale = LocaleHelper.parseLocale(jsonObject.get("locale").getTextValue());
        if (jsonObject.get("byteEncodeMode") != null)
            this.byteEncodeMode = ByteEncodeMode.valueOf(jsonObject.get("byteEncodeMode").getTextValue());
        if (jsonObject.get("caseSensitive") != null)
            this.caseSensitive = jsonObject.get("caseSensitive").getBooleanValue();
    }

    public Locale getLocale() {
        return locale;
    }

    /**
     * The Locale to use. This locale is used to fold the case when
     * case sensitivity is not desired, and is used in case the
     * {@link ByteEncodeMode#COLLATOR} mode is selected.
     */
    public void setLocale(Locale locale) {
        ArgumentValidator.notNull(locale, "locale");