All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.permazen.util.ImmutableNavigableMap Maven / Gradle / Ivy


/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.util;

import com.google.common.base.Preconditions;

import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Objects;

/**
 * An immutable {@link NavigableMap} implementation optimized for read efficiency.
 *
 * @param  key type
 * @param  value type
 */
@SuppressWarnings("serial")
public class ImmutableNavigableMap extends AbstractNavigableMap {

    private final K[] keys;
    private final V[] vals;
    private final int minIndex;
    private final int maxIndex;
    private final Comparator comparator;
    private final Comparator actualComparator;

    /**
     * Constructor.
     *
     * @param source data source
     * @throws IllegalArgumentException if {@code source} is null
     */
    @SuppressWarnings("unchecked")
    public ImmutableNavigableMap(NavigableMap source) {
        this((K[])source.keySet().toArray(), (V[])source.values().toArray(), source.comparator());
    }

    /**
     * Convenience constructor.
     *
     * 

* Equivalent to: {@code ImmutableNavigableMap(keys, vals, 0, Math.min(keys.length, vals.length), comparator)}. * * @param keys sorted key array; this array is not copied and must be already sorted * @param vals value array corresponding to {@code keys}; this array is not copied * @param comparator key comparator, or null for natural ordering * @throws IllegalArgumentException if {@code keys} or {@code vals} is null * @throws IllegalArgumentException if {@code keys} and {@code vals} have different lengths */ public ImmutableNavigableMap(K[] keys, V[] vals, Comparator comparator) { this(new Bounds<>(), keys, vals, 0, Math.min(keys.length, vals.length), comparator); } /** * Primary constructor. * * @param keys sorted key array; this array is not copied and must be already sorted * @param vals value array corresponding to {@code keys}; this array is not copied * @param minIndex minimum index into arrays (inclusive) * @param maxIndex maximum index into arrays (exclusive) * @param comparator key comparator, or null for natural ordering * @throws IllegalArgumentException if {@code keys} or {@code vals} is null * @throws IllegalArgumentException if {@code keys} and {@code vals} has length less than {@code maxIndex} * @throws IllegalArgumentException if {@code minIndex > maxIndex} */ public ImmutableNavigableMap(K[] keys, V[] vals, int minIndex, int maxIndex, Comparator comparator) { this(new Bounds<>(), keys, vals, minIndex, maxIndex, comparator); } @SuppressWarnings("unchecked") private ImmutableNavigableMap(Bounds bounds, K[] keys, V[] vals, int minIndex, int maxIndex, Comparator comparator) { super(bounds); Preconditions.checkArgument(keys != null); Preconditions.checkArgument(vals != null); Preconditions.checkArgument(minIndex >= 0 && maxIndex >= minIndex); Preconditions.checkArgument(keys.length >= maxIndex); Preconditions.checkArgument(vals.length >= maxIndex); this.keys = keys; this.vals = vals; this.minIndex = minIndex; this.maxIndex = maxIndex; this.comparator = comparator; this.actualComparator = this.comparator != null ? this.comparator : (Comparator)Comparator.naturalOrder(); for (int i = minIndex + 1; i < maxIndex; i++) assert this.actualComparator.compare(this.keys[i - 1], this.keys[i]) < 0; } /** * Search for the given element in the underlying array. * *

* This method works like {@link Arrays#binarySearch(Object[], Object) Arrays.binarySearch()}, returning * either the index of {@code key} in the underlying array given to the constructor if found, or else * the one's complement of {@code key}'s insertion point. * *

* The array searched is the array given to the constructor, or if {@link #ImmutableNavigableMap(NavigableMap)} * was used, an array containing all of the keys in this map. * * @param key key to search for * @return index of {@code key}, or {@code -(insertion point) - 1} if not found */ public int binarySearch(K key) { return this.find(key); } // NavigableSet @Override public Comparator comparator() { return this.comparator; } @Override public boolean isEmpty() { return this.minIndex == this.maxIndex; } @Override public int size() { return this.maxIndex - this.minIndex; } @Override public boolean containsKey(Object obj) { return this.find(obj) >= 0; } @Override @SuppressWarnings("unchecked") public V get(Object obj) { final int index = this.find(obj); return index >= 0 ? this.vals[index] : null; } @Override public List values() { List list = Arrays.asList(this.vals); if (this.minIndex > 0 || this.maxIndex < this.vals.length) list = list.subList(this.minIndex, this.maxIndex); return Collections.unmodifiableList(list); } @Override public boolean containsValue(Object obj) { for (int i = this.minIndex; i < this.maxIndex; i++) { if (Objects.equals(obj, this.vals[i])) return true; } return false; } @Override public K firstKey() { return this.keys[this.checkIndex(this.minIndex)]; } @Override public Map.Entry firstEntry() { return !this.isEmpty() ? this.createEntry(this.minIndex) : null; } @Override public K lastKey() { return this.keys[this.checkIndex(this.maxIndex - 1)]; } @Override public Map.Entry lastEntry() { return !this.isEmpty() ? this.createEntry(this.maxIndex - 1) : null; } @Override public K lowerKey(K maxKey) { return this.findKey(maxKey, -1, -1); } @Override public Map.Entry lowerEntry(K maxKey) { return this.findEntry(maxKey, -1, -1); } @Override public K floorKey(K maxKey) { return this.findKey(maxKey, -1, 0); } @Override public Map.Entry floorEntry(K maxKey) { return this.findEntry(maxKey, -1, 0); } @Override public K higherKey(K minKey) { return this.findKey(minKey, 0, 1); } @Override public Map.Entry higherEntry(K minKey) { return this.findEntry(minKey, 0, 1); } @Override public K ceilingKey(K minKey) { return this.findKey(minKey, 0, 0); } @Override public Map.Entry ceilingEntry(K minKey) { return this.findEntry(minKey, 0, 0); } @Override public Map.Entry pollFirstEntry() { throw new UnsupportedOperationException(); } @Override public Map.Entry pollLastEntry() { throw new UnsupportedOperationException(); } @Override public NavigableSet navigableKeySet() { return new ImmutableNavigableSet<>(this.bounds, this.keys, this.minIndex, this.maxIndex, this.comparator); } @Override @SuppressWarnings("unchecked") public ImmutableNavigableSet> entrySet() { final int size = this.size(); final AbstractMap.SimpleImmutableEntry[] entries = (AbstractMap.SimpleImmutableEntry[])new AbstractMap.SimpleImmutableEntry[size]; for (int i = this.minIndex; i < this.maxIndex; i++) entries[i - this.minIndex] = this.createEntry(i); return new ImmutableNavigableSet<>(entries, Comparator.comparing(Map.Entry::getKey, this.actualComparator)); } @Override protected ImmutableNavigableMap createSubMap(boolean reverse, Bounds newBounds) { // Get upper and lower bounds; note: "newBounds" are already reversed final K minBound = reverse ? newBounds.getUpperBound() : newBounds.getLowerBound(); final K maxBound = reverse ? newBounds.getLowerBound() : newBounds.getUpperBound(); final BoundType minBoundType = reverse ? newBounds.getUpperBoundType() : newBounds.getLowerBoundType(); final BoundType maxBoundType = reverse ? newBounds.getLowerBoundType() : newBounds.getUpperBoundType(); // Calculate the index range in our current array corresponding to the new bounds final int newMinIndex; switch (minBoundType) { case INCLUSIVE: newMinIndex = this.findNearby(minBound, 0, 0); break; case EXCLUSIVE: newMinIndex = this.findNearby(minBound, 0, 1); break; case NONE: newMinIndex = this.minIndex; break; default: throw new RuntimeException("internal error"); } final int newMaxIndex; switch (maxBoundType) { case INCLUSIVE: newMaxIndex = this.findNearby(maxBound, 0, 1); break; case EXCLUSIVE: newMaxIndex = this.findNearby(maxBound, 0, 0); break; case NONE: newMaxIndex = this.maxIndex; break; default: throw new RuntimeException("internal error"); } // Create new instance if (reverse) { final int newSize = newMaxIndex - newMinIndex; return new ImmutableNavigableMap(newBounds, ImmutableNavigableSet.reverseArray(Arrays.copyOfRange(this.keys, newMinIndex, newMaxIndex)), ImmutableNavigableSet.reverseArray(Arrays.copyOfRange(this.vals, newMinIndex, newMaxIndex)), 0, newSize, ImmutableNavigableSet.reversedComparator(this.comparator)); } else return new ImmutableNavigableMap(newBounds, this.keys, this.vals, newMinIndex, newMaxIndex, this.comparator); } private K findKey(Object key, int notFoundOffset, int foundOffset) { final int index = this.findNearby(key, notFoundOffset, foundOffset); if (index < this.minIndex || index >= this.maxIndex) return null; return this.keys[index]; } private Map.Entry findEntry(Object key, int notFoundOffset, int foundOffset) { final int index = this.findNearby(key, notFoundOffset, foundOffset); if (index < this.minIndex || index >= this.maxIndex) return null; return this.createEntry(index); } private int findNearby(Object key, int notFoundOffset, int foundOffset) { final int index = this.find(key); return index < 0 ? ~index + notFoundOffset : index + foundOffset; } @SuppressWarnings("unchecked") private int find(Object key) { return Arrays.binarySearch(this.keys, this.minIndex, this.maxIndex, (K)key, this.actualComparator); } private int checkIndex(final int index) { if (index < this.minIndex || index >= this.maxIndex) throw new NoSuchElementException(); return index; } private AbstractMap.SimpleImmutableEntry createEntry(int index) { assert index >= this.minIndex && index < this.maxIndex; return new AbstractMap.SimpleImmutableEntry<>(this.keys[index], this.vals[index]); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy