/*
* Copyright 2015 Goldman Sachs.
*
* 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 com.gs.collections.impl.map.sorted.immutable;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import com.gs.collections.api.RichIterable;
import com.gs.collections.api.block.procedure.Procedure2;
import com.gs.collections.api.map.ImmutableMap;
import com.gs.collections.api.map.sorted.ImmutableSortedMap;
import com.gs.collections.api.map.sorted.MutableSortedMap;
import com.gs.collections.api.set.sorted.MutableSortedSet;
import com.gs.collections.api.tuple.Pair;
import com.gs.collections.impl.block.factory.Comparators;
import com.gs.collections.impl.block.factory.Predicates2;
import com.gs.collections.impl.factory.SortedMaps;
import com.gs.collections.impl.factory.SortedSets;
import com.gs.collections.impl.list.mutable.FastList;
import com.gs.collections.impl.set.sorted.mutable.TreeSortedSet;
import com.gs.collections.impl.tuple.ImmutableEntry;
import com.gs.collections.impl.utility.ArrayIterate;
import com.gs.collections.impl.utility.Iterate;
import com.gs.collections.impl.utility.MapIterate;
import net.jcip.annotations.Immutable;
/**
* @see ImmutableSortedMap
*/
@Immutable
public class ImmutableTreeMap<K, V>
extends AbstractImmutableSortedMap<K, V>
implements Serializable
{
private final K[] keys;
private final V[] values;
private final Comparator<? super K> comparator;
public ImmutableTreeMap(SortedMap<K, V> sortedMap)
{
if (sortedMap == null)
{
throw new NullPointerException("Cannot convert null to ImmutableSortedMap");
}
this.comparator = sortedMap.comparator();
K[] keysCopy = (K[]) new Object[sortedMap.size()];
V[] valuesCopy = (V[]) new Object[sortedMap.size()];
int index = 0;
for (Entry<K, V> entry : sortedMap.entrySet())
{
keysCopy[index] = entry.getKey();
valuesCopy[index] = entry.getValue();
index++;
}
this.keys = keysCopy;
this.values = valuesCopy;
}
public static <K, V> ImmutableSortedMap<K, V> newMap(SortedMap<K, V> sortedMap)
{
return new ImmutableTreeMap<K, V>(sortedMap);
}
@Override
public boolean equals(Object object)
{
if (this == object)
{
return true;
}
if (!(object instanceof Map))
{
return false;
}
Map<?, ?> other = (Map<?, ?>) object;
if (this.size() != other.size())
{
return false;
}
for (int index = 0; index < this.size(); index++)
{
if (!Comparators.nullSafeEquals(this.values[index], other.get(this.keys[index])))
{
return false;
}
}
return true;
}
@Override
public int hashCode()
{
int length = this.keys.length;
int hashCode = 0;
for (int index = 0; index < length; index++)
{
K key = this.keys[index];
V value = this.values[index];
hashCode += (key == null ? 0 : key.hashCode())
^ (value == null ? 0 : value.hashCode());
}
return hashCode;
}
@Override
public String toString()
{
int length = this.keys.length;
if (length == 0)
{
return "{}";
}
StringBuilder sb = new StringBuilder();
sb.append('{');
for (int index = 0; index < length; index++)
{
sb.append(this.keys[index]);
sb.append('=');
sb.append(this.values[index]);
if (index < length - 1)
{
sb.append(',').append(' ');
}
}
return sb.append('}').toString();
}
public int size()
{
return this.keys.length;
}
public boolean containsKey(Object key)
{
return Arrays.binarySearch(this.keys, (K) key, this.comparator) >= 0;
}
public boolean containsValue(Object value)
{
return ArrayIterate.contains(this.values, value);
}
public V get(Object key)
{
int index = Arrays.binarySearch(this.keys, (K) key, this.comparator);
if (index >= 0)
{
return this.values[index];
}
return null;
}
public void forEachKeyValue(Procedure2<? super K, ? super V> procedure)
{
for (int index = 0; index < this.keys.length; index++)
{
procedure.value(this.keys[index], this.values[index]);
}
}
public ImmutableMap<V, K> flipUniqueValues()
{
return MapIterate.flipUniqueValues(this).toImmutable();
}
public RichIterable<K> keysView()
{
return FastList.newListWith(this.keys).asLazy();
}
public RichIterable<V> valuesView()
{
return FastList.newListWith(this.values).asLazy();
}
public RichIterable<Pair<K, V>> keyValuesView()
{
return FastList.newListWith(this.keys).asLazy().zip(FastList.newListWith(this.values));
}
public Comparator<? super K> comparator()
{
return this.comparator;
}
public K firstKey()
{
if (this.keys.length == 0)
{
throw new NoSuchElementException();
}
return this.keys[0];
}
public K lastKey()
{
if (this.keys.length == 0)
{
throw new NoSuchElementException();
}
return this.keys[this.keys.length - 1];
}
public Set<K> keySet()
{
return new ImmutableSortedMapKeySet();
}
public Collection<V> values()
{
return FastList.newListWith(this.values).asUnmodifiable();
}
public Set<Entry<K, V>> entrySet()
{
int length = this.keys.length;
MutableSortedSet<Entry<K, V>> entrySet = SortedSets.mutable.with(new EntryComparator<K, V>(this.comparator));
for (int index = 0; index < length; index++)
{
entrySet.add(ImmutableEntry.of(this.keys[index], this.values[index]));
}
return entrySet.toImmutable().castToSortedSet();
}
protected Object writeReplace()
{
return new ImmutableSortedMapSerializationProxy<K, V>(this);
}
public ImmutableSortedMap<K, V> take(int count)
{
if (count < 0)
{
throw new IllegalArgumentException("Count must be greater than zero, but was: " + count);
}
if (count == 0)
{
return SortedMaps.immutable.of(this.comparator());
}
if (count >= this.size())
{
return this;
}
MutableSortedMap<K, V> output = SortedMaps.mutable.of(this.comparator());
for (int i = 0; i < count; i++)
{
output.put(this.keys[i], this.values[i]);
}
return output.toImmutable();
}
public ImmutableSortedMap<K, V> drop(int count)
{
if (count < 0)
{
throw new IllegalArgumentException("Count must be greater than zero, but was: " + count);
}
if (count == 0)
{
return this;
}
if (count >= this.size())
{
return SortedMaps.immutable.of(this.comparator());
}
MutableSortedMap<K, V> output = SortedMaps.mutable.of(this.comparator());
for (int i = 0; i < this.size(); i++)
{
if (i >= count)
{
output.put(this.keys[i], this.values[i]);
}
}
return output.toImmutable();
}
private static final class EntryComparator<K, V> implements Comparator<Entry<K, V>>
{
private final Comparator<? super K> comparator;
private EntryComparator(Comparator<? super K> comparator)
{
if (comparator == null)
{
this.comparator = Comparators.naturalOrder();
}
else
{
this.comparator = comparator;
}
}
public int compare(Entry<K, V> o1, Entry<K, V> o2)
{
return this.comparator.compare(o1.getKey(), o2.getKey());
}
}
protected class ImmutableSortedMapKeySet extends AbstractSet<K> implements Serializable
{
private static final long serialVersionUID = 1L;
@Override
public boolean contains(Object o)
{
return ImmutableTreeMap.this.containsKey(o);
}
@Override
public boolean containsAll(Collection<?> collection)
{
return Iterate.allSatisfyWith(collection, Predicates2.in(), this);
}
@Override
public int size()
{
return ImmutableTreeMap.this.keys.length;
}
@Override
public Iterator<K> iterator()
{
return FastList.newListWith(ImmutableTreeMap.this.keys).asUnmodifiable().iterator();
}
@Override
public Object[] toArray()
{
int size = ImmutableTreeMap.this.keys.length;
Object[] result = new Object[size];
System.arraycopy(ImmutableTreeMap.this.keys, 0, result, 0, size);
return result;
}
@Override
public <T> T[] toArray(T[] a)
{
T[] result = a.length < this.size()
? (T[]) Array.newInstance(a.getClass().getComponentType(), this.size())
: a;
System.arraycopy(ImmutableTreeMap.this.keys, 0, result, 0, ImmutableTreeMap.this.keys.length);
if (result.length > this.size())
{
result[this.size()] = null;
}
return result;
}
@Override
public boolean add(K key)
{
throw new UnsupportedOperationException("Cannot call add() on " + this.getClass().getSimpleName());
}
@Override
public boolean addAll(Collection<? extends K> collection)
{
throw new UnsupportedOperationException("Cannot call addAll() on " + this.getClass().getSimpleName());
}
@Override
public boolean remove(Object key)
{
throw new UnsupportedOperationException("Cannot call remove() on " + this.getClass().getSimpleName());
}
@Override
public boolean removeAll(Collection<?> collection)
{
throw new UnsupportedOperationException("Cannot call removeAll() on " + this.getClass().getSimpleName());
}
@Override
public void clear()
{
throw new UnsupportedOperationException("Cannot call clear() on " + this.getClass().getSimpleName());
}
@Override
public boolean retainAll(Collection<?> collection)
{
throw new UnsupportedOperationException("Cannot call retainAll() on " + this.getClass().getSimpleName());
}
protected Object writeReplace()
{
return TreeSortedSet.newSetWith(ImmutableTreeMap.this.comparator, ImmutableTreeMap.this.keys).toImmutable();
}
}
}