UnmodifiableMapBase.java
/*
* Copyright (C) 2018 Stein Eldar Johnsen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.morimekta.collect;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static java.util.Objects.requireNonNull;
import static net.morimekta.collect.UnmodifiableList.listOf;
/**
* Base implementation class for immutable maps.
*
* @param <K> The map key type.
* @param <V> The map value type.
*/
public abstract class UnmodifiableMapBase<K, V> implements Map<K, V> {
/**
* Get the map as a sorted map ordered by the specific comparator.
*
* @param comparator The comparator to make the map sorted for.
* @return The sorted map.
*/
public UnmodifiableSortedMap<K, V> orderedBy(Comparator<? super K> comparator) {
requireNonNull(comparator, "comparator == null");
if (this instanceof UnmodifiableSortedMap) {
@SuppressWarnings("rawtypes")
UnmodifiableSortedMap self = (UnmodifiableSortedMap) this;
if (comparator.equals(self.comparator()) ||
(self.comparator() == null && comparator.equals(Comparator.naturalOrder()))) {
return (UnmodifiableSortedMap<K, V>) this;
}
}
if (size == 0) {
return UnmodifiableSortedMap.sortedMapOf();
}
Entry<K, V>[] copy = Arrays.copyOf(entries, size);
Comparator<Entry<K, V>> entryComparator = UnmodifiableSortedMap.makeEntryComparator(comparator);
Arrays.sort(copy, entryComparator);
return new UnmodifiableSortedMap<>(size, copy, comparator, entryComparator);
}
/**
* Get a map with entries from this with the additional entry. The entry will overwrite
* if the key conflicts.
*
* @param key The entry key.
* @param value The entry value.
* @return The resulting map.
*/
public abstract Map<K, V> withEntry(K key, V value);
/**
* Get a map with entries from this with additional entries from provided map. Entries
* in the provided map will overwrite if key conflict.
*
* @param map Map with additional entries.
* @return The resulting map.
*/
public abstract Map<K, V> withEntries(Map<? extends K, ? extends V> map);
// -------- Map --------
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
// get(Object o) -> impl
// containsKey(Object o) -> impl
@Override
public boolean containsValue(Object o) {
for (int i = 0; i < size; ++i) {
if (entries[i].getValue().equals(o)) {
return true;
}
}
return false;
}
@Override
public Set<K> keySet() {
if (keySet == null) {
keySet = makeKeySet();
}
return keySet;
}
@Override
public Collection<V> values() {
if (values == null) {
if (size == 0) {
values = listOf();
} else {
Object[] tmp = new Object[size];
for (int i = 0; i < size; ++i) {
tmp[i] = entries[i].getValue();
}
values = new UnmodifiableList<>(0, size, tmp);
}
}
return values;
}
@Override
public Set<Entry<K, V>> entrySet() {
if (entrySet == null) {
entrySet = makeEntrySet();
}
return entrySet;
}
// -------- Object --------
@Override
public int hashCode() {
if (hashCode == null) {
int hash = Objects.hash(getClass(), size);
for (int i = 0; i < size; ++i) {
hash ^= Objects.hash(entries[i].getKey(), entries[i].getValue());
}
hashCode = hash;
}
return hashCode;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
for (int i = 0; i < size; ++i) {
Entry<K, V> entry = entries[i];
if (builder.length() > 1) {
builder.append(", ");
}
builder.append(entry.getKey());
builder.append(": ");
builder.append(entry.getValue());
}
return builder.append("}").toString();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Map)) {
return false;
}
@SuppressWarnings("unchecked")
Map<Object, Object> other = (Map<Object, Object>) obj;
if (other.size() != size) {
return false;
}
for (Entry<Object, Object> entry : other.entrySet()) {
if (entry.getValue() == null ||
!entry.getValue().equals(get(entry.getKey()))) {
return false;
}
}
return true;
}
// -------- Unsupported --------
@Override
public V put(K k, V v) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public V remove(Object o) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
throw new UnsupportedOperationException("Operation not allowed");
}
@Override
public void clear() {
throw new UnsupportedOperationException("Operation not allowed");
}
// -------- Private --------
UnmodifiableMapBase(int size, Entry<K, V>[] entries) {
this.entries = entries;
this.size = size;
}
static final Entry<Object, Object>[] NO_ENTRIES = makeEntryArray(0);
@SuppressWarnings("unchecked")
static <K, V> Entry<K, V>[] makeEntryArray(int len) {
return new Entry[len];
}
/**
* Entries in the map.
*/
final transient Entry<K, V>[] entries;
/**
* Size of the map. May be smaller than entry array.
*/
final transient int size;
abstract Set<Entry<K, V>> makeEntrySet();
abstract Set<K> makeKeySet();
static <K, V> Map.Entry<K, V> entry(K key, V value) {
return new AbstractMap.SimpleImmutableEntry<>(
requireNonNull(key, "key == null"),
requireNonNull(value, "value == null"));
}
private transient Integer hashCode;
private transient Set<K> keySet;
private transient List<V> values;
private transient Set<Entry<K, V>> entrySet;
}