/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.collation;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import com.foundationdb.server.error.InvalidCollationSchemeException;
import com.foundationdb.server.error.UnsupportedCollationException;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.RuleBasedCollator;
/**
* Provides Collator instances. Collator is not threadsafe, so this class keeps
* SoftReferences to thread-local instances.
*
* @author peter
*
*/
public class AkCollatorFactory {
public final static int UCS_BINARY_ID = 0;
public final static String UCS_BINARY = "UCS_BINARY";
public final static AkCollator UCS_BINARY_COLLATOR = new AkCollatorBinary();
private final static Map<String, Collator> sourceMap = new HashMap<>();
private final static Map<String, SoftReference<AkCollator>> collatorMap = new ConcurrentHashMap<>();
private final static Map<Integer, SoftReference<AkCollator>> collationIdMap = new ConcurrentHashMap<>();
private final static Map<String, Integer> schemeToIdMap = new ConcurrentHashMap<>();
private final static AtomicInteger collationIdGenerator = new AtomicInteger(UCS_BINARY_ID);
private volatile static Mode mode = Mode.STRICT;
/*
* Note: used only in a single-threaded unit test.
*/
private static int cacheHits;
public enum Mode {
STRICT, LOOSE, DISABLED
}
/**
* Set factory to one of three modes specified by case-insensitive string:
* <dl>
* <dt>disabled</dt>
* <dd>always uses UCS_BINARY_COLLATOR</dd>
* <dt>strict</dt>
* <dd>disallows unimplemented collators</dd>
* <dt>loose</dt>
* <dd>returns UCS_BINARY_COLLATOR for any unrecognized name</dd>
* </dl
*/
public static void setCollationMode(String modeString) {
try {
setCollationMode(Mode.valueOf(modeString.toUpperCase()));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Collation mode must be STRICT, LOOSE or DISABLED: " + modeString);
}
}
public static void setCollationMode(Mode m) {
if (m == null) {
throw new NullPointerException();
}
synchronized (collatorMap) {
collationIdMap.clear();
collatorMap.clear();
mode = m;
}
}
public static Mode getCollationMode() {
return mode;
}
public static AkCollator getAkCollator(final String scheme) {
if (mode == Mode.DISABLED || scheme == null) {
return UCS_BINARY_COLLATOR;
}
if (scheme.equalsIgnoreCase(UCS_BINARY)) {
return mapToBinary(scheme);
}
SoftReference<AkCollator> ref = collatorMap.get(scheme);
if (ref != null) {
AkCollator akCollator = ref.get();
if (akCollator != null) {
cacheHits++;
return akCollator;
}
}
synchronized (collatorMap) {
Integer collationId = schemeToIdMap.get(scheme);
if (collationId == null) {
collationId = collationIdGenerator.incrementAndGet();
}
final AkCollator akCollator;
try {
akCollator = new AkCollatorICU(scheme, collationId);
} catch (InvalidCollationSchemeException | UnsupportedCollationException e) {
if (mode == Mode.LOOSE) {
return mapToBinary(scheme);
}
throw e;
}
ref = new SoftReference<>(akCollator);
collatorMap.put(scheme, ref);
collationIdMap.put(collationId, ref);
schemeToIdMap.put(scheme, collationId);
return akCollator;
}
}
public static AkCollator getAkCollator(final int collatorId) {
final SoftReference<AkCollator> ref = collationIdMap.get(collatorId);
AkCollator collator = (ref == null ? null : ref.get());
if (collator == null) {
if (collatorId == UCS_BINARY_ID) {
return UCS_BINARY_COLLATOR;
}
else {
String scheme = getKeyByValue(schemeToIdMap, collatorId);
if (scheme == null) return null;
return getAkCollator(scheme);
}
} else {
cacheHits++;
}
return collator;
}
public static <T, E> T getKeyByValue(Map<T, E> map, E value) {
for (Entry<T, E> entry : map.entrySet()) {
if (value.equals(entry.getValue())) {
return entry.getKey();
}
}
return null;
}
/**
* Construct an actual ICU Collator given a collation specifier. The
* result is a Collator that must be use in a thread-private manner.
*/
static synchronized Collator forScheme(final CollationSpecifier specifier) {
RuleBasedCollator collator = (RuleBasedCollator) sourceMap.get(specifier.toString());
if (collator == null) {
collator = specifier.createCollator();
sourceMap.put(specifier.toString(), collator);
}
collator = collator.cloneAsThawed();
return collator;
}
private static AkCollator mapToBinary(final String scheme) {
collatorMap.put(scheme, new SoftReference<>(UCS_BINARY_COLLATOR));
return UCS_BINARY_COLLATOR;
}
/**
* Intended only for unit tests.
*
* @return Number of times either getAkCollator() method has returned a
* cached value.
*/
static int getCacheHits() {
return cacheHits;
}
}