package org.warp.commonutils.type; import com.google.common.collect.Streams; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.lang.reflect.Array; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.function.BiConsumer; import java.util.function.IntFunction; import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; public interface UnmodifiableIterableMap extends Iterable> { /** * Returns the number of key-value mappings in this map. If the * map contains more than {@code Integer.MAX_VALUE} elements, returns * {@code Integer.MAX_VALUE}. * * @return the number of key-value mappings in this map */ int size(); /** * Returns {@code true} if this map contains no key-value mappings. * * @return {@code true} if this map contains no key-value mappings */ boolean isEmpty(); /** * Performs the given action for each entry in this map until all entries * have been processed or the action throws an exception. Unless * otherwise specified by the implementing class, actions are performed in * the order of entry set iteration (if an iteration order is specified.) * Exceptions thrown by the action are relayed to the caller. * * @implSpec * The default implementation is equivalent to, for this {@code map}: *
 {@code
	 * for (Map.Entry entry : map.entrySet())
	 *     action.accept(entry.getKey(), entry.getValue());
	 * }
* * The default implementation makes no guarantees about synchronization * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. * * @param action The action to be performed for each entry * @throws NullPointerException if the specified action is null * @throws ConcurrentModificationException if an entry is found to be * removed during iteration * @since 1.8 */ void forEach(BiConsumer action); Map toUnmodifiableMap(); Stream> stream(); UnmodifiableIterableSet toUnmodifiableIterableKeysSet(IntFunction generator); @SuppressWarnings("SuspiciousSystemArraycopy") static UnmodifiableIterableMap ofObjects(Object[] keys, Object[] values) { if (keys == null || values == null || (keys.length == 0 && values.length == 0)) { return UnmodifiableIterableMap.of(null, null); } else if (keys.length == values.length) { //noinspection unchecked K[] keysArray = (K[]) Array.newInstance(keys[0].getClass(), keys.length); System.arraycopy(keys, 0, keysArray, 0, keys.length); //noinspection unchecked V[] valuesArray = (V[]) Array.newInstance(values[0].getClass(), keys.length); System.arraycopy(values, 0, valuesArray, 0, values.length); return UnmodifiableIterableMap.of(keysArray, valuesArray); } else { throw new IllegalArgumentException("The number of keys doesn't match the number of values."); } } static UnmodifiableIterableMap of(K[] keys, V[] values) { int keysSize = (keys != null) ? keys.length : 0; int valuesSize = (values != null) ? values.length : 0; if (keysSize == 0 && valuesSize == 0) { // return mutable map return new EmptyUnmodifiableIterableMap<>(); } if (keysSize != valuesSize) { throw new IllegalArgumentException("The number of keys doesn't match the number of values."); } return new ArrayUnmodifiableIterableMap<>(keys, values, keysSize); } class EmptyUnmodifiableIterableMap implements UnmodifiableIterableMap { private EmptyUnmodifiableIterableMap() {} @NotNull @Override public Iterator> iterator() { return new Iterator<>() { @Override public boolean hasNext() { return false; } @Override public Entry next() { throw new NoSuchElementException(); } }; } @Override public int size() { return 0; } @Override public boolean isEmpty() { return true; } @Override public void forEach(BiConsumer action) {} @Override public Map toUnmodifiableMap() { //noinspection unchecked return Object2ObjectMaps.EMPTY_MAP; } @Override public Stream> stream() { return Stream.empty(); } @Override public UnmodifiableIterableSet toUnmodifiableIterableKeysSet(IntFunction generator) { return UnmodifiableIterableSet.of(null); } } class ArrayUnmodifiableIterableMap implements UnmodifiableIterableMap { private final K[] keys; private final V[] values; private final int keysSize; private ArrayUnmodifiableIterableMap(K[] keys, V[] values, int keysSize) { this.keys = keys; this.values = values; this.keysSize = keysSize; } @NotNull @Override public Iterator> iterator() { return new Object2ObjectOpenHashMap(keys, values, 1.0f).entrySet().iterator(); } @Override public int size() { return keysSize; } @Override public boolean isEmpty() { return false; } @Override public void forEach(BiConsumer action) { for (int i = 0; i < keys.length; i++) { action.accept(keys[i], values[i]); } } @Override public Map toUnmodifiableMap() { return Object2ObjectMaps.unmodifiable(new Object2ObjectOpenHashMap<>(keys, values, 1.0f)); } @Override public Stream> stream() { //noinspection UnstableApiUsage return Streams.zip(Stream.of(keys), Stream.of(values), Map::entry); } @Override public UnmodifiableIterableSet toUnmodifiableIterableKeysSet(IntFunction generator) { return UnmodifiableIterableSet.of(keys); } } }