/* * Copyright (C) 2021 Dominik Klumpp (klumpp@informatik.uni-freiburg.de) * Copyright (C) 2021 University of Freiburg * * This file is part of the ULTIMATE Util Library. * * The ULTIMATE Util Library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ULTIMATE Util Library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ULTIMATE Util Library. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE Util Library, or any covered work, by linking * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), * containing parts covered by the terms of the Eclipse Public License, the * licensors of the ULTIMATE Util Library grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.util.datastructures; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collector; import java.util.stream.Collector.Characteristics; import java.util.stream.Collectors; /** * Represents an immutable set, i.e., a set that cannot be modified. Immutable sets are suitable as elements of sets of * sets, or keys in hash maps. Immutability allows to perform calls to {@link #hashCode()} in O(1) time. * * @author Dominik Klumpp (klumpp@informatik.uni-freiburg.de) * * @param * The type of elements in the set */ public final class ImmutableSet implements Set { private static final String ERROR_MSG = "Set is immutable"; @SuppressWarnings("rawtypes") private static final ImmutableSet EMPTY_SET = new ImmutableSet<>(Collections.emptySet()); private final Set mUnderlying; private int mHash; private boolean mHashCached; /** * Creates a new immutable set, with the given underlying set of elements. The caller must guarantee to prevent any * modification of the underlying set. * * @param underlying * The underlying set of elements */ private ImmutableSet(final Set underlying) { mUnderlying = Objects.requireNonNull(underlying); } /** * Returns an empty, immutable set. * * @param * The element type * @return empty, immutable set */ @SuppressWarnings("unchecked") public static ImmutableSet empty() { return EMPTY_SET; } /** * Creates a new immutable singleton set. * * @param * The element type * @param elem * The single element in the set * @return the newly created set */ public static ImmutableSet singleton(final E elem) { return of(Collections.singleton(elem)); } /** * Creates a new immutable set, with the given underlying set of elements. The caller must guarantee to prevent any * modification of the underlying set. * * This method automatically prevents instances of {@link ImmutableSet} from being nested. * * @param set * The underlying set of elements * @return an immutable view of the given set */ public static ImmutableSet of(final Set set) { if (set.isEmpty()) { return empty(); } if (set instanceof ImmutableSet) { return (ImmutableSet) set; } return new ImmutableSet<>(set); } /** * Creates a new immutable set, with the given underlying set of elements. In contrast to * {@link ImmutableSet#ImmutableSet(Set)}, a copy is made, and hence the caller can make modifications to the given * set (that will not be reflected by the immutable copy). * * Beware that creating a copy of a set is performance-intensive. Thus, if a caller can guarantee that the given set * will never be modified, it is recommended to use {@link #of(Set)} instead. * * If the given set is already immutable, it is returned immediately and no copy is made. * * @param * The type of elements in the set * @param set * The set of which an immutable copy is made. * @return an immutable copy of the given set's current contents */ public static ImmutableSet copyOf(final Collection set) { if (set.isEmpty()) { return empty(); } if (set instanceof ImmutableSet) { return (ImmutableSet) set; } return new ImmutableSet<>(new HashSet<>(set)); } /** * Creates a new immutable set, with the given underlying set of elements, and preserving the iteration order. In * contrast to {@link ImmutableSet#ImmutableSet(Set)}, a copy is made, and hence the caller can make modifications * to the given set (that will not be reflected by the immutable copy). * * Beware that creating a copy of a set is performance-intensive. Thus, if a caller can guarantee that the given set * will never be modified, it is recommended to use {@link #of(Set)} instead. * * If the given set is already immutable, it is returned immediately and no copy is made. * * @param * The type of elements in the set * @param set * The set of which an immutable copy is made. * @return an immutable copy of the given set's current contents */ public static ImmutableSet orderedCopyOf(final Collection set) { if (set.isEmpty()) { return empty(); } if (set instanceof ImmutableSet) { return (ImmutableSet) set; } return new ImmutableSet<>(new LinkedHashSet<>(set)); } /** * Creates a new collector to collect elements of a stream to an immutable set. * * @param * The type of elements in the stream * @return the collector */ public static Collector> collector() { return collector(Collectors.toSet(), new Characteristics[] { Characteristics.UNORDERED }, true); } /** * Wraps an existing set collector in order to create an immutable set. The given collector can be used to customize * the type of the underlying set. * * @param * the type of stream elements * @param * An intermediate type used by the given collector * @param coll * the underlying set collector * @return A collector that wraps the set returned by the given collector in an immutable set */ public static > Collector> collector(final Collector> coll) { final Set characteristics = EnumSet.noneOf(Characteristics.class); if (coll.characteristics().contains(Characteristics.UNORDERED)) { characteristics.add(Characteristics.UNORDERED); } if (coll.characteristics().contains(Characteristics.CONCURRENT)) { characteristics.add(Characteristics.CONCURRENT); } final boolean elideFinish = coll.characteristics().contains(Characteristics.IDENTITY_FINISH); return collector(coll, characteristics.toArray(Characteristics[]::new), elideFinish); } private static > Collector> collector(final Collector> coll, final Characteristics[] characteristics, final boolean elideFinish) { final Function> finisher; if (elideFinish) { finisher = ImmutableSet::of; } else { finisher = coll.finisher().andThen(ImmutableSet::of); } return Collector.of(coll.supplier(), coll.accumulator(), coll.combiner(), finisher, characteristics); } @Override public boolean contains(final Object o) { return mUnderlying.contains(o); } @Override public boolean containsAll(final Collection c) { return mUnderlying.containsAll(c); } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj instanceof ImmutableSet) { // Optimized #equals for immutable sets final ImmutableSet other = (ImmutableSet) obj; if (mHashCached && other.mHashCached && mHash != other.mHash) { return false; } return mUnderlying.equals(other.mUnderlying); } // fall back to normal Set#equals return mUnderlying.equals(obj); } @Override public int hashCode() { if (!mHashCached) { mHash = mUnderlying.hashCode(); mHashCached = true; } assert mUnderlying.hashCode() == mHash : "Immutable set was modified"; return mHash; } @Override public boolean isEmpty() { return mUnderlying.isEmpty(); } @Override public Iterator iterator() { final Iterator it = mUnderlying.iterator(); return new Iterator<>() { @Override public boolean hasNext() { return it.hasNext(); } @Override public E next() { return it.next(); } @Override public void remove() { throw new UnsupportedOperationException(ERROR_MSG); } }; } @Override public int size() { return mUnderlying.size(); } @Override public Object[] toArray() { return mUnderlying.toArray(); } @Override public T[] toArray(final T[] a) { return mUnderlying.toArray(a); } @Override public String toString() { return mUnderlying.toString(); } // Mutating methods below @Override public boolean add(final E e) { throw new UnsupportedOperationException(ERROR_MSG); } @Override public boolean addAll(final Collection c) { throw new UnsupportedOperationException(ERROR_MSG); } @Override public void clear() { throw new UnsupportedOperationException(ERROR_MSG); } @Override public boolean remove(final Object o) { throw new UnsupportedOperationException(ERROR_MSG); } @Override public boolean removeAll(final Collection c) { throw new UnsupportedOperationException(ERROR_MSG); } @Override public boolean removeIf(final Predicate filter) { throw new UnsupportedOperationException(ERROR_MSG); } @Override public boolean retainAll(final Collection c) { throw new UnsupportedOperationException(ERROR_MSG); } }