/* * 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 com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; 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.Map; import java.util.SequencedMap; import org.eclipse.jdt.annotation.NonNull; /** * 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. * *

* In case the set of keys is statically known, you can use {@link SharedSingletonMapTemplate} to efficiently create * {@link SharedSingletonMap} instances. * * @param the type of keys maintained by this map * @param the type of mapped values */ public abstract sealed class SharedSingletonMap implements Serializable, SequencedMap, UnmodifiableMapPhase { static final class Ordered extends SharedSingletonMap { @java.io.Serial private static final long serialVersionUID = 1L; Ordered(final K key, final V value) { super(key, value); } Ordered(final SingletonSet keySet, final V value) { super(keySet, value); } @Override public @NonNull ModifiableMapPhase toModifiableMap() { return MutableOffsetMap.orderedCopyOf(this); } } static final class Unordered extends SharedSingletonMap { @java.io.Serial private static final long serialVersionUID = 1L; Unordered(final K key, final V value) { super(key, value); } Unordered(final SingletonSet keySet, final V value) { super(keySet, value); } @Override public @NonNull ModifiableMapPhase toModifiableMap() { return MutableOffsetMap.unorderedCopyOf(this); } } @java.io.Serial private static final long serialVersionUID = 1L; private static final LoadingCache> CACHE = CacheBuilder.newBuilder().weakValues() .build(new CacheLoader>() { @Override public SingletonSet load(final Object key) { return SingletonSet.of(key); } }); private final @NonNull SingletonSet keySet; private final @NonNull V value; private int hashCode; private SharedSingletonMap(final SingletonSet keySet, final V value) { this.keySet = requireNonNull(keySet); this.value = requireNonNull(value); } private SharedSingletonMap(final K key, final V value) { this(cachedSet(key), value); } /** * Create a {@link SharedSingletonMap} of specified {@code key} and {@code value}, which retains insertion order * when transformed via {@link #toModifiableMap()}. * * @param key key * @param value value * @return A SharedSingletonMap * @throws NullPointerException if any of the arguments is null */ public static @NonNull SharedSingletonMap orderedOf(final K key, final V value) { return new Ordered<>(key, value); } /** * Create a {@link SharedSingletonMap} of specified {@code key} and {@code value}, which does not retain insertion * order when transformed via {@link #toModifiableMap()}. * * @param key key * @param value value * @return A SharedSingletonMap * @throws NullPointerException if any of the arguments is null */ public static @NonNull SharedSingletonMap unorderedOf(final K key, final V value) { return new Unordered<>(key, value); } /** * Create a {@link SharedSingletonMap} of specified {@code key} and {@code value}, which retains insertion order * when transformed via {@link #toModifiableMap()}. * * @param map input map * @return A SharedSingletonMap * @throws NullPointerException if {@code map} is null * @throws IllegalArgumentException if {@code map} does not have exactly one entry */ public static @NonNull SharedSingletonMap orderedCopyOf(final Map map) { final var entry = singleEntry(map); return new Ordered<>(entry.getKey(), entry.getValue()); } /** * Create a {@link SharedSingletonMap} from specified single-element map, which does not retain insertion order when * transformed via {@link #toModifiableMap()}. * * @param map input map * @return A SharedSingletonMap * @throws NullPointerException if {@code map} is null * @throws IllegalArgumentException if {@code map} does not have exactly one entry */ public static @NonNull SharedSingletonMap unorderedCopyOf(final Map map) { final var entry = singleEntry(map); return new Unordered<>(entry.getKey(), entry.getValue()); } @Deprecated(since = "14.0.0", forRemoval = true) public final Entry getEntry() { return firstEntry(); } @Override public final Entry firstEntry() { return Map.entry(keySet.getFirst(), value); } @Override public final Entry lastEntry() { return firstEntry(); } @Override public final @NonNull SharedSingletonMap reversed() { return this; } @Override public final @NonNull SingletonSet> entrySet() { return SingletonSet.of(firstEntry()); } @Override public final @NonNull SingletonSet> sequencedEntrySet() { return entrySet(); } @Override public final @NonNull SingletonSet keySet() { return keySet; } @Override public final @NonNull SingletonSet sequencedKeySet() { return keySet; } @Override public final @NonNull SingletonSet values() { return SingletonSet.of(value); } @Override public final @NonNull SingletonSet sequencedValues() { return values(); } @Override public final boolean containsKey(final Object key) { return keySet.contains(key); } @Override @SuppressWarnings("checkstyle:hiddenField") public final boolean containsValue(final Object value) { return this.value.equals(value); } @Override public final V get(final Object key) { return keySet.contains(key) ? value : null; } @Override public final int size() { return 1; } @Override public final boolean isEmpty() { return false; } @Override @SuppressWarnings("checkstyle:hiddenField") public final V put(final K key, final V value) { throw new UnsupportedOperationException(); } @Override public final V remove(final Object key) { throw new UnsupportedOperationException(); } @Override @SuppressWarnings("checkstyle:parameterName") public final void putAll(final Map m) { throw new UnsupportedOperationException(); } @Override public final void clear() { throw new UnsupportedOperationException(); } @Override public final Entry pollFirstEntry() { throw new UnsupportedOperationException(); } @Override public final Entry pollLastEntry() { throw new UnsupportedOperationException(); } @Override public final int hashCode() { if (hashCode == 0) { hashCode = keySet.getFirst().hashCode() ^ value.hashCode(); } return hashCode; } @Override public final boolean equals(final Object obj) { return this == obj || obj instanceof Map other && other.size() == 1 && value.equals(other.get(keySet.getFirst())); } @Override public final String toString() { return "{" + keySet.getFirst() + '=' + value + '}'; } @SuppressWarnings("unchecked") static @NonNull SingletonSet cachedSet(final K key) { return (SingletonSet) CACHE.getUnchecked(key); } private static Entry singleEntry(final Map map) { final var it = map.entrySet().iterator(); checkArgument(it.hasNext(), "Input map is empty"); final var ret = it.next(); checkArgument(!it.hasNext(), "Input map has more than one entry"); return ret; } }