/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*/
package com.sun.sgs.impl.util;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.NameNotBoundException;
import com.sun.sgs.app.ObjectNotFoundException;
import com.sun.sgs.app.util.ManagedSerializable;
import com.sun.sgs.impl.sharedutil.Objects;
import com.sun.sgs.service.DataService;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Map.Entry;
import java.util.Set;
/**
* An implementation of a persistent {@code Map} that uses service
* bindings in the data service to store key/value pairs. This map does
* not permit {@code null} keys or values.
*
* <p>Note: This map is parameterized by value type only. A {@code String}
* is the only valid key type for a {@code BindingKeyedMap}.
*
* <p>A value is stored in the data service using its associated key (a
* String) as a suffix to the {@code keyPrefix} specified during
* construction. All values must implement {@code Serializable}. If a
* value implements {@code Serializable}, but does not implement {@link
* ManagedObject}, the value will be wrapped in an instance of {@code
* ManagedSerializable} when storing it in the data service. Note: users of
* this map must use this map's APIs to avoid leaking wrappers for
* non-managed, serializable objects.
*
* <p>Instances of {@code BindingKeyedMap} as well as its associated
* iterators are serializable, but not managed, objects.
*
* @param <V> the type for the map's values
*/
public class BindingKeyedMapImpl<V>
extends AbstractMap<String, V>
implements BindingKeyedMap<V>, Serializable
{
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
/** The key prefix. */
private final String keyPrefix;
/**
* Constructs an instance with the specified {@code keyPrefix}.
*
* @param keyPrefix a key prefix
* @throws IllegalArgumentException if {@code keyPrefix} is empty
*/
BindingKeyedMapImpl(String keyPrefix) {
if (keyPrefix == null) {
throw new NullPointerException("null keyPrefix");
} else if (keyPrefix.isEmpty()) {
throw new IllegalArgumentException("empty keyPrefix");
}
this.keyPrefix = keyPrefix;
}
/* -- Override AbstractMap methods -- */
/** {@inheritDoc} */
public boolean isEmpty() {
return isEmptyInternal(keyPrefix);
}
/** {@inheritDoc} */
public V put(String key, V value) {
Objects.checkNull("key", key);
checkSerializable("value", value);
String bindingName = getBindingName(key);
V previousValue = get(key);
if (previousValue != null) {
removeValue(bindingName);
}
// Store key/value pair.
putKeyValue(bindingName, value);
return previousValue;
}
/** {@inheritDoc} */
public V get(Object key) {
checkKey("key", key);
String bindingName = getBindingName((String) key);
V value = null;
try {
value = getValue(bindingName);
} catch (NameNotBoundException e) {
}
return value;
}
/** {@inheritDoc} */
public boolean containsKey(Object key) {
checkKey("key", key);
return containsKeyInternal(getBindingName((String) key));
}
/** {@inheritDoc} */
public boolean containsValue(Object value) {
Objects.checkNull("value", value);
Iterator iter = new ValueIterator<V>(keyPrefix);
while (iter.hasNext()) {
try {
if (value.equals(iter.next())) {
return true;
}
} catch (ObjectNotFoundException e) {
}
}
return false;
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
public V remove(Object key) {
checkKey("key", key);
DataService dataService = BindingKeyedCollectionsImpl.getDataService();
String bindingName = getBindingName((String) key);
V value = null;
try {
ManagedObject v = dataService.getServiceBinding(bindingName);
if (v instanceof Wrapper) {
value = (V) ((Wrapper) v).get();
dataService.removeObject(v);
} else {
value = (V) v;
}
dataService.removeServiceBinding(bindingName);
} catch (NameNotBoundException e) {
}
return value;
}
/** {@inheritDoc} */
public void clear() {
clearInternal(keyPrefix);
}
/** {@inheritDoc} */
public int size() {
return sizeInternal(keyPrefix);
}
/** {@inheritDoc} */
public Set<Entry<String, V>> entrySet() {
return new EntrySet<V>(keyPrefix);
}
/**
* A serializable {@code Set} for this map's entries.
*/
private static final class EntrySet<V>
extends AbstractSet<Entry<String, V>>
implements Serializable
{
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
/** The key prefix. */
private final String keyPrefix;
/**
* Constructs an instance with the specified {@code keyPrefix}.
*
* @param keyPrefix a key prefix
*/
EntrySet(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
/** {@inheritDoc} */
public Iterator<Entry<String, V>> iterator() {
return new EntryIterator<V>(keyPrefix);
}
/** {@inheritDoc} */
public boolean isEmpty() {
return isEmptyInternal(keyPrefix);
}
/** {@inheritDoc} */
public int size() {
return sizeInternal(keyPrefix);
}
/** {@inheritDoc} */
public void clear() {
clearInternal(keyPrefix);
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
public boolean contains(Object o) {
Entry<String, V> entry = (Entry<String, V>) o;
return containsKeyInternal(keyPrefix + entry.getKey());
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
public boolean remove(Object o) {
Entry<String, V> entry = (Entry<String, V>) o;
return removeOverrideInternal(keyPrefix + entry.getKey());
}
}
/** {@inheritDoc} */
public Set<String> keySet() {
return new KeySet<V>(keyPrefix);
}
/**
* A serializable {@code Set} for this map's entries.
*/
private static final class KeySet<V>
extends AbstractSet<String>
implements Serializable
{
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
/** The key prefix. */
private final String keyPrefix;
/**
* Constructs an instance with the specified {@code keyPrefix}.
*
* @param keyPrefix a key prefix
*/
KeySet(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
/** {@inheritDoc} */
public Iterator<String> iterator() {
return new KeyIterator<V>(keyPrefix);
}
/** {@inheritDoc} */
public boolean isEmpty() {
return isEmptyInternal(keyPrefix);
}
/** {@inheritDoc} */
public int size() {
return sizeInternal(keyPrefix);
}
/** {@inheritDoc} */
public void clear() {
clearInternal(keyPrefix);
}
/** {@inheritDoc} */
public boolean contains(Object o) {
return containsKeyInternal(keyPrefix + (String) o);
}
/** {@inheritDoc} */
public boolean remove(Object o) {
return removeOverrideInternal(keyPrefix + (String) o);
}
}
/** {@inheritDoc} */
public Collection<V> values() {
return new Values<V>(keyPrefix);
}
/**
* A serializable {@code Collection} for this map's values.
*/
private static final class Values<V>
extends AbstractCollection<V>
implements Serializable
{
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
/** The key prefix. */
private final String keyPrefix;
/**
* Constructs an instance with the specified {@code keyPrefix}.
*
* @param keyPrefix a key prefix
*/
Values(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
/** {@inheritDoc} */
public Iterator<V> iterator() {
return new ValueIterator<V>(keyPrefix);
}
/** {@inheritDoc} */
public boolean isEmpty() {
return isEmptyInternal(keyPrefix);
}
/** {@inheritDoc} */
public int size() {
return sizeInternal(keyPrefix);
}
/** {@inheritDoc} */
public void clear() {
clearInternal(keyPrefix);
}
}
/**
* An abstract iterator for obtaining this map's entries.
* values.
*/
private abstract static class AbstractIterator<E, V>
implements Iterator<E>, Serializable
{
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
/** The data service. */
private transient DataService dataService;
/** The prefix for keys. */
private final String prefix;
/** The key used to look up the next service bound name; or null. */
private String key;
/** The key returned by {@code next}, or null. */
private String keyReturnedByNext;
/** The name fetched in the {@code hasNext} method, which
* is only valid if {@code hasNext} returns {@code true}. */
private String nextName;
/**
* Constructs an instance of this class with the specified
* {@code keyPrefix}.
*/
AbstractIterator(String prefix) {
this.prefix = prefix;
this.key = prefix;
dataService = BindingKeyedCollectionsImpl.getDataService();
}
/** {@inheritDoc} */
public boolean hasNext() {
if (key == null) {
return false;
}
if (nextName != null) {
return true;
}
String name = dataService.nextServiceBoundName(key);
if (name != null && name.startsWith(prefix)) {
nextName = name;
return true;
} else {
key = null;
return false;
}
}
/** {@inheritDoc} */
public abstract E next();
/** {@inheritDoc} */
public void remove() {
if (keyReturnedByNext == null) {
throw new IllegalStateException();
}
removeOverrideInternal(keyReturnedByNext);
keyReturnedByNext = null;
}
/**
* Returns the next entry or throws {@code NoSuchElementException} if
* there is no next entry.
*/
Entry<String, V> nextEntry() {
try {
if (!hasNext()) {
throw new NoSuchElementException();
}
keyReturnedByNext = nextName;
key = nextName;
return new KeyValuePair<V>(
prefix,
keyReturnedByNext.substring(prefix.length()));
} finally {
nextName = null;
}
}
/**
* Returns the next key or throws {@code NoSuchElementException} if
* there is no next key.
*/
String nextKey() {
try {
if (!hasNext()) {
throw new NoSuchElementException();
}
keyReturnedByNext = nextName;
key = nextName;
return keyReturnedByNext.substring(prefix.length());
} finally {
nextName = null;
}
}
/**
* Returns the next value or throws {@code NoSuchElementException} if
* there is no next value.
*/
V nextValue() {
try {
if (!hasNext()) {
throw new NoSuchElementException();
}
keyReturnedByNext = nextName;
key = nextName;
return getValue(keyReturnedByNext);
} finally {
nextName = null;
}
}
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
s.defaultReadObject();
dataService = BindingKeyedCollectionsImpl.getDataService();
}
@SuppressWarnings("unchecked")
private V getValue(String bindingName) {
ManagedObject v = dataService.getServiceBinding(bindingName);
return
v instanceof Wrapper ?
(V) ((Wrapper) v).get() :
(V) v;
}
}
/**
* An iterator over the entry set
*/
private static final class EntryIterator<V>
extends AbstractIterator<Entry<String, V>, V>
{
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
/**
* Constructs an instance with the given {@code keyPrefix}.
*
* @param keyPrefix a key prefix.
*/
EntryIterator(String keyPrefix) {
super(keyPrefix);
}
/**
* {@inheritDoc}
*/
public Entry<String, V> next() {
return nextEntry();
}
}
/**
* An iterator over the keys in the map.
*/
private static final class KeyIterator<V>
extends AbstractIterator<String, V>
{
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
/**
* Constructs an instance with the given {@code keyPrefix}.
*
* @param keyPrefix a key prefix.
*/
KeyIterator(String keyPrefix) {
super(keyPrefix);
}
/**
* {@inheritDoc}
*/
public String next() {
return nextKey();
}
}
/**
* An iterator over the values in the tree.
*/
static final class ValueIterator<V>
extends AbstractIterator<V, V>
{
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
/**
* Constructs an instance with the given {@code keyPrefix}.
*
* @param keyPrefix a key prefix.
*/
ValueIterator(String keyPrefix) {
super(keyPrefix);
}
/**
* {@inheritDoc}
*/
public V next() {
return nextValue();
}
}
/* -- Implement BindingKeyedMap -- */
/** {@inheritDoc} */
public String getKeyPrefix() {
return keyPrefix;
}
/** {@inheritDoc} */
public boolean putOverride(String key, V value) {
Objects.checkNull("key", key);
checkSerializable("value", value);
String bindingName = getBindingName(key);
boolean previouslyMapped = containsKeyInternal(bindingName);
if (previouslyMapped) {
try {
removeValue(bindingName);
} catch (ObjectNotFoundException e) {
}
}
putKeyValue(getBindingName(key), value);
return previouslyMapped;
}
/** {@inheritDoc} */
public boolean removeOverride(String key) {
Objects.checkNull("key", key);
return removeOverrideInternal(getBindingName(key));
}
/* -- Private classes and methods. -- */
/**
* A wrapper for a serializable, but not managed, object.
*/
private static final class Wrapper<V> extends ManagedSerializable<V> {
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
Wrapper(V obj) {
super(obj);
}
}
/**
* A serializable {@code Entry} used in entry sets for this map.
*/
private static final class KeyValuePair<V>
implements Entry<String, V>, Serializable
{
/** The serialVersionUID for this class. */
private static final long serialVersionUID = 1L;
private final String prefix;
private final String k;
KeyValuePair(String prefix, String key) {
this.prefix = prefix;
this.k = key;
}
/** {@inheritDoc} */
public String getKey() {
return k;
}
/** {@inheritDoc} */
public V getValue() {
return getValue(prefix + k);
}
/** {@inheritDoc} */
public V setValue(V value) {
checkSerializable("value", value);
String bindingName = prefix + k;
V previousValue = getValue(bindingName);
if (previousValue != null) {
removeValue(bindingName);
}
putKeyValue(bindingName, value);
return previousValue;
}
/** {@inheritDoc} */
public int hashCode() {
return getKey().hashCode() ^ getValue().hashCode();
}
/** {@inheritDoc} */
public boolean equals(Object o) {
if (o instanceof Entry) {
Entry entry = (Entry) o;
Object entryKey = entry.getKey();
Object entryValue = entry.getValue();
return
entryKey != null && getKey().equals(entryKey) &&
entryValue != null && getValue().equals(entryValue);
} else {
return false;
}
}
/** {@inheritDoc} */
public String toString() {
return k + "=" + getValue().toString();
}
@SuppressWarnings("unchecked")
private V getValue(String bindingName) {
try {
ManagedObject v = BindingKeyedCollectionsImpl.getDataService().
getServiceBinding(bindingName);
return
v instanceof Wrapper ?
(V) ((Wrapper) v).get() :
(V) v;
} catch (NameNotBoundException e) {
throw new IllegalStateException("entry has been removed");
}
}
}
/**
* Returns the binding name for the specified key name (i.e.,
* adds the key prefix to the key name).
*
* @param keyName a key name
* @return a binding name
*/
private String getBindingName(String keyName) {
return keyPrefix + keyName;
}
/**
* Returns {@code true} if there is no key with the given {@code keyPrefix},
* or the first key with the {@code keyPrefix} is the key stop.
*
* @param keyPrefix a key prefix
* @returns {@code true} if the {@code keyPrefix} corresponds to an
* empty map
*/
private static boolean isEmptyInternal(String keyPrefix) {
DataService dataService = BindingKeyedCollectionsImpl.getDataService();
String key = dataService.nextServiceBoundName(keyPrefix);
return key == null || !key.startsWith(keyPrefix);
}
/**
* Returns the size of the map with the specified {@code keyPrefix}.
*
* @param keyPrefix a key prefix
* @return the size of the map
*/
private static int sizeInternal(String keyPrefix) {
int size = 0;
Iterator<String> iter = new KeyIterator<Object>(keyPrefix);
while (iter.hasNext()) {
iter.next();
size++;
}
return size;
}
/**
* Clears the map with the specified {@code keyPrefix}.
*/
private static void clearInternal(String keyPrefix) {
Iterator<String> iter = new KeyIterator<Object>(keyPrefix);
while (iter.hasNext()) {
iter.next();
iter.remove();
}
}
/**
* Returns {@code true} if a service binding with the specified
* {@code bindingName} exists.
*/
private static boolean containsKeyInternal(String bindingName) {
DataService dataService = BindingKeyedCollectionsImpl.getDataService();
boolean containsKey = false;
try {
dataService.getServiceBinding(bindingName);
containsKey = true;
} catch (NameNotBoundException e) {
} catch (ObjectNotFoundException e) {
containsKey = true;
}
return containsKey;
}
/**
* If a service binding with the specified {@code bindingName} exists,
* removes the binding (and the wrapper for the associated value if
* applicable) and returns {@code true}. Otherwise, returns
* {@code false}.
*/
private static boolean removeOverrideInternal(String bindingName) {
boolean previouslyMapped = containsKeyInternal(bindingName);
if (previouslyMapped) {
DataService dataService =
BindingKeyedCollectionsImpl.getDataService();
try {
removeValue(bindingName);
} catch (ObjectNotFoundException ignore) {
}
dataService.removeServiceBinding(bindingName);
}
return previouslyMapped;
}
/**
* Removes the wrapper (if applicable) for the value associated with
* the specified {@code bindingName}.
*
* @throws NameNotBoundException if the service binding does not exist
* @throws ObjectNotFoundException if the value associated with the
* specified {@code bindingName} has been removed
*/
private static void removeValue(String bindingName) {
DataService dataService =
BindingKeyedCollectionsImpl.getDataService();
ManagedObject v = dataService.getServiceBinding(bindingName);
if (v instanceof Wrapper) {
dataService.removeObject(v);
}
}
/**
* Puts the specified {@code key}/{@code value} pair in this map,
* wrapping the value if the value does not implement {@code
* ManagedObject}. The caller is responsible for removing the wrapper
* for the old value, if applicable.
*
* @param key a key
* @param value a value
*/
private static void putKeyValue(String bindingName, Object value) {
assert value != null && value instanceof Serializable;
ManagedObject v =
value instanceof ManagedObject ?
(ManagedObject) value :
new Wrapper<Object>(value);
BindingKeyedCollectionsImpl.getDataService().
setServiceBinding(bindingName, v);
}
/**
* Returns the value associated with the specified {@code bindingName}.
* removing the wrapper if applicable.
*/
@SuppressWarnings("unchecked")
private V getValue(String bindingName) {
ManagedObject v =
BindingKeyedCollectionsImpl.getDataService().
getServiceBinding(bindingName);
return
v instanceof Wrapper ?
(V) ((Wrapper) v).get() :
(V) v;
}
/**
* Throws {@code IllegalArgumentException} of {@code obj} is not
* serializable.
*/
private static void checkSerializable(String name, Object obj) {
Objects.checkNull(name, obj);
if (!(obj instanceof Serializable)) {
throw new IllegalArgumentException(name + " not serializable");
}
}
/**
* Throws {@code ClassCastException} if (@code key) is not an instance
* of {@code String}.
*/
private static void checkKey(String keyName, Object key) {
Objects.checkNull(keyName, key);
if (!(key instanceof String)) {
throw new ClassCastException(
"key is not an instance of String: " +
key.getClass().getName());
}
}
}