/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.value;

import java.nio.charset.Charset;
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;
import java.util.Objects;

import org.h2.engine.SysProperties;
import org.h2.util.StringUtils;

Instances of this class can compare strings. Case sensitive and case insensitive comparison is supported, and comparison using a collator.
/** * Instances of this class can compare strings. Case sensitive and case * insensitive comparison is supported, and comparison using a collator. */
public class CompareMode implements Comparator<Value> {
This constant means there is no collator set, and the default string comparison is to be used.
/** * This constant means there is no collator set, and the default string * comparison is to be used. */
public static final String OFF = "OFF";
This constant means the default collator should be used, even if ICU4J is in the classpath.
/** * This constant means the default collator should be used, even if ICU4J is * in the classpath. */
public static final String DEFAULT = "DEFAULT_";
This constant means ICU4J should be used (this will fail if it is not in the classpath).
/** * This constant means ICU4J should be used (this will fail if it is not in * the classpath). */
public static final String ICU4J = "ICU4J_";
This constant means the charset specified should be used. This will fail if the specified charset does not exist.
/** * This constant means the charset specified should be used. * This will fail if the specified charset does not exist. */
public static final String CHARSET = "CHARSET_";
This constant means that the BINARY or UUID columns are sorted as if the bytes were signed.
/** * This constant means that the BINARY or UUID columns are sorted as if the * bytes were signed. */
public static final String SIGNED = "SIGNED";
This constant means that the BINARY or UUID columns are sorted as if the bytes were unsigned.
/** * This constant means that the BINARY or UUID columns are sorted as if the * bytes were unsigned. */
public static final String UNSIGNED = "UNSIGNED"; private static volatile CompareMode lastUsed; private static final boolean CAN_USE_ICU4J; static { boolean b = false; try { Class.forName("com.ibm.icu.text.Collator"); b = true; } catch (Exception e) { // ignore } CAN_USE_ICU4J = b; } private final String name; private final int strength;
If true, sort BINARY columns as if they contain unsigned bytes.
/** * If true, sort BINARY columns as if they contain unsigned bytes. */
private final boolean binaryUnsigned;
If true, sort UUID columns as if they contain unsigned bytes instead of Java-compatible sorting.
/** * If true, sort UUID columns as if they contain unsigned bytes instead of * Java-compatible sorting. */
private final boolean uuidUnsigned; protected CompareMode(String name, int strength, boolean binaryUnsigned, boolean uuidUnsigned) { this.name = name; this.strength = strength; this.binaryUnsigned = binaryUnsigned; this.uuidUnsigned = uuidUnsigned; }
Create a new compare mode with the given collator and strength. If required, a new CompareMode is created, or if possible the last one is returned. A cache is used to speed up comparison when using a collator; CollationKey objects are cached.
Params:
  • name – the collation name or null
  • strength – the collation strength
Returns:the compare mode
/** * Create a new compare mode with the given collator and strength. If * required, a new CompareMode is created, or if possible the last one is * returned. A cache is used to speed up comparison when using a collator; * CollationKey objects are cached. * * @param name the collation name or null * @param strength the collation strength * @return the compare mode */
public static CompareMode getInstance(String name, int strength) { return getInstance(name, strength, SysProperties.SORT_BINARY_UNSIGNED, SysProperties.SORT_UUID_UNSIGNED); }
Create a new compare mode with the given collator and strength. If required, a new CompareMode is created, or if possible the last one is returned. A cache is used to speed up comparison when using a collator; CollationKey objects are cached.
Params:
  • name – the collation name or null
  • strength – the collation strength
  • binaryUnsigned – whether to compare binaries as unsigned
  • uuidUnsigned – whether to compare UUIDs as unsigned
Returns:the compare mode
/** * Create a new compare mode with the given collator and strength. If * required, a new CompareMode is created, or if possible the last one is * returned. A cache is used to speed up comparison when using a collator; * CollationKey objects are cached. * * @param name the collation name or null * @param strength the collation strength * @param binaryUnsigned whether to compare binaries as unsigned * @param uuidUnsigned whether to compare UUIDs as unsigned * @return the compare mode */
public static CompareMode getInstance(String name, int strength, boolean binaryUnsigned, boolean uuidUnsigned) { CompareMode last = lastUsed; if (last != null) { if (Objects.equals(last.name, name) && last.strength == strength && last.binaryUnsigned == binaryUnsigned && last.uuidUnsigned == uuidUnsigned) { return last; } } if (name == null || name.equals(OFF)) { last = new CompareMode(name, strength, binaryUnsigned, uuidUnsigned); } else { boolean useICU4J; if (name.startsWith(ICU4J)) { useICU4J = true; name = name.substring(ICU4J.length()); } else if (name.startsWith(DEFAULT)) { useICU4J = false; name = name.substring(DEFAULT.length()); } else { useICU4J = CAN_USE_ICU4J; } if (useICU4J) { last = new CompareModeIcu4J(name, strength, binaryUnsigned, uuidUnsigned); } else { last = new CompareModeDefault(name, strength, binaryUnsigned, uuidUnsigned); } } lastUsed = last; return last; }
Compare two characters in a string.
Params:
  • a – the first string
  • ai – the character index in the first string
  • b – the second string
  • bi – the character index in the second string
  • ignoreCase – true if a case-insensitive comparison should be made
Returns:true if the characters are equals
/** * Compare two characters in a string. * * @param a the first string * @param ai the character index in the first string * @param b the second string * @param bi the character index in the second string * @param ignoreCase true if a case-insensitive comparison should be made * @return true if the characters are equals */
public boolean equalsChars(String a, int ai, String b, int bi, boolean ignoreCase) { char ca = a.charAt(ai); char cb = b.charAt(bi); if (ignoreCase) { ca = Character.toUpperCase(ca); cb = Character.toUpperCase(cb); } return ca == cb; }
Compare two strings.
Params:
  • a – the first string
  • b – the second string
  • ignoreCase – true if a case-insensitive comparison should be made
Returns:-1 if the first string is 'smaller', 1 if the second string is smaller, and 0 if they are equal
/** * Compare two strings. * * @param a the first string * @param b the second string * @param ignoreCase true if a case-insensitive comparison should be made * @return -1 if the first string is 'smaller', 1 if the second string is * smaller, and 0 if they are equal */
public int compareString(String a, String b, boolean ignoreCase) { if (ignoreCase) { return a.compareToIgnoreCase(b); } return a.compareTo(b); }
Get the collation name.
Params:
  • l – the locale
Returns:the name of the collation
/** * Get the collation name. * * @param l the locale * @return the name of the collation */
public static String getName(Locale l) { Locale english = Locale.ENGLISH; String name = l.getDisplayLanguage(english) + ' ' + l.getDisplayCountry(english) + ' ' + l.getVariant(); name = StringUtils.toUpperEnglish(name.trim().replace(' ', '_')); return name; }
Compare name of the locale with the given name. The case of the name is ignored.
Params:
  • locale – the locale
  • name – the name
Returns:true if they match
/** * Compare name of the locale with the given name. The case of the name * is ignored. * * @param locale the locale * @param name the name * @return true if they match */
static boolean compareLocaleNames(Locale locale, String name) { return name.equalsIgnoreCase(locale.toString()) || name.equalsIgnoreCase(getName(locale)); }
Get the collator object for the given language name or language / country combination.
Params:
  • name – the language name
Returns:the collator
/** * Get the collator object for the given language name or language / country * combination. * * @param name the language name * @return the collator */
public static Collator getCollator(String name) { Collator result = null; if (name.startsWith(ICU4J)) { name = name.substring(ICU4J.length()); } else if (name.startsWith(DEFAULT)) { name = name.substring(DEFAULT.length()); } else if (name.startsWith(CHARSET)) { return new CharsetCollator(Charset.forName(name.substring(CHARSET.length()))); } int length = name.length(); if (length == 2) { Locale locale = new Locale(StringUtils.toLowerEnglish(name), ""); if (compareLocaleNames(locale, name)) { result = Collator.getInstance(locale); } } else if (length == 5) { // LL_CC (language_country) int idx = name.indexOf('_'); if (idx >= 0) { String language = StringUtils.toLowerEnglish(name.substring(0, idx)); String country = name.substring(idx + 1); Locale locale = new Locale(language, country); if (compareLocaleNames(locale, name)) { result = Collator.getInstance(locale); } } } if (result == null) { for (Locale locale : Collator.getAvailableLocales()) { if (compareLocaleNames(locale, name)) { result = Collator.getInstance(locale); break; } } } return result; } public String getName() { return name == null ? OFF : name; } public int getStrength() { return strength; } public boolean isBinaryUnsigned() { return binaryUnsigned; } public boolean isUuidUnsigned() { return uuidUnsigned; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } else if (!(obj instanceof CompareMode)) { return false; } CompareMode o = (CompareMode) obj; if (!getName().equals(o.getName())) { return false; } if (strength != o.strength) { return false; } if (binaryUnsigned != o.binaryUnsigned) { return false; } if (uuidUnsigned != o.uuidUnsigned) { return false; } return true; } @Override public int hashCode() { int result = 1; result = 31 * result + getName().hashCode(); result = 31 * result + strength; result = 31 * result + (binaryUnsigned ? 1231 : 1237); result = 31 * result + (uuidUnsigned ? 1231 : 1237); return result; } @Override public int compare(Value o1, Value o2) { return o1.compareTo(o2, null, this); } }