/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2012-2016 ForgeRock AS. */ package com.forgerock.opendj.util; import java.util.AbstractSet; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.NoSuchElementException; import org.forgerock.util.Reject; /** * A small set of values. This set implementation is optimized to use * as little memory as possible in the case where there zero or one elements. *

* In addition, any normalization of elements is delayed until the second * element is added (normalization may be triggered by invoking * {@link Object#hashCode()} or {@link Object#equals(Object)}. * * @param * The type of elements to be contained in this small set. */ public final class SmallSet extends AbstractSet { /** * The set of elements if there are more than one. *

* It is implemented using a {@link Map} to be able to retrieve quickly * the actual value with {@link Map#get(Object)}. *

* See the {@link #get(Object)} and {@link #addOrReplace(Object)} methods. */ private LinkedHashMap elements; /** The first element. */ private E firstElement; /** Creates a new small set which is initially empty. */ public SmallSet() { // No implementation required. } /** * Creates a new small set from the provided collection. * * @param c * the collection whose elements are to be placed into this set * @throws java.lang.NullPointerException * if the specified collection is null */ public SmallSet(Collection c) { addAll(c); } /** * Creates a new small set with an initial capacity. * * @param initialCapacity * The capacity of the set */ public SmallSet(int initialCapacity) { Reject.ifFalse(initialCapacity >= 0); if (initialCapacity > 1) { elements = new LinkedHashMap<>(initialCapacity); } } @Override public boolean add(E e) { // Special handling for the first value which avoids potentially expensive normalization. if (firstElement == null && elements == null) { firstElement = e; return true; } // Create the value set if necessary. if (elements == null) { if (firstElement.equals(e)) { return false; } elements = new LinkedHashMap<>(2); addForbidsReplace(elements, firstElement); firstElement = null; } return addForbidsReplace(elements, e); } @Override public boolean addAll(Collection c) { if (elements != null) { return addAllForbidsReplace(elements, c); } if (firstElement != null && !c.isEmpty()) { elements = new LinkedHashMap<>(1 + c.size()); addForbidsReplace(elements, firstElement); firstElement = null; return addAllForbidsReplace(elements, c); } // Initially empty. switch (c.size()) { case 0: // Do nothing. return false; case 1: firstElement = c.iterator().next(); return true; default: elements = new LinkedHashMap<>(c.size()); addAllForbidsReplace(elements, c); return true; } } private boolean addForbidsReplace(LinkedHashMap map, E e) { if (map.containsKey(e)) { return false; } return map.put(e, e) == null; } private boolean addAllForbidsReplace(LinkedHashMap map, Collection c) { boolean modified = false; for (E e : c) { modified |= addForbidsReplace(map, e); } return modified; } /** * Adds an element to this small set or replaces the existing element's value with the provided * one. *

* The replacement aspect is useful when the small set uses a "normalized" value to compare * elements for equality but stores them as an "actual" value. * * @param element * the actual element to replace in this small set */ public void addOrReplace(E element) { remove(element); add(element); } @Override public void clear() { firstElement = null; elements = null; } @Override public Iterator iterator() { if (elements != null) { return elements.keySet().iterator(); } else if (firstElement != null) { return new Iterator() { private boolean hasNext = true; @Override public boolean hasNext() { return hasNext; } @Override public E next() { if (!hasNext) { throw new NoSuchElementException(); } hasNext = false; return firstElement; } @Override public void remove() { firstElement = null; } }; } else { return Collections. emptySet().iterator(); } } @Override public boolean remove(Object o) { if (elements != null) { // Note: if there is one or zero values left we could stop using the set. // However, lets assume that if the set was multi-valued before // then it may become multi-valued again. return elements.keySet().remove(o); } if (firstElement != null && firstElement.equals(o)) { firstElement = null; return true; } return false; } @Override public boolean contains(Object o) { if (elements != null) { return elements.containsKey(o); } return firstElement != null && firstElement.equals(o); } /** * Returns the element in this small set that is equal to the provided element. * * @param o * the element to look for * @return the element in this small set that is equal to the provided element, * or {@code null} if no such element exists */ public E get(Object o) { if (elements != null) { return elements.get(o); } return firstElement != null && firstElement.equals(o) ? firstElement : null; } @Override public int size() { if (elements != null) { return elements.size(); } else if (firstElement != null) { return 1; } else { return 0; } } }