/*
* Copyright 2012, We The Internet Ltd.
*
* All rights reserved.
*
* Distributed under a modified BSD License as follow:
*
* 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, unless otherwise
* agreed to in a written document signed by a director of We The Internet Ltd.
*
* Neither the name of We The Internet 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 THE COPYRIGHT HOLDER 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 xapi.collect.impl;
import xapi.collect.api.InitMap;
import xapi.util.X_Runtime;
import xapi.util.api.ConvertsValue;
/**
* A map for singleton values; if containsKey(key) == true,
* the value is returned from the underlying ConcurrentHashMap, even if null.
*
* When a key is not present, the map will call {@link #initialize(Object)},
* which accepts the key as parameter, and return the singleton which will be set in the map.
*
* This map IS entirely threadsafe.
* The initialization process is guarded by a double-checked lock,
* which is ONLY checked in jre runtime environments,
* and only if the multithreading system property ("xapi.multithreaded") is set.
*
* If you set a value manually, {@link #initialize(Object)} will not be called for that key.
*
* @author James X. Nelson (james@wetheinter.net)
*
* @param <Key> - The type of key to use. Be sure to implement hashCode() and equals().
* @param <Value> - The type of values provided.
*/
public abstract class AbstractInitMap <Key, Value> implements InitMap<Key,Value>{
/**
* A default toString converter that just calls String.valueOf.
*
* This will return "null" for null.
* But, you weren't using "null" for an id anyway, were you?
*/
@SuppressWarnings("rawtypes")
protected static final ConvertsValue TO_STRING = new ConvertsValue<Object,String>() {
@Override
public String convert(Object from) {
return String.valueOf(from);// will return "null" for null.
}
};
public static final ConvertsValue<String, String> PASS_THRU = new ConvertsValue<String,String>() {
@Override
public String convert(String from) {
return from;// will return null for null.
}
};
public static final ConvertsValue<Class<?>, String> CLASS_NAME = new ConvertsValue<Class<?>,String>() {
@Override
public String convert(Class<?> from) {
// fail fast for gwt's sake, so we get a usable exception
if (from == null)throw new NullPointerException();
return from.getName();
}
};
@SuppressWarnings("rawtypes") //it erases to object, we're fine
protected static final ConvertsValue ALWAYS_NULL = new ConvertsValue() {
@Override
public Object convert(Object from) {
return null;
}
};
// This class is super-sourced without ConcurrentMap, for code size optimization.
// We would inject it, but it is used in the inject subsystem;
private final ConvertsValue<Key,String> keyProvider;
@SuppressWarnings("unchecked") // Accepts Object in only method's only param.
protected AbstractInitMap() {
this(TO_STRING);
}
protected AbstractInitMap(ConvertsValue<Key,String> keyProvider) {
this.keyProvider = keyProvider;
assert keyProvider != null : "Cannot use null key provider for init map.";
}
private static final Object default_lock = new Object();
public Value get(Key k) {
String key = keyProvider.convert(k);
if (hasKey(key))
return getValue(key);
Value value;
if (isMultiThreaded()) {
//double-checked lock for multithreaded enviros only
synchronized(getLock(key)) {
if (hasKey(key)) {
return getValue(key);
}
//init object
value = initialize(k);
setValue(key, value);
}
}else {
//init object
value = initialize(k);
setValue(key, value);
}
return value;
}
@Override
public Value put(Key key, Value value) {
return setValue(keyProvider.convert(key), value);
}
public boolean containsKey(Key key) {
return hasKey(keyProvider.convert(key));
}
protected boolean isMultiThreaded() {
return X_Runtime.isMultithreaded();
}
protected Object getLock(Object forKey) {
return default_lock;
}
public String toKey(Key key) {
return keyProvider.convert(key);
}
@Override
public Value convert(Key from) {
return get(from);
}
}