* @return An isolated, immutable copy of the input map
*/
@Nonnull public static <K, V> Map<K, V> copyOf(@Nonnull final Map<K, V> m) {
- // Prevent a copy
- if (m instanceof ImmutableOffsetMap) {
+ // Prevent a copy. Note that ImmutableMap is not listed here because of its potentially larger keySet overhead.
+ if (m instanceof ImmutableOffsetMap || m instanceof SharedSingletonMap) {
return m;
}
- // Better-packed
+ // Familiar and efficient to copy
+ if (m instanceof MutableOffsetMap) {
+ return ((MutableOffsetMap<K, V>) m).toUnmodifiableMap();
+ }
+
final int size = m.size();
if (size == 0) {
+ // Shares a single object
return ImmutableMap.of();
}
if (size == 1) {
- return ImmutableMap.copyOf(m);
- }
-
- // Familiar and efficient
- if (m instanceof MutableOffsetMap) {
- return ((MutableOffsetMap<K, V>) m).toUnmodifiableMap();
+ // Efficient single-entry implementation
+ final Entry<K, V> e = m.entrySet().iterator().next();
+ return SharedSingletonMap.of(e.getKey(), e.getValue());
}
final Map<K, Integer> offsets = OffsetMapCache.offsetsFor(m.keySet());
this(Collections.<K>emptySet());
}
- public MutableOffsetMap(final Collection<K> keySet) {
+ protected MutableOffsetMap(final Collection<K> keySet) {
if (!keySet.isEmpty()) {
removed = keySet.size();
offsets = OffsetMapCache.offsetsFor(keySet);
}
protected MutableOffsetMap(final ImmutableOffsetMap<K, V> m) {
- this.offsets = m.offsets();
- this.objects = m.objects();
- this.newKeys = new LinkedHashMap<>();
+ this(m.offsets(), m.objects());
+ }
+
+ protected MutableOffsetMap(final Map<K, V> m) {
+ this(OffsetMapCache.offsetsFor(m.keySet()), m.values().toArray());
}
protected MutableOffsetMap(final MutableOffsetMap<K, V> m) {
this.removed = m.removed;
}
+ private MutableOffsetMap(final Map<K, Integer> offsets, final Object[] objects) {
+ this.offsets = Preconditions.checkNotNull(offsets);
+ this.objects = Preconditions.checkNotNull(objects);
+ this.newKeys = new LinkedHashMap<>();
+ }
+
+ public static <K, V> MutableOffsetMap<K, V> copyOf(final Map<K, V> m) {
+ if (m instanceof MutableOffsetMap) {
+ return ((MutableOffsetMap<K, V>) m).clone();
+ }
+ if (m instanceof ImmutableOffsetMap) {
+ return ((ImmutableOffsetMap<K, V>) m).toModifiableMap();
+ }
+
+ return new MutableOffsetMap<>(m);
+ }
+
+ public static <K, V> MutableOffsetMap<K, V> forOffsets(final Map<K, Integer> offsets) {
+ final Object[] objects = new Object[offsets.size()];
+ Arrays.fill(objects, NO_VALUE);
+
+ return new MutableOffsetMap<>(offsets, objects);
+ }
+
+ public static <K, V> MutableOffsetMap<K, V> forKeySet(final Collection<K> keySet) {
+ return forOffsets(OffsetMapCache.offsetsFor(keySet));
+ }
+
@Override
public final int size() {
return offsets.size() + newKeys.size() - removed;
}
@Override
- public MutableOffsetMap<K, V> clone() throws CloneNotSupportedException {
+ public MutableOffsetMap<K, V> clone() {
return new MutableOffsetMap<K, V>(this);
}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.util;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import java.io.Serializable;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Map;
+
+/**
+ * Implementation of the {@link Map} interface which stores a single mapping. The key set is shared among all instances
+ * which contain the same key. This implementation does not support null keys or values.
+ *
+ * @param <K> the type of keys maintained by this map
+ * @param <V> the type of mapped values
+ */
+@Beta
+public final class SharedSingletonMap<K, V> implements Serializable, UnmodifiableMapPhase<K, V> {
+ private static final long serialVersionUID = 1L;
+ private static final LoadingCache<Object, SingletonSet<Object>> CACHE = CacheBuilder.newBuilder().weakValues()
+ .build(new CacheLoader<Object, SingletonSet<Object>>() {
+ @Override
+ public SingletonSet<Object> load(final Object key) {
+ return SingletonSet.of(key);
+ }
+ });
+ private final SingletonSet<K> keySet;
+ private final V value;
+ private int hashCode;
+
+ @SuppressWarnings("unchecked")
+ private SharedSingletonMap(final K key, final V value) {
+ this.keySet = (SingletonSet<K>) CACHE.getUnchecked(key);
+ this.value = Preconditions.checkNotNull(value);
+ }
+
+ public static <K, V> SharedSingletonMap<K, V> of(final K key, final V value) {
+ return new SharedSingletonMap<>(key, value);
+ }
+
+ public static <K, V> SharedSingletonMap<K, V> copyOf(final Map<K, V> m) {
+ Preconditions.checkArgument(m.size() == 1);
+
+ final Entry<K, V> e = m.entrySet().iterator().next();
+ return new SharedSingletonMap<>(e.getKey(), e.getValue());
+ }
+
+ @Override
+ public ModifiableMapPhase<K, V> toModifiableMap() {
+ return new MutableOffsetMap<K, V>(this);
+ }
+
+ @Override
+ public SingletonSet<Entry<K, V>> entrySet() {
+ return SingletonSet.<Entry<K, V>>of(new SimpleImmutableEntry<>(keySet.getElement(), value));
+ }
+
+ @Override
+ public SingletonSet<K> keySet() {
+ return keySet;
+ }
+
+ @Override
+ public SingletonSet<V> values() {
+ return SingletonSet.of(value);
+ }
+
+ @Override
+ public boolean containsKey(final Object key) {
+ return keySet.contains(key);
+ }
+
+ @Override
+ public boolean containsValue(final Object value) {
+ return this.value.equals(value);
+ }
+
+ @Override
+ public V get(final Object key) {
+ return keySet.contains(key) ? value : null;
+ }
+
+ @Override
+ public int size() {
+ return 1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public V put(final K key, final V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V remove(final Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void putAll(final Map<? extends K, ? extends V> m) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ if (hashCode == 0) {
+ hashCode = keySet.getElement().hashCode() ^ value.hashCode();
+ }
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Map)) {
+ return false;
+ }
+
+ final Map<?, ?> m = (Map<?, ?>)obj;
+ return m.size() == 1 && value.equals(m.get(keySet.getElement()));
+ }
+
+ @Override
+ public String toString() {
+ return "{" + keySet.getElement() + '=' + value + '}';
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.util;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterators;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * A {@link Set} containing a single value. For some reason neither Java nor Guava provide direct access to the retained
+ * element -- which is desirable in some situations, as is the case in {@link SharedSingletonMap#entrySet()}.
+ */
+@Beta
+public abstract class SingletonSet<E> implements Set<E>, Immutable, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private static final SingletonSet<?> NULL_SINGLETON = new SingletonSet<Object>() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public boolean contains(final Object o) {
+ return o == null;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public Object getElement() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "[null]";
+ }
+
+ private Object readResolve() {
+ return NULL_SINGLETON;
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ public static <E> SingletonSet<E> of(@Nonnull final E element) {
+ if (element == null) {
+ return (SingletonSet<E>) NULL_SINGLETON;
+ }
+ return new RegularSingletonSet<E>(element);
+ }
+
+ public abstract E getElement();
+
+ @Override
+ public final int size() {
+ return 1;
+ }
+
+ @Override
+ public final boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public final Iterator<E> iterator() {
+ return Iterators.singletonIterator(getElement());
+ }
+
+ @Override
+ public final Object[] toArray() {
+ return new Object[] { getElement() };
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public final <T> T[] toArray(final T[] a) {
+ if (a.length > 0) {
+ a[0] = (T)getElement();
+ return a;
+ }
+
+ return (T[]) new Object[] { (T) getElement() };
+ }
+
+ @Override
+ public final boolean add(final E e) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public final boolean remove(final Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public final boolean containsAll(final Collection<?> c) {
+ if (c.isEmpty()) {
+ return true;
+ }
+ if (c.size() != 1) {
+ return false;
+ }
+
+ return otherContains(c);
+ }
+
+ @Override
+ public final boolean addAll(final Collection<? extends E> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public final boolean retainAll(final Collection<?> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public final boolean removeAll(final Collection<?> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public final void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public final boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Set)) {
+ return false;
+ }
+
+ final Set<?> s = (Set<?>)obj;
+ return s.size() == 1 && otherContains(s);
+ }
+
+ private boolean otherContains(final Collection<?> other) {
+ try {
+ return other.contains(getElement());
+ } catch (ClassCastException | NullPointerException e) {
+ return false;
+ }
+ }
+
+ private static final class RegularSingletonSet<E> extends SingletonSet<E> {
+ private static final long serialVersionUID = 1L;
+ private final E element;
+
+ RegularSingletonSet(final E element) {
+ this.element = Preconditions.checkNotNull(element);
+ }
+
+ @Override
+ public boolean contains(final Object o) {
+ return element.equals(o);
+ }
+
+ @Override
+ public E getElement() {
+ return element;
+ }
+
+ @Override
+ public int hashCode() {
+ return getElement().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "[" + element + ']';
+ }
+ }
+}
final Map<String, String> result = ImmutableOffsetMap.copyOf(source);
assertEquals(source, result);
- assertTrue(result instanceof ImmutableMap);
+ assertTrue(result instanceof SharedSingletonMap);
}
@Test
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+
+public class SharedSingletonMapTest {
+ private static UnmodifiableMapPhase<String, String> create() {
+ return SharedSingletonMap.of("k1", "v1");
+ }
+
+ @Test
+ public void testSimpleOperations() {
+ final Map<String, String> m = create();
+
+ assertFalse(m.isEmpty());
+ assertEquals(1, m.size());
+
+ assertTrue(m.containsKey("k1"));
+ assertFalse(m.containsKey(null));
+ assertFalse(m.containsKey("v1"));
+
+ assertTrue(m.containsValue("v1"));
+ assertFalse(m.containsValue(null));
+ assertFalse(m.containsValue("k1"));
+
+ assertEquals("v1", m.get("k1"));
+ assertNull(m.get(null));
+ assertNull(m.get("v1"));
+
+ assertFalse(m.equals(null));
+ assertTrue(m.equals(m));
+ assertFalse(m.equals(""));
+
+ final Map<String, String> same = Collections.singletonMap("k1", "v1");
+ assertEquals(same.toString(), m.toString());
+ assertTrue(same.equals(m));
+ assertTrue(m.equals(same));
+ assertEquals(same.entrySet(), m.entrySet());
+ assertEquals(same.values(), m.values());
+
+ // Perform twice to exercise the cache
+ assertEquals(same.hashCode(), m.hashCode());
+ assertEquals(same.hashCode(), m.hashCode());
+
+ assertFalse(m.equals(Collections.singletonMap(null, null)));
+ assertFalse(m.equals(Collections.singletonMap("k1", null)));
+ assertFalse(m.equals(Collections.singletonMap(null, "v1")));
+ assertFalse(m.equals(Collections.singletonMap("k1", "v2")));
+
+ final Set<String> set = m.keySet();
+ assertTrue(set instanceof SingletonSet);
+ assertTrue(set.contains("k1"));
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testClear() {
+ create().clear();
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testPut() {
+ create().put(null, null);
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testPutAll() {
+ create().putAll(Collections.singletonMap("", ""));
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testRemove() {
+ create().remove(null);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import java.util.Collections;
+import java.util.Iterator;
+import org.junit.Test;
+
+public class SingletonSetTest {
+ private static final String ELEMENT = "element";
+
+ private static SingletonSet<?> nullSet() {
+ return SingletonSet.of(null);
+ }
+
+ @Test
+ public void testNullSingleton() {
+ final SingletonSet<?> s = nullSet();
+
+ assertFalse(s.isEmpty());
+ assertEquals(1, s.size());
+ assertFalse(s.contains(""));
+ assertTrue(s.contains(null));
+ assertNull(s.getElement());
+ assertEquals(0, s.hashCode());
+ assertTrue(s.equals(Collections.singleton(null)));
+ assertFalse(s.equals(Collections.singleton("")));
+ assertFalse(s.equals(""));
+ assertTrue(s.equals(s));
+ assertFalse(s.equals(null));
+ assertEquals(Collections.singleton(null).toString(), s.toString());
+ }
+
+ @Test
+ public void testRegularSingleton() {
+ final SingletonSet<?> s = SingletonSet.of(ELEMENT);
+
+ assertFalse(s.isEmpty());
+ assertEquals(1, s.size());
+ assertFalse(s.contains(""));
+ assertFalse(s.contains(null));
+ assertTrue(s.contains(ELEMENT));
+
+ assertSame(ELEMENT, s.getElement());
+ assertEquals(ELEMENT.hashCode(), s.hashCode());
+ assertTrue(s.equals(Collections.singleton(ELEMENT)));
+ assertFalse(s.equals(Collections.singleton("")));
+ assertFalse(s.equals(Collections.singleton(null)));
+ assertFalse(s.equals(""));
+ assertTrue(s.equals(s));
+ assertFalse(s.equals(null));
+ assertEquals(Collections.singleton(ELEMENT).toString(), s.toString());
+ }
+
+ @Test
+ public void testIterator() {
+ final SingletonSet<?> s = SingletonSet.of(ELEMENT);
+ final Iterator<?> it = s.iterator();
+
+ assertTrue(it.hasNext());
+ assertSame(ELEMENT, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testRejectedAdd() {
+ final SingletonSet<?> s = nullSet();
+ s.add(null);
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testRejectedAddAll() {
+ final SingletonSet<?> s = nullSet();
+ s.addAll(null);
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testRejectedClear() {
+ final SingletonSet<?> s = nullSet();
+ s.clear();
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testRejectedRemove() {
+ final SingletonSet<?> s = nullSet();
+ s.remove(null);
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testRejectedRemoveAll() {
+ final SingletonSet<?> s = nullSet();
+ s.removeAll(null);
+ }
+
+ @Test(expected=UnsupportedOperationException.class)
+ public void testRejectedRetainAll() {
+ final SingletonSet<?> s = nullSet();
+ s.retainAll(null);
+ }
+}
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.io.Serializable;
import org.opendaylight.yangtools.concepts.Path;
import org.opendaylight.yangtools.util.HashCodeBuilder;
import org.opendaylight.yangtools.util.ImmutableOffsetMap;
+import org.opendaylight.yangtools.util.SharedSingletonMap;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
public NodeIdentifierWithPredicates(final QName node, final Map<QName, Object> keyValues) {
super(node);
- // Retains ImmutableMap for maps with size() <= 1. For larger sizes uses a shared key set.
+ // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set.
this.keyValues = ImmutableOffsetMap.copyOf(keyValues);
}
public NodeIdentifierWithPredicates(final QName node, final QName key, final Object value) {
- this(node, ImmutableMap.of(key, value));
+ this(node, SharedSingletonMap.of(key, value));
}
public Map<QName, Object> getKeyValues() {