/* Copyright (c) 2001-2019, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 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.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS 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 HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR 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.
 */


package org.hsqldb.types;

import java.text.Collator;
import java.util.Locale;

import org.hsqldb.HsqlException;
import org.hsqldb.HsqlNameManager;
import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.SchemaObject;
import org.hsqldb.Session;
import org.hsqldb.SqlInvariants;
import org.hsqldb.Tokens;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.HashMap;
import org.hsqldb.lib.Iterator;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.lib.StringUtil;
import org.hsqldb.rights.Grantee;

Implementation of collation support for CHAR and VARCHAR data.
Author:Frand Schoenheit (frank.schoenheit@sun dot com), Fred Toussi (fredt@users dot sourceforge.net)
Version:2.5.0
Since:1.8.0
/** * Implementation of collation support for CHAR and VARCHAR data. * * @author Frand Schoenheit (frank.schoenheit@sun dot com) * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 2.5.0 * @since 1.8.0 */
public class Collation implements SchemaObject { static String defaultCollationName = "SQL_TEXT"; static String defaultIgnoreCaseCollationName = "SQL_TEXT_UCC"; public static final HashMap nameToJavaName = new HashMap(101); public static final HashMap dbNameToJavaName = new HashMap(101); public static final HashMap dbNameToCollation = new HashMap(11); static { nameToJavaName.put("Afrikaans", "af-ZA"); nameToJavaName.put("Amharic", "am-ET"); nameToJavaName.put("Arabic", "ar"); nameToJavaName.put("Assamese", "as-IN"); nameToJavaName.put("Azerbaijani_Latin", "az-AZ"); nameToJavaName.put("Azerbaijani_Cyrillic", "az-cyrillic"); nameToJavaName.put("Belarusian", "be-BY"); nameToJavaName.put("Bulgarian", "bg-BG"); nameToJavaName.put("Bengali", "bn-IN"); nameToJavaName.put("Tibetan", "bo-CN"); nameToJavaName.put("Bosnian", "bs-BA"); nameToJavaName.put("Catalan", "ca-ES"); nameToJavaName.put("Czech", "cs-CZ"); nameToJavaName.put("Welsh", "cy-GB"); nameToJavaName.put("Danish", "da-DK"); nameToJavaName.put("German", "de-DE"); nameToJavaName.put("Greek", "el-GR"); nameToJavaName.put("Latin1_General", "en-US"); nameToJavaName.put("English", "en-US"); nameToJavaName.put("Spanish", "es-ES"); nameToJavaName.put("Estonian", "et-EE"); nameToJavaName.put("Basque", "eu"); nameToJavaName.put("Finnish", "fi-FI"); nameToJavaName.put("French", "fr-FR"); nameToJavaName.put("Guarani", "gn-PY"); nameToJavaName.put("Gujarati", "gu-IN"); nameToJavaName.put("Hausa", "ha-NG"); nameToJavaName.put("Hebrew", "he-IL"); nameToJavaName.put("Hindi", "hi-IN"); nameToJavaName.put("Croatian", "hr-HR"); nameToJavaName.put("Hungarian", "hu-HU"); nameToJavaName.put("Armenian", "hy-AM"); nameToJavaName.put("Indonesian", "id-ID"); nameToJavaName.put("Igbo", "ig-NG"); nameToJavaName.put("Icelandic", "is-IS"); nameToJavaName.put("Italian", "it-IT"); nameToJavaName.put("Inuktitut", "iu-CA"); nameToJavaName.put("Japanese", "ja-JP"); nameToJavaName.put("Georgian", "ka-GE"); nameToJavaName.put("Kazakh", "kk-KZ"); nameToJavaName.put("Khmer", "km-KH"); nameToJavaName.put("Kannada", "kn-IN"); nameToJavaName.put("Korean", "ko-KR"); nameToJavaName.put("Konkani", "kok-IN"); nameToJavaName.put("Kashmiri", "ks"); nameToJavaName.put("Kirghiz", "ky-KG"); nameToJavaName.put("Lao", "lo-LA"); nameToJavaName.put("Lithuanian", "lt-LT"); nameToJavaName.put("Latvian", "lv-LV"); nameToJavaName.put("Maori", "mi-NZ"); nameToJavaName.put("Macedonian", "mk-MK"); nameToJavaName.put("Malayalam", "ml-IN"); nameToJavaName.put("Mongolian", "mn-MN"); nameToJavaName.put("Manipuri", "mni-IN"); nameToJavaName.put("Marathi", "mr-IN"); nameToJavaName.put("Malay", "ms-MY"); nameToJavaName.put("Maltese", "mt-MT"); nameToJavaName.put("Burmese", "my-MM"); nameToJavaName.put("Danish_Norwegian", "nb-NO"); nameToJavaName.put("Nepali", "ne-NP"); nameToJavaName.put("Dutch", "nl-NL"); nameToJavaName.put("Norwegian", "nn-NO"); nameToJavaName.put("Oriya", "or-IN"); nameToJavaName.put("Punjabi", "pa-IN"); nameToJavaName.put("Polish", "pl-PL"); nameToJavaName.put("Pashto", "ps-AF"); nameToJavaName.put("Portuguese", "pt-PT"); nameToJavaName.put("Romanian", "ro-RO"); nameToJavaName.put("Russian", "ru-RU"); nameToJavaName.put("Sanskrit", "sa-IN"); nameToJavaName.put("Sindhi", "sd-IN"); nameToJavaName.put("Slovak", "sk-SK"); nameToJavaName.put("Slovenian", "sl-SI"); nameToJavaName.put("Somali", "so-SO"); nameToJavaName.put("Albanian", "sq-AL"); nameToJavaName.put("Serbian_Cyrillic", "sr-YU"); nameToJavaName.put("Serbian_Latin", "sh-BA"); nameToJavaName.put("Swedish", "sv-SE"); nameToJavaName.put("Swahili", "sw-KE"); nameToJavaName.put("Tamil", "ta-IN"); nameToJavaName.put("Telugu", "te-IN"); nameToJavaName.put("Tajik", "tg-TJ"); nameToJavaName.put("Thai", "th-TH"); nameToJavaName.put("Turkmen", "tk-TM"); nameToJavaName.put("Tswana", "tn-BW"); nameToJavaName.put("Turkish", "tr-TR"); nameToJavaName.put("Tatar", "tt-RU"); nameToJavaName.put("Ukrainian", "uk-UA"); nameToJavaName.put("Urdu", "ur-PK"); nameToJavaName.put("Uzbek_Latin", "uz-UZ"); nameToJavaName.put("Venda", "ven-ZA"); nameToJavaName.put("Vietnamese", "vi-VN"); nameToJavaName.put("Yoruba", "yo-NG"); nameToJavaName.put("Chinese", "zh-CN"); nameToJavaName.put("Zulu", "zu-ZA"); // Iterator it = nameToJavaName.values().iterator(); while (it.hasNext()) { String javaName = (String) it.next(); String dbName = javaName.replace('-', '_').toUpperCase(Locale.ENGLISH); dbNameToJavaName.put(dbName, javaName); } } static final Collation defaultCollation = new Collation(true); static final Collation defaultIgnoreCaseCollation = new Collation(false); static { defaultCollation.charset = Charset.SQL_TEXT; defaultIgnoreCaseCollation.charset = Charset.SQL_TEXT; dbNameToCollation.put(defaultCollationName, defaultCollation); dbNameToCollation.put(defaultIgnoreCaseCollationName, defaultIgnoreCaseCollation); } final HsqlName name; private Collator collator; private Locale locale; private boolean isUnicodeSimple; private boolean isUpperCaseCompare; private boolean isFinal; private boolean padSpace = true; // private Charset charset; private HsqlName sourceName; private Collation(boolean simple) { String nameString = simple ? defaultCollationName : defaultIgnoreCaseCollationName; locale = Locale.ENGLISH; name = HsqlNameManager.newInfoSchemaObjectName(nameString, false, SchemaObject.COLLATION); isUnicodeSimple = simple; isFinal = true; } private Collation(String name, String language, String country, int strength, int decomposition, boolean ucc) { locale = new Locale(language, country); collator = Collator.getInstance(locale); if (strength >= 0) { collator.setStrength(strength); } if (decomposition >= 0) { collator.setDecomposition(decomposition); } strength = collator.getStrength(); isUnicodeSimple = false; this.name = HsqlNameManager.newInfoSchemaObjectName(name, true, SchemaObject.COLLATION); charset = Charset.SQL_TEXT; isUpperCaseCompare = ucc; isFinal = true; } public Collation(HsqlName name, Collation source, Charset charset, Boolean padSpace) { this.name = name; this.locale = source.locale; this.collator = source.collator; this.isUnicodeSimple = source.isUnicodeSimple; this.isFinal = true; // this.charset = charset; this.sourceName = source.name; if (padSpace != null) { this.padSpace = padSpace.booleanValue(); } } public static Collation getDefaultInstance() { return defaultCollation; } public static Collation getDefaultIgnoreCaseInstance() { return defaultIgnoreCaseCollation; } public static Collation newDatabaseInstance() { Collation collation = new Collation(true); collation.isFinal = false; return collation; } public static Iterator getCollationsIterator() { return nameToJavaName.keySet().iterator(); } public static Iterator getLocalesIterator() { return nameToJavaName.values().iterator(); } public synchronized static Collation getCollation(String name) { Collation collation = (Collation) dbNameToCollation.get(name); if (collation != null) { return collation; } collation = getNewCollation(name); dbNameToCollation.put(name, collation); return collation; } public synchronized static Collation getUpperCaseCompareCollation( Collation source) { if (defaultCollationName.equals(source.name.name) || defaultIgnoreCaseCollationName.equals(source.name.name)) { return defaultIgnoreCaseCollation; } if (source.isUpperCaseCompare) { return source; } String name = source.getName().name; if (name.contains(" UCC")) { return source; } name = name + " UCC"; return getCollation(name); } private static Collation getNewCollation(String name) { int strength = -1; int decomposition = -1; boolean ucc = false; String[] parts = StringUtil.split(name, " "); String dbName = parts[0]; int index = 1; int limit = parts.length; if (parts.length > index && "UCC".equals(parts[limit - 1])) { ucc = true; limit--; } if (index < limit) { strength = Integer.parseInt(parts[index]); index++; } if (index < limit) { decomposition = Integer.parseInt(parts[index]); index++; } if (index < limit) { throw Error.error(ErrorCode.X_42501, name); } String javaName = (String) dbNameToJavaName.get(dbName); if (javaName == null) { javaName = (String) nameToJavaName.get(dbName); if (javaName == null) { throw Error.error(ErrorCode.X_42501, dbName); } } parts = StringUtil.split(javaName, "-"); String language = parts[0]; String country = parts.length == 2 ? parts[1] : ""; return new Collation(name, language, country, strength, decomposition, ucc); } public void setPadding(boolean padSpace) { if (isFinal) { throw Error.error(ErrorCode.X_42503); } this.padSpace = padSpace; } public void setCollationAsLocale() { Locale locale = Locale.getDefault(); String language = locale.getDisplayLanguage(Locale.ENGLISH); try { setCollation(language, false); } catch (HsqlException e) {} } public void setCollation(String newName, boolean padSpace) { if (isFinal) { throw Error.error(ErrorCode.X_42503, newName); } Collation newCollation = Collation.getCollation(newName); this.name.rename(newCollation.name.name, true); this.locale = newCollation.locale; this.collator = newCollation.collator; this.isUnicodeSimple = newCollation.isUnicodeSimple; this.padSpace = padSpace; } public boolean isPadSpace() { return padSpace; } public boolean isUnicodeSimple() { return isUnicodeSimple; } public boolean isUpperCaseCompare() { return isUpperCaseCompare; } public boolean isCaseSensitive() { // add support for case-sensitive language collations if (collator == null) { return isUnicodeSimple; } else { return !isUpperCaseCompare; } }
returns -1, 0 or +1
/** * returns -1, 0 or +1 */
public int compare(String a, String b) { int i; if (collator == null) { if (isUnicodeSimple) { i = a.compareTo(b); } else { i = a.compareToIgnoreCase(b); } } else { if (isUpperCaseCompare) { i = collator.compare(toUpperCase(a), toUpperCase(b)); } else { i = collator.compare(a, b); } } return (i == 0) ? 0 : (i < 0 ? -1 : 1); } public String toUpperCase(String s) { return s.toUpperCase(locale); } public String toLowerCase(String s) { return s.toLowerCase(locale); }
the SQL_TEXT collation
/** * the SQL_TEXT collation */
public boolean isDefaultCollation() { return collator == null && isUnicodeSimple && padSpace; }
collation for individual object
/** * collation for individual object */
public boolean isObjectCollation() { return isFinal; } public HsqlName getName() { return name; } public int getType() { return SchemaObject.COLLATION; } public HsqlName getSchemaName() { return name.schema; } public HsqlName getCatalogName() { return name.schema.schema; } public Grantee getOwner() { return name.schema.owner; } public OrderedHashSet getReferences() { if (charset == null) { return new OrderedHashSet(); } else { return charset.getReferences(); } } public OrderedHashSet getComponents() { return null; } public void compile(Session session, SchemaObject parentObject) {} public String getSQL() { StringBuilder sb = new StringBuilder(); sb.append(Tokens.T_CREATE).append(' '); sb.append(Tokens.T_COLLATION).append(' '); if (SqlInvariants.INFORMATION_SCHEMA.equals(name.schema.name)) { sb.append(name.getStatementName()); } else { sb.append(name.getSchemaQualifiedStatementName()); } sb.append(' ').append(Tokens.T_FOR).append(' '); if (SqlInvariants.INFORMATION_SCHEMA.equals( charset.name.schema.name)) { sb.append(charset.name.getStatementName()); } else { sb.append(charset.name.getSchemaQualifiedStatementName()); } sb.append(' ').append(Tokens.T_FROM).append(' '); sb.append(sourceName.statementName); sb.append(' '); if (!padSpace) { sb.append(Tokens.T_NO).append(' ').append(Tokens.T_PAD); } return sb.toString(); } public long getChangeTimestamp() { return 0; } public String getCollateSQL() { StringBuilder sb = new StringBuilder(); sb.append(Tokens.T_COLLATE).append(' '); if (isObjectCollation()) { sb.append(getName().getSchemaQualifiedStatementName()); } else { sb.append(getName().statementName); } return sb.toString(); } public String getDatabaseCollationSQL() { StringBuilder sb = new StringBuilder(); sb.append(Tokens.T_SET).append(' '); sb.append(Tokens.T_DATABASE).append(' '); sb.append(Tokens.T_COLLATION).append(' '); sb.append(getName().statementName); sb.append(' '); if (!padSpace) { sb.append(Tokens.T_NO).append(' ').append(Tokens.T_PAD); } return sb.toString(); } }