/* * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.util; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.Array; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.UnaryOperator; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.VM; import jdk.internal.vm.annotation.Stable; /** * Container class for immutable collections. Not part of the public API. * Mainly for namespace management and shared infrastructure. * * Serial warnings are suppressed throughout because all implementation * classes use a serial proxy and thus have no need to declare serialVersionUID. */ @SuppressWarnings("serial") class ImmutableCollections { /** * A "salt" value used for randomizing iteration order. This is initialized once * and stays constant for the lifetime of the JVM. It need not be truly random, but * it needs to vary sufficiently from one run to the next so that iteration order * will vary between JVM runs. */ private static final long SALT32L; /** * For set and map iteration, we will iterate in "reverse" stochastically, * decided at bootstrap time. */ private static final boolean REVERSE; static { // to generate a reasonably random and well-mixed SALT, use an arbitrary // value (a slice of pi), multiply with the System.nanoTime, then pick // the mid 32-bits from the product. By picking a SALT value in the // [0 ... 0xFFFF_FFFFL == 2^32-1] range, we ensure that for any positive // int N, (SALT32L * N) >> 32 is a number in the [0 ... N-1] range. This // property will be used to avoid more expensive modulo-based // calculations. long color = 0x243F_6A88_85A3_08D3L; // slice of pi long seed = System.nanoTime(); SALT32L = (int)((color * seed) >> 16) & 0xFFFF_FFFFL; // use the lowest bit to determine if we should reverse iteration REVERSE = (SALT32L & 1) == 0; } /** * Constants following this might be initialized from the CDS archive via * this array. */ private static Object[] archivedObjects; private static final Object EMPTY; static final ListN EMPTY_LIST; static final SetN EMPTY_SET; static final MapN EMPTY_MAP; static { VM.initializeFromArchive(ImmutableCollections.class); if (archivedObjects == null) { EMPTY = new Object(); EMPTY_LIST = new ListN<>(); EMPTY_SET = new SetN<>(); EMPTY_MAP = new MapN<>(); archivedObjects = new Object[] { EMPTY, EMPTY_LIST, EMPTY_SET, EMPTY_MAP }; } else { EMPTY = archivedObjects[0]; EMPTY_LIST = (ListN)archivedObjects[1]; EMPTY_SET = (SetN)archivedObjects[2]; EMPTY_MAP = (MapN)archivedObjects[3]; } } /** No instances. */ private ImmutableCollections() { } /** * The reciprocal of load factor. Given a number of elements * to store, multiply by this factor to get the table size. */ static final int EXPAND_FACTOR = 2; static UnsupportedOperationException uoe() { return new UnsupportedOperationException(); } static abstract class AbstractImmutableCollection extends AbstractCollection { // all mutating methods throw UnsupportedOperationException @Override public boolean add(E e) { throw uoe(); } @Override public boolean addAll(Collection c) { throw uoe(); } @Override public void clear() { throw uoe(); } @Override public boolean remove(Object o) { throw uoe(); } @Override public boolean removeAll(Collection c) { throw uoe(); } @Override public boolean removeIf(Predicate filter) { throw uoe(); } @Override public boolean retainAll(Collection c) { throw uoe(); } } // ---------- List Implementations ---------- // make a copy, short-circuiting based on implementation class @SuppressWarnings("unchecked") static List listCopy(Collection coll) { if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) { return (List)coll; } else { return (List)List.of(coll.toArray()); } } static abstract class AbstractImmutableList extends AbstractImmutableCollection implements List, RandomAccess { // all mutating methods throw UnsupportedOperationException @Override public void add(int index, E element) { throw uoe(); } @Override public boolean addAll(int index, Collection c) { throw uoe(); } @Override public E remove(int index) { throw uoe(); } @Override public void replaceAll(UnaryOperator operator) { throw uoe(); } @Override public E set(int index, E element) { throw uoe(); } @Override public void sort(Comparator c) { throw uoe(); } @Override public List subList(int fromIndex, int toIndex) { int size = size(); subListRangeCheck(fromIndex, toIndex, size); return SubList.fromList(this, fromIndex, toIndex); } static void subListRangeCheck(int fromIndex, int toIndex, int size) { if (fromIndex < 0) throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); if (toIndex > size) throw new IndexOutOfBoundsException("toIndex = " + toIndex); if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); } @Override public Iterator iterator() { return new ListItr(this, size()); } @Override public ListIterator listIterator() { return listIterator(0); } @Override public ListIterator listIterator(final int index) { int size = size(); if (index < 0 || index > size) { throw outOfBounds(index); } return new ListItr(this, size, index); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof List)) { return false; } Iterator oit = ((List) o).iterator(); for (int i = 0, s = size(); i < s; i++) { if (!oit.hasNext() || !get(i).equals(oit.next())) { return false; } } return !oit.hasNext(); } @Override public int indexOf(Object o) { Objects.requireNonNull(o); for (int i = 0, s = size(); i < s; i++) { if (o.equals(get(i))) { return i; } } return -1; } @Override public int lastIndexOf(Object o) { Objects.requireNonNull(o); for (int i = size() - 1; i >= 0; i--) { if (o.equals(get(i))) { return i; } } return -1; } @Override public int hashCode() { int hash = 1; for (int i = 0, s = size(); i < s; i++) { hash = 31 * hash + get(i).hashCode(); } return hash; } @Override public boolean contains(Object o) { return indexOf(o) >= 0; } IndexOutOfBoundsException outOfBounds(int index) { return new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); } } static final class ListItr implements ListIterator { @Stable private final List list; @Stable private final int size; @Stable private final boolean isListIterator; private int cursor; ListItr(List list, int size) { this.list = list; this.size = size; this.cursor = 0; isListIterator = false; } ListItr(List list, int size, int index) { this.list = list; this.size = size; this.cursor = index; isListIterator = true; } public boolean hasNext() { return cursor != size; } public E next() { try { int i = cursor; E next = list.get(i); cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { throw new NoSuchElementException(); } } public void remove() { throw uoe(); } public boolean hasPrevious() { if (!isListIterator) { throw uoe(); } return cursor != 0; } public E previous() { if (!isListIterator) { throw uoe(); } try { int i = cursor - 1; E previous = list.get(i); cursor = i; return previous; } catch (IndexOutOfBoundsException e) { throw new NoSuchElementException(); } } public int nextIndex() { if (!isListIterator) { throw uoe(); } return cursor; } public int previousIndex() { if (!isListIterator) { throw uoe(); } return cursor - 1; } public void set(E e) { throw uoe(); } public void add(E e) { throw uoe(); } } static final class SubList extends AbstractImmutableList implements RandomAccess { @Stable private final List root; @Stable private final int offset; @Stable private final int size; private SubList(List root, int offset, int size) { this.root = root; this.offset = offset; this.size = size; } /** * Constructs a sublist of another SubList. */ static SubList fromSubList(SubList parent, int fromIndex, int toIndex) { return new SubList<>(parent.root, parent.offset + fromIndex, toIndex - fromIndex); } /** * Constructs a sublist of an arbitrary AbstractImmutableList, which is * not a SubList itself. */ static SubList fromList(List list, int fromIndex, int toIndex) { return new SubList<>(list, fromIndex, toIndex - fromIndex); } public E get(int index) { Objects.checkIndex(index, size); return root.get(offset + index); } public int size() { return size; } public Iterator iterator() { return new ListItr<>(this, size()); } public ListIterator listIterator(int index) { rangeCheck(index); return new ListItr<>(this, size(), index); } public List subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return SubList.fromSubList(this, fromIndex, toIndex); } private void rangeCheck(int index) { if (index < 0 || index > size) { throw outOfBounds(index); } } @Override public Object[] toArray() { Object[] array = new Object[size]; for (int i = 0; i < size; i++) { array[i] = get(i); } return array; } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { T[] array = a.length >= size ? a : (T[])java.lang.reflect.Array .newInstance(a.getClass().getComponentType(), size); for (int i = 0; i < size; i++) { array[i] = (T)get(i); } if (array.length > size) { array[size] = null; // null-terminate } return array; } } static final class List12 extends AbstractImmutableList implements Serializable { @Stable private final E e0; @Stable private final Object e1; List12(E e0) { this.e0 = Objects.requireNonNull(e0); // Use EMPTY as a sentinel for an unused element: not using null // enable constant folding optimizations over single-element lists this.e1 = EMPTY; } List12(E e0, E e1) { this.e0 = Objects.requireNonNull(e0); this.e1 = Objects.requireNonNull(e1); } @Override public int size() { return e1 != EMPTY ? 2 : 1; } @Override public boolean isEmpty() { return false; } @Override @SuppressWarnings("unchecked") public E get(int index) { if (index == 0) { return e0; } else if (index == 1 && e1 != EMPTY) { return (E)e1; } throw outOfBounds(index); } @java.io.Serial private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("not serial proxy"); } @java.io.Serial private Object writeReplace() { if (e1 == EMPTY) { return new CollSer(CollSer.IMM_LIST, e0); } else { return new CollSer(CollSer.IMM_LIST, e0, e1); } } @Override public Object[] toArray() { if (e1 == EMPTY) { return new Object[] { e0 }; } else { return new Object[] { e0, e1 }; } } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { int size = size(); T[] array = a.length >= size ? a : (T[])Array.newInstance(a.getClass().getComponentType(), size); array[0] = (T)e0; if (size == 2) { array[1] = (T)e1; } if (array.length > size) { array[size] = null; // null-terminate } return array; } } static final class ListN extends AbstractImmutableList implements Serializable { @Stable private final E[] elements; @SafeVarargs ListN(E... input) { // copy and check manually to avoid TOCTOU @SuppressWarnings("unchecked") E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input for (int i = 0; i < input.length; i++) { tmp[i] = Objects.requireNonNull(input[i]); } elements = tmp; } @Override public boolean isEmpty() { return elements.length == 0; } @Override public int size() { return elements.length; } @Override public E get(int index) { return elements[index]; } @java.io.Serial private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("not serial proxy"); } @java.io.Serial private Object writeReplace() { return new CollSer(CollSer.IMM_LIST, elements); } @Override public Object[] toArray() { return Arrays.copyOf(elements, elements.length); } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { int size = elements.length; if (a.length < size) { // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elements, size, a.getClass()); } System.arraycopy(elements, 0, a, 0, size); if (a.length > size) { a[size] = null; // null-terminate } return a; } } // ---------- Set Implementations ---------- static abstract class AbstractImmutableSet extends AbstractImmutableCollection implements Set { @Override public boolean equals(Object o) { if (o == this) { return true; } else if (!(o instanceof Set)) { return false; } Collection c = (Collection) o; if (c.size() != size()) { return false; } for (Object e : c) { if (e == null || !contains(e)) { return false; } } return true; } @Override public abstract int hashCode(); } static final class Set12 extends AbstractImmutableSet implements Serializable { @Stable private final E e0; @Stable private final Object e1; Set12(E e0) { this.e0 = Objects.requireNonNull(e0); // Use EMPTY as a sentinel for an unused element: not using null // enable constant folding optimizations over single-element sets this.e1 = EMPTY; } Set12(E e0, E e1) { if (e0.equals(Objects.requireNonNull(e1))) { // implicit nullcheck of e0 throw new IllegalArgumentException("duplicate element: " + e0); } this.e0 = e0; this.e1 = e1; } @Override public int size() { return (e1 == EMPTY) ? 1 : 2; } @Override public boolean isEmpty() { return false; } @Override public boolean contains(Object o) { return o.equals(e0) || e1.equals(o); // implicit nullcheck of o } @Override public int hashCode() { return e0.hashCode() + (e1 == EMPTY ? 0 : e1.hashCode()); } @Override public Iterator iterator() { return new Iterator<>() { private int idx = (e1 == EMPTY) ? 1 : 2; @Override public boolean hasNext() { return idx > 0; } @Override @SuppressWarnings("unchecked") public E next() { if (idx == 1) { idx = 0; return (REVERSE || e1 == EMPTY) ? e0 : (E)e1; } else if (idx == 2) { idx = 1; return REVERSE ? (E)e1 : e0; } else { throw new NoSuchElementException(); } } }; } @java.io.Serial private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("not serial proxy"); } @java.io.Serial private Object writeReplace() { if (e1 == EMPTY) { return new CollSer(CollSer.IMM_SET, e0); } else { return new CollSer(CollSer.IMM_SET, e0, e1); } } @Override public Object[] toArray() { if (e1 == EMPTY) { return new Object[] { e0 }; } else if (REVERSE) { return new Object[] { e1, e0 }; } else { return new Object[] { e0, e1 }; } } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { int size = size(); T[] array = a.length >= size ? a : (T[])Array.newInstance(a.getClass().getComponentType(), size); if (size == 1) { array[0] = (T)e0; } else if (REVERSE) { array[0] = (T)e1; array[1] = (T)e0; } else { array[0] = (T)e0; array[1] = (T)e1; } if (array.length > size) { array[size] = null; // null-terminate } return array; } } /** * An array-based Set implementation. The element array must be strictly * larger than the size (the number of contained elements) so that at * least one null is always present. * @param the element type */ static final class SetN extends AbstractImmutableSet implements Serializable { @Stable final E[] elements; @Stable final int size; @SafeVarargs @SuppressWarnings("unchecked") SetN(E... input) { size = input.length; // implicit nullcheck of input elements = (E[])new Object[EXPAND_FACTOR * input.length]; for (int i = 0; i < input.length; i++) { E e = input[i]; int idx = probe(e); // implicit nullcheck of e if (idx >= 0) { throw new IllegalArgumentException("duplicate element: " + e); } else { elements[-(idx + 1)] = e; } } } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public boolean contains(Object o) { Objects.requireNonNull(o); return size > 0 && probe(o) >= 0; } private final class SetNIterator implements Iterator { private int remaining; private int idx; SetNIterator() { remaining = size; // pick a starting index in the [0 .. element.length-1] range // randomly based on SALT32L idx = (int) ((SALT32L * elements.length) >>> 32); } @Override public boolean hasNext() { return remaining > 0; } @Override public E next() { if (remaining > 0) { E element; int idx = this.idx; int len = elements.length; // step to the next element; skip null elements do { if (REVERSE) { if (++idx >= len) { idx = 0; } } else { if (--idx < 0) { idx = len - 1; } } } while ((element = elements[idx]) == null); this.idx = idx; remaining--; return element; } else { throw new NoSuchElementException(); } } } @Override public Iterator iterator() { return new SetNIterator(); } @Override public int hashCode() { int h = 0; for (E e : elements) { if (e != null) { h += e.hashCode(); } } return h; } // returns index at which element is present; or if absent, // (-i - 1) where i is location where element should be inserted. // Callers are relying on this method to perform an implicit nullcheck // of pe private int probe(Object pe) { int idx = Math.floorMod(pe.hashCode(), elements.length); while (true) { E ee = elements[idx]; if (ee == null) { return -idx - 1; } else if (pe.equals(ee)) { return idx; } else if (++idx == elements.length) { idx = 0; } } } @java.io.Serial private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("not serial proxy"); } @java.io.Serial private Object writeReplace() { Object[] array = new Object[size]; int dest = 0; for (Object o : elements) { if (o != null) { array[dest++] = o; } } return new CollSer(CollSer.IMM_SET, array); } @Override public Object[] toArray() { Object[] array = new Object[size]; Iterator it = iterator(); for (int i = 0; i < size; i++) { array[i] = it.next(); } return array; } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { T[] array = a.length >= size ? a : (T[])Array.newInstance(a.getClass().getComponentType(), size); Iterator it = iterator(); for (int i = 0; i < size; i++) { array[i] = (T)it.next(); } if (array.length > size) { array[size] = null; // null-terminate } return array; } } // ---------- Map Implementations ---------- abstract static class AbstractImmutableMap extends AbstractMap implements Serializable { @Override public void clear() { throw uoe(); } @Override public V compute(K key, BiFunction rf) { throw uoe(); } @Override public V computeIfAbsent(K key, Function mf) { throw uoe(); } @Override public V computeIfPresent(K key, BiFunction rf) { throw uoe(); } @Override public V merge(K key, V value, BiFunction rf) { throw uoe(); } @Override public V put(K key, V value) { throw uoe(); } @Override public void putAll(Map m) { throw uoe(); } @Override public V putIfAbsent(K key, V value) { throw uoe(); } @Override public V remove(Object key) { throw uoe(); } @Override public boolean remove(Object key, Object value) { throw uoe(); } @Override public V replace(K key, V value) { throw uoe(); } @Override public boolean replace(K key, V oldValue, V newValue) { throw uoe(); } @Override public void replaceAll(BiFunction f) { throw uoe(); } /** * @implNote {@code null} values are disallowed in these immutable maps, * so we can improve upon the default implementation since a * {@code null} return from {@code get(key)} always means the default * value should be returned. */ @Override public V getOrDefault(Object key, V defaultValue) { V v; return ((v = get(key)) != null) ? v : defaultValue; } } static final class Map1 extends AbstractImmutableMap { @Stable private final K k0; @Stable private final V v0; Map1(K k0, V v0) { this.k0 = Objects.requireNonNull(k0); this.v0 = Objects.requireNonNull(v0); } @Override public Set> entrySet() { return Set.of(new KeyValueHolder<>(k0, v0)); } @Override public V get(Object o) { return o.equals(k0) ? v0 : null; // implicit nullcheck of o } @Override public boolean containsKey(Object o) { return o.equals(k0); // implicit nullcheck of o } @Override public boolean containsValue(Object o) { return o.equals(v0); // implicit nullcheck of o } @Override public int size() { return 1; } @Override public boolean isEmpty() { return false; } @java.io.Serial private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("not serial proxy"); } @java.io.Serial private Object writeReplace() { return new CollSer(CollSer.IMM_MAP, k0, v0); } @Override public int hashCode() { return k0.hashCode() ^ v0.hashCode(); } } /** * An array-based Map implementation. There is a single array "table" that * contains keys and values interleaved: table[0] is kA, table[1] is vA, * table[2] is kB, table[3] is vB, etc. The table size must be even. It must * also be strictly larger than the size (the number of key-value pairs contained * in the map) so that at least one null key is always present. * @param the key type * @param the value type */ static final class MapN extends AbstractImmutableMap { @Stable final Object[] table; // pairs of key, value @Stable final int size; // number of pairs MapN(Object... input) { if ((input.length & 1) != 0) { // implicit nullcheck of input throw new InternalError("length is odd"); } size = input.length >> 1; int len = EXPAND_FACTOR * input.length; len = (len + 1) & ~1; // ensure table is even length table = new Object[len]; for (int i = 0; i < input.length; i += 2) { @SuppressWarnings("unchecked") K k = Objects.requireNonNull((K)input[i]); @SuppressWarnings("unchecked") V v = Objects.requireNonNull((V)input[i+1]); int idx = probe(k); if (idx >= 0) { throw new IllegalArgumentException("duplicate key: " + k); } else { int dest = -(idx + 1); table[dest] = k; table[dest+1] = v; } } } @Override public boolean containsKey(Object o) { Objects.requireNonNull(o); return size > 0 && probe(o) >= 0; } @Override public boolean containsValue(Object o) { Objects.requireNonNull(o); for (int i = 1; i < table.length; i += 2) { Object v = table[i]; if (v != null && o.equals(v)) { return true; } } return false; } @Override public int hashCode() { int hash = 0; for (int i = 0; i < table.length; i += 2) { Object k = table[i]; if (k != null) { hash += k.hashCode() ^ table[i + 1].hashCode(); } } return hash; } @Override @SuppressWarnings("unchecked") public V get(Object o) { if (size == 0) { Objects.requireNonNull(o); return null; } int i = probe(o); if (i >= 0) { return (V)table[i+1]; } else { return null; } } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } class MapNIterator implements Iterator> { private int remaining; private int idx; MapNIterator() { remaining = size; // pick an even starting index in the [0 .. table.length-1] // range randomly based on SALT32L idx = (int) ((SALT32L * (table.length >> 1)) >>> 32) << 1; } @Override public boolean hasNext() { return remaining > 0; } private int nextIndex() { int idx = this.idx; if (REVERSE) { if ((idx += 2) >= table.length) { idx = 0; } } else { if ((idx -= 2) < 0) { idx = table.length - 2; } } return this.idx = idx; } @Override public Map.Entry next() { if (remaining > 0) { int idx; while (table[idx = nextIndex()] == null) {} @SuppressWarnings("unchecked") Map.Entry e = new KeyValueHolder<>((K)table[idx], (V)table[idx+1]); remaining--; return e; } else { throw new NoSuchElementException(); } } } @Override public Set> entrySet() { return new AbstractSet<>() { @Override public int size() { return MapN.this.size; } @Override public Iterator> iterator() { return new MapNIterator(); } }; } // returns index at which the probe key is present; or if absent, // (-i - 1) where i is location where element should be inserted. // Callers are relying on this method to perform an implicit nullcheck // of pk. private int probe(Object pk) { int idx = Math.floorMod(pk.hashCode(), table.length >> 1) << 1; while (true) { @SuppressWarnings("unchecked") K ek = (K)table[idx]; if (ek == null) { return -idx - 1; } else if (pk.equals(ek)) { return idx; } else if ((idx += 2) == table.length) { idx = 0; } } } @java.io.Serial private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("not serial proxy"); } @java.io.Serial private Object writeReplace() { Object[] array = new Object[2 * size]; int len = table.length; int dest = 0; for (int i = 0; i < len; i += 2) { if (table[i] != null) { array[dest++] = table[i]; array[dest++] = table[i+1]; } } return new CollSer(CollSer.IMM_MAP, array); } } } // ---------- Serialization Proxy ---------- /** * A unified serialization proxy class for the immutable collections. * * @serial * @since 9 */ final class CollSer implements Serializable { @java.io.Serial private static final long serialVersionUID = 6309168927139932177L; static final int IMM_LIST = 1; static final int IMM_SET = 2; static final int IMM_MAP = 3; /** * Indicates the type of collection that is serialized. * The low order 8 bits have the value 1 for an immutable * {@code List}, 2 for an immutable {@code Set}, and 3 for * an immutable {@code Map}. Any other value causes an * {@link InvalidObjectException} to be thrown. The high * order 24 bits are zero when an instance is serialized, * and they are ignored when an instance is deserialized. * They can thus be used by future implementations without * causing compatibility issues. * *

The tag value also determines the interpretation of the * transient {@code Object[] array} field. * For {@code List} and {@code Set}, the array's length is the size * of the collection, and the array contains the elements of the collection. * Null elements are not allowed. For {@code Set}, duplicate elements * are not allowed. * *

For {@code Map}, the array's length is twice the number of mappings * present in the map. The array length is necessarily even. * The array contains a succession of key and value pairs: * {@code k1, v1, k2, v2, ..., kN, vN.} Nulls are not allowed, * and duplicate keys are not allowed. * * @serial * @since 9 */ private final int tag; /** * @serial * @since 9 */ private transient Object[] array; CollSer(int t, Object... a) { tag = t; array = a; } /** * Reads objects from the stream and stores them * in the transient {@code Object[] array} field. * * @serialData * A nonnegative int, indicating the count of objects, * followed by that many objects. * * @param ois the ObjectInputStream from which data is read * @throws IOException if an I/O error occurs * @throws ClassNotFoundException if a serialized class cannot be loaded * @throws InvalidObjectException if the count is negative * @since 9 */ @java.io.Serial private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); int len = ois.readInt(); if (len < 0) { throw new InvalidObjectException("negative length " + len); } SharedSecrets.getJavaObjectInputStreamAccess().checkArray(ois, Object[].class, len); Object[] a = new Object[len]; for (int i = 0; i < len; i++) { a[i] = ois.readObject(); } array = a; } /** * Writes objects to the stream from * the transient {@code Object[] array} field. * * @serialData * A nonnegative int, indicating the count of objects, * followed by that many objects. * * @param oos the ObjectOutputStream to which data is written * @throws IOException if an I/O error occurs * @since 9 */ @java.io.Serial private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeInt(array.length); for (int i = 0; i < array.length; i++) { oos.writeObject(array[i]); } } /** * Creates and returns an immutable collection from this proxy class. * The instance returned is created as if by calling one of the * static factory methods for * List, * Map, or * Set. * This proxy class is the serial form for all immutable collection instances, * regardless of implementation type. This is necessary to ensure that the * existence of any particular implementation type is kept out of the * serialized form. * * @return a collection created from this proxy object * @throws InvalidObjectException if the tag value is illegal or if an exception * is thrown during creation of the collection * @throws ObjectStreamException if another serialization error has occurred * @since 9 */ @java.io.Serial private Object readResolve() throws ObjectStreamException { try { if (array == null) { throw new InvalidObjectException("null array"); } // use low order 8 bits to indicate "kind" // ignore high order 24 bits switch (tag & 0xff) { case IMM_LIST: return List.of(array); case IMM_SET: return Set.of(array); case IMM_MAP: if (array.length == 0) { return ImmutableCollections.EMPTY_MAP; } else if (array.length == 2) { return new ImmutableCollections.Map1<>(array[0], array[1]); } else { return new ImmutableCollections.MapN<>(array); } default: throw new InvalidObjectException(String.format("invalid flags 0x%x", tag)); } } catch (NullPointerException|IllegalArgumentException ex) { InvalidObjectException ioe = new InvalidObjectException("invalid object"); ioe.initCause(ex); throw ioe; } } }