From 5eb89147746f7bd91f29f3e45d3e85ca04bba9db Mon Sep 17 00:00:00 2001
From: Jean-Noël Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Fri, 09 Sep 2016 13:21:34 +0000
Subject: [PATCH] OPENDJ-3281 Move SmallSet into its own file

---
 opendj-server-legacy/src/main/java/org/opends/server/types/AttributeBuilder.java                    |  264 ----------------
 opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalMultiple.java |   98 ++----
 opendj-core/src/test/java/com/forgerock/opendj/util/SmallSetTest.java                               |  264 +++++++++++++++++
 opendj-core/src/main/java/com/forgerock/opendj/util/SmallSet.java                                   |  252 ++++++++++++++++
 4 files changed, 563 insertions(+), 315 deletions(-)

diff --git a/opendj-core/src/main/java/com/forgerock/opendj/util/SmallSet.java b/opendj-core/src/main/java/com/forgerock/opendj/util/SmallSet.java
new file mode 100644
index 0000000..df3c2ff
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/util/SmallSet.java
@@ -0,0 +1,252 @@
+/*
+ * 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.
+ * <p>
+ * 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 <E>
+ *          The type of elements to be contained in this small set.
+ */
+public final class SmallSet<E> extends AbstractSet<E> {
+    /**
+     * The set of elements if there are more than one.
+     * <p>
+     * It is implemented using a {@link Map} to be able to retrieve quickly
+     * the actual value with {@link Map#get(Object)}.
+     * <p>
+     * See the {@link #get(Object)} and {@link #addOrReplace(Object)} methods.
+     */
+    private LinkedHashMap<E, E> 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<? extends E> 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<? extends E> 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<E, E> map, E e) {
+        if (map.containsKey(e)) {
+            return false;
+        }
+        return map.put(e, e) == null;
+    }
+
+    private boolean addAllForbidsReplace(LinkedHashMap<E, E> map, Collection<? extends E> 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.
+     * <p>
+     * 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<E> iterator() {
+        if (elements != null) {
+            return elements.keySet().iterator();
+        } else if (firstElement != null) {
+            return new Iterator<E>() {
+                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.<E> 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;
+        }
+    }
+}
diff --git a/opendj-core/src/test/java/com/forgerock/opendj/util/SmallSetTest.java b/opendj-core/src/test/java/com/forgerock/opendj/util/SmallSetTest.java
new file mode 100644
index 0000000..2efb851
--- /dev/null
+++ b/opendj-core/src/test/java/com/forgerock/opendj/util/SmallSetTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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 2016 ForgeRock AS.
+ */
+package com.forgerock.opendj.util;
+
+import static java.util.Locale.*;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+
+import org.testng.annotations.Test;
+
+/** Tests for the {@link SmallSet} class. */
+@SuppressWarnings("javadoc")
+public class SmallSetTest extends UtilTestCase {
+    // @Checkstyle:off
+    private final CaseInsentiveString _a = c("a");
+    private final CaseInsentiveString _A = c("A");
+    private final CaseInsentiveString _b = c("b");
+    private final CaseInsentiveString _c = c("c");
+    private final CaseInsentiveString _d = c("d");
+    // @Checkstyle:on
+
+    @Test
+    public void testFirstElement() throws Exception {
+        SmallSet<String> set = new SmallSet<>();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.contains("d")).isFalse();
+        assertThat(set.remove("d")).isFalse();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.add("a")).isTrue();
+        assertThat(set).hasSize(1)
+                       .containsExactly("a");
+        assertThat(set.contains("a")).isTrue();
+        assertThat(set.contains("d")).isFalse();
+        assertThat(set.add("a")).isFalse();
+        assertThat(set).hasSize(1)
+                       .containsExactly("a");
+        assertThat(set.remove("c")).isFalse();
+        assertThat(set).hasSize(1)
+                       .containsExactly("a");
+        assertThat(set.remove("a")).isTrue();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+    }
+
+    @Test
+    public void testElements() throws Exception {
+        SmallSet<String> set = new SmallSet<>();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.add("a")).isTrue();
+        assertThat(set).hasSize(1)
+                       .containsExactly("a");
+        assertThat(set.add("a")).isFalse();
+        assertThat(set).hasSize(1)
+                       .containsExactly("a");
+        assertThat(set.add("b")).isTrue();
+        assertThat(set).hasSize(2)
+                       .containsExactly("a", "b");
+        assertThat(set.add("c")).isTrue();
+        assertThat(set).hasSize(3)
+                       .containsExactly("a", "b", "c");
+        assertThat(set.contains("a")).isTrue();
+        assertThat(set.contains("d")).isFalse();
+
+        assertThat(set.remove("d")).isFalse();
+        assertThat(set).hasSize(3)
+                       .containsExactly("a", "b", "c");
+        assertThat(set.remove("b")).isTrue();
+        assertThat(set).hasSize(2)
+                       .containsExactly("a", "c");
+        assertThat(set.remove("a")).isTrue();
+        assertThat(set).hasSize(1)
+                       .containsExactly("c");
+        assertThat(set.remove("c")).isTrue();
+
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+    }
+
+    @Test
+    public void testFirstElementWithNormalizedValues() throws Exception {
+        SmallSet<CaseInsentiveString> set = new SmallSet<>();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.get(_d)).isNull();
+        assertThat(set.remove(_c)).isFalse();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.add(_a)).isTrue();
+        assertThat(set).hasSize(1)
+                       .containsExactly(_a);
+        assertThat(set.get(_a)).isSameAs(_a);
+        assertThat(set.get(_d)).isNull();
+        assertThat(set.add(_A)).isFalse();
+        assertThat(set.get(_a)).isSameAs(_a);
+        assertThat(set).hasSize(1)
+                       .containsExactly(_a);
+        set.addOrReplace(_A);
+        assertThat(set).hasSize(1)
+                       .containsExactly(_a);
+        set.addOrReplace(_A);
+        assertThat(set).hasSize(1)
+                       .containsExactly(_A);
+        assertThat(set.remove(_c)).isFalse();
+        assertThat(set).hasSize(1)
+                       .containsExactly(_a);
+        assertThat(set.remove(_a)).isTrue();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+    }
+
+    @Test
+    public void testElementsWithNormalizedValues() throws Exception {
+        SmallSet<CaseInsentiveString> set = new SmallSet<>();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.add(c("a"))).isTrue();
+        assertThat(set).hasSize(1)
+                       .containsExactly(c("a"));
+        assertThat(set.add(c("a"))).isFalse();
+        assertThat(set).hasSize(1)
+                       .containsExactly(c("a"));
+        assertThat(set.add(c("b"))).isTrue();
+        assertThat(set).hasSize(2)
+                       .containsExactly(c("a"), c("b"));
+        assertThat(set.add(c("c"))).isTrue();
+        assertThat(set).hasSize(3)
+                       .containsExactly(c("a"), c("b"), c("c"));
+
+        assertThat(set.remove(c("d"))).isFalse();
+        assertThat(set).hasSize(3)
+                       .containsExactly(c("a"), c("b"), c("c"));
+        assertThat(set.remove(c("b"))).isTrue();
+        assertThat(set).hasSize(2)
+                       .containsExactly(c("a"), c("c"));
+        assertThat(set.remove(c("a"))).isTrue();
+        assertThat(set).hasSize(1)
+                       .containsExactly(c("c"));
+        assertThat(set.remove(c("c"))).isTrue();
+
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+    }
+
+    @Test
+    public void testElementsWithNormalizedValuesSpecificMethods() throws Exception {
+        final SmallSet<CaseInsentiveString> set = new SmallSet<>();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.addAll(newLinkedHashSet(_a, _b, _c))).isTrue();
+        assertThat(set).hasSize(3)
+                       .containsExactly(_a, _b, _c);
+
+        assertThat(set.add(_a)).isFalse();
+        assertThat(set.get(_a)).isSameAs(_a);
+
+        assertThat(set.add(_A)).isFalse();
+        assertThat(set.get(_a)).isSameAs(_a);
+
+        assertThat(set.get(_d)).isNull();
+
+        set.addOrReplace(_a);
+        assertThat(set).hasSize(3)
+                       .containsExactly(_b, _c, _a);
+        assertThat(set.get(_a)).isSameAs(_a);
+
+        set.addOrReplace(_A);
+        assertThat(set).hasSize(3)
+                       .containsExactly(_b, _c, _A);
+        assertThat(set.get(_a)).isSameAs(_A);
+    }
+
+    @Test
+    public void testAddAllOnInitiallyEmptySet() {
+        SmallSet<String> set = new SmallSet<>();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.addAll(SmallSetTest.<String> newLinkedHashSet())).isFalse();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.addAll(newLinkedHashSet("a"))).isTrue();
+        assertThat(set).hasSize(1)
+                       .containsExactly("a");
+        set.clear();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.addAll(newLinkedHashSet("a", "b", "c"))).isTrue();
+        assertThat(set).hasSize(3)
+                       .containsExactly("a", "b", "c");
+    }
+
+    @Test
+    public void testAddAllOnFirstElementAndAllElements() {
+        SmallSet<String> set = new SmallSet<>();
+        assertThat(set).hasSize(0)
+                       .isEmpty();
+        assertThat(set.add("a")).isTrue();
+        assertThat(set).hasSize(1)
+                       .containsExactly("a");
+        assertThat(set.addAll(newLinkedHashSet("a", "b", "c"))).isTrue();
+        assertThat(set).hasSize(3)
+                       .containsExactly("a", "b", "c");
+        assertThat(set.addAll(newLinkedHashSet("a", "b", "c"))).isFalse();
+        assertThat(set).hasSize(3)
+                       .containsExactly("a", "b", "c");
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <E> LinkedHashSet<E> newLinkedHashSet(E... elements) {
+        return new LinkedHashSet<>(Arrays.asList(elements));
+    }
+
+    private static CaseInsentiveString c(String s) {
+        return new CaseInsentiveString(s);
+    }
+
+    private static final class CaseInsentiveString {
+        private String s;
+
+        public CaseInsentiveString(String s) {
+            this.s = s;
+        }
+
+        private String lowerCase() {
+            return s.toLowerCase(ROOT);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof CaseInsentiveString) {
+                return lowerCase().equals(((CaseInsentiveString) obj).lowerCase());
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return lowerCase().hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return s;
+        }
+    }
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalMultiple.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalMultiple.java
index 463a382..6b70441 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalMultiple.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalMultiple.java
@@ -17,8 +17,6 @@
 package org.opends.server.replication.plugin;
 
 import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
 import java.util.Set;
 
 import org.forgerock.opendj.ldap.ByteString;
@@ -30,6 +28,8 @@
 import org.opends.server.types.Entry;
 import org.opends.server.types.Modification;
 
+import com.forgerock.opendj.util.SmallSet;
+
 /**
  * This class is used to store historical information for multiple valued attributes.
  * One object of this type is created for each attribute that was changed in the entry.
@@ -42,16 +42,8 @@
   private CSN deleteTime;
   /** Last time the attribute was modified. */
   private CSN lastUpdateTime;
-  /**
-   * Change history for the values of this attribute.
-   * <p>
-   * We are using a LinkedHashMap here because we want:
-   * <ol>
-   * <li>Fast access for removing/adding a AttrValueHistorical keyed by the attribute value => Use a Map</li>
-   * <li>Ordering changes according to the CSN of each changes => Use a LinkedHashMap</li>
-   * </ol>
-   */
-  private final Map<AttrValueHistorical, AttrValueHistorical> valuesHist = new LinkedHashMap<>();
+  /** Change history for the values of this attribute. */
+  private final SmallSet<AttrValueHistorical> valuesHist = new SmallSet<>();
 
    /**
     * Create a new object from the provided information.
@@ -59,15 +51,13 @@
     * @param updateTime the last time this attribute was updated
     * @param valuesHist the new attribute values when updated.
     */
-   public AttrHistoricalMultiple(CSN deleteTime,
-       CSN updateTime,
-       Map<AttrValueHistorical,AttrValueHistorical> valuesHist)
+   AttrHistoricalMultiple(CSN deleteTime, CSN updateTime, Set<AttrValueHistorical> valuesHist)
    {
      this.deleteTime = deleteTime;
      this.lastUpdateTime = updateTime;
      if (valuesHist != null)
      {
-       this.valuesHist.putAll(valuesHist);
+      this.valuesHist.addAll(valuesHist);
      }
    }
 
@@ -82,7 +72,7 @@
     * Returns the last time when the attribute was updated.
     * @return the last time when the attribute was updated
     */
-   private CSN getLastUpdateTime()
+   CSN getLastUpdateTime()
    {
      return lastUpdateTime;
    }
@@ -94,18 +84,6 @@
    }
 
    /**
-    * Duplicate an object. CSNs are duplicated by references.
-    * <p>
-    * Method only called in tests
-    *
-    * @return the duplicated object.
-    */
-   AttrHistoricalMultiple duplicate()
-   {
-     return new AttrHistoricalMultiple(this.deleteTime, this.lastUpdateTime, this.valuesHist);
-   }
-
-   /**
     * Delete all historical information that is older than the provided CSN for
     * this attribute type.
     * Add the delete attribute state information
@@ -115,8 +93,7 @@
    {
      // iterate through the values in the valuesInfo and suppress all the values
      // that have not been added after the date of this delete.
-     Iterator<AttrValueHistorical> it = valuesHist.keySet().iterator();
-     while (it.hasNext())
+     for (Iterator<AttrValueHistorical> it = valuesHist.iterator(); it.hasNext();)
      {
        AttrValueHistorical info = it.next();
        if (csn.isNewerThanOrEqualTo(info.getValueUpdateTime()) &&
@@ -185,39 +162,34 @@
     }
   }
 
-   /**
-     * Update the historical information when a value is added.
-     *
-     * @param addedValue
-     *          values that was added
-     * @param attrType
-     * @param csn
-     *          time when the value was added
-     */
-   void add(ByteString addedValue, AttributeType attrType, CSN csn)
-   {
-     update(csn, new AttrValueHistorical(addedValue, attrType, csn, null));
-   }
+  /**
+   * Update the historical information when a value is added.
+   *
+   * @param addedValue
+   *          the added value
+   * @param attrType
+   *          the attribute type of the added value
+   * @param csn
+   *          time when the value was added
+   */
+  void add(ByteString addedValue, AttributeType attrType, CSN csn)
+  {
+    update(csn, new AttrValueHistorical(addedValue, attrType, csn, null));
+  }
 
   private void update(CSN csn, AttrValueHistorical valInfo)
   {
-    updateValInfo(valInfo, valInfo);
+    valuesHist.addOrReplace(valInfo);
     if (csn.isNewerThan(lastUpdateTime))
     {
       lastUpdateTime = csn;
     }
   }
 
-  private void updateValInfo(AttrValueHistorical oldValInfo, AttrValueHistorical newValInfo)
-  {
-    valuesHist.remove(oldValInfo);
-    valuesHist.put(newValInfo, newValInfo);
-  }
-
   @Override
   public Set<AttrValueHistorical> getValuesHistorical()
   {
-    return valuesHist.keySet();
+    return valuesHist;
   }
 
   @Override
@@ -394,7 +366,7 @@
       m.setModificationType(ModificationType.REPLACE);
       AttributeBuilder builder = new AttributeBuilder(modAttr.getAttributeDescription());
 
-      for (Iterator<AttrValueHistorical> it = valuesHist.keySet().iterator(); it.hasNext();)
+      for (Iterator<AttrValueHistorical> it = valuesHist.iterator(); it.hasNext();)
       {
         AttrValueHistorical valInfo = it.next();
 
@@ -440,7 +412,11 @@
         // update historical information
         AttrValueHistorical valInfo = new AttrValueHistorical(val, attrType, null, csn);
         AttrValueHistorical oldValInfo = valuesHist.get(valInfo);
-        if (oldValInfo != null)
+        if (oldValInfo == null)
+        {
+          valuesHist.add(valInfo);
+        }
+        else
         {
           // this value already exist in the historical information
           if (csn.equals(oldValInfo.getValueUpdateTime()))
@@ -452,17 +428,13 @@
           if (csn.isNewerThanOrEqualTo(oldValInfo.getValueDeleteTime()) &&
               csn.isNewerThanOrEqualTo(oldValInfo.getValueUpdateTime()))
           {
-            updateValInfo(oldValInfo, valInfo);
+            valuesHist.addOrReplace(valInfo);
           }
           else if (oldValInfo.isUpdate())
           {
             deleteIt = false;
           }
         }
-        else
-        {
-          updateValInfo(oldValInfo, valInfo);
-        }
 
         /* if the attribute value is not to be deleted
          * or if attribute value is not present suppress it from the
@@ -536,7 +508,7 @@
          * add it in the historical information
          * let the operation process normally
          */
-        valuesHist.put(valInfo, valInfo);
+        valuesHist.add(valInfo);
       }
       else
       {
@@ -549,7 +521,7 @@
            */
           if (csn.isNewerThan(oldValInfo.getValueUpdateTime()))
           {
-            updateValInfo(oldValInfo, valInfo);
+            valuesHist.addOrReplace(valInfo);
           }
           builder.remove(addVal);
         }
@@ -560,7 +532,7 @@
            */
           if (csn.isNewerThanOrEqualTo(oldValInfo.getValueDeleteTime()))
           {
-            updateValInfo(oldValInfo, valInfo);
+            valuesHist.addOrReplace(valInfo);
           }
           else
           {
@@ -642,7 +614,7 @@
       }
       sb.append("lastUpdateTime=").append(lastUpdateTime);
     }
-    sb.append(", valuesHist=").append(valuesHist.keySet());
+    sb.append(", valuesHist=").append(valuesHist);
     sb.append(")");
     return sb.toString();
   }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/AttributeBuilder.java b/opendj-server-legacy/src/main/java/org/opends/server/types/AttributeBuilder.java
index b597c97..cf619e2 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/AttributeBuilder.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/AttributeBuilder.java
@@ -16,13 +16,9 @@
  */
 package org.opends.server.types;
 
-import java.util.AbstractSet;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.NoSuchElementException;
 import java.util.Set;
 
 import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -38,6 +34,8 @@
 import org.opends.server.types.Attribute.RemoveOnceSwitchingAttributes;
 import org.opends.server.util.CollectionUtils;
 
+import com.forgerock.opendj.util.SmallSet;
+
 /**
  * This class provides an interface for creating new non-virtual
  * {@link Attribute}s, or "real" attributes.
@@ -96,7 +94,6 @@
 @RemoveOnceSwitchingAttributes
 public final class AttributeBuilder implements Iterable<ByteString>
 {
-
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
   /** A real attribute */
@@ -154,9 +151,8 @@
         catch (Exception e)
         {
           logger.traceException(e);
-          // We couldn't normalize one of the attribute values. If we
-          // can't find a definite match, then we should return
-          // "undefined".
+          // We could not normalize one of the attribute values.
+          // If we cannot find a definite match, then we should return "undefined".
           result = ConditionResult.UNDEFINED;
         }
       }
@@ -233,8 +229,8 @@
         catch (Exception e)
         {
           logger.traceException(e);
-          // We couldn't normalize one of the attribute values. If we
-          // can't find a definite match, then we should return "undefined".
+          // We could not normalize one of the attribute values.
+          // If we cannot find a definite match, then we should return "undefined".
           result = ConditionResult.UNDEFINED;
         }
       }
@@ -289,8 +285,8 @@
         {
           logger.traceException(e);
 
-          // We couldn't normalize one of the attribute values. If we
-          // can't find a definite match, then we should return "undefined".
+          // We could not normalize one of the attribute values.
+          // If we cannot find a definite match, then we should return "undefined".
           result = ConditionResult.UNDEFINED;
         }
       }
@@ -335,8 +331,8 @@
         {
           logger.traceException(e);
 
-          // The value couldn't be normalized. If we can't find a
-          // definite match, then we should return "undefined".
+          // The value could not be normalized.
+          // If we cannot find a definite match, then we should return "undefined".
           result = ConditionResult.UNDEFINED;
         }
       }
@@ -344,8 +340,6 @@
       return result;
     }
 
-
-
     @Override
     public final int size()
     {
@@ -375,239 +369,6 @@
   }
 
   /**
-   * 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 <T>
-   *          The type of elements to be contained in this small set.
-   */
-  private static final class SmallSet<T> extends AbstractSet<T>
-  {
-
-    /** The set of elements if there are more than one. */
-    private LinkedHashSet<T> elements;
-
-    /** The first element. */
-    private T firstElement;
-
-    /**
-     * Creates a new small set which is initially empty.
-     */
-    public SmallSet()
-    {
-      // No implementation required.
-    }
-
-    /**
-     * 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 LinkedHashSet<>(initialCapacity);
-      }
-    }
-
-    @Override
-    public boolean add(T e)
-    {
-      // Special handling for the first value. This 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 LinkedHashSet<>(2);
-
-        // Move the first value into the set.
-        elements.add(firstElement);
-        firstElement = null;
-      }
-
-      return elements.add(e);
-    }
-
-    @Override
-    public boolean addAll(Collection<? extends T> c)
-    {
-      if (elements != null)
-      {
-        return elements.addAll(c);
-      }
-
-      if (firstElement != null)
-      {
-        elements = new LinkedHashSet<>(1 + c.size());
-        elements.add(firstElement);
-        firstElement = null;
-        return elements.addAll(c);
-      }
-
-      // Initially empty.
-      switch (c.size())
-      {
-      case 0:
-        // Do nothing.
-        return false;
-      case 1:
-        firstElement = c.iterator().next();
-        return true;
-      default:
-        elements = new LinkedHashSet<>(c);
-        return true;
-      }
-    }
-
-    @Override
-    public void clear()
-    {
-      firstElement = null;
-      elements = null;
-    }
-
-    @Override
-    public Iterator<T> iterator()
-    {
-      if (elements != null)
-      {
-        return elements.iterator();
-      }
-      else if (firstElement != null)
-      {
-        return new Iterator<T>()
-        {
-          private boolean hasNext = true;
-
-          @Override
-          public boolean hasNext()
-          {
-            return hasNext;
-          }
-
-          @Override
-          public T next()
-          {
-            if (!hasNext)
-            {
-              throw new NoSuchElementException();
-            }
-
-            hasNext = false;
-            return firstElement;
-          }
-
-          @Override
-          public void remove()
-          {
-            throw new UnsupportedOperationException();
-          }
-
-        };
-      }
-      else
-      {
-        return Collections.<T> 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.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.contains(o);
-      }
-
-      return firstElement != null && firstElement.equals(o);
-    }
-
-    /**
-     * Sets the initial capacity of this small set. If this small set
-     * already contains elements or if its capacity has already been
-     * defined then an {@link IllegalStateException} is thrown.
-     *
-     * @param initialCapacity
-     *          The initial capacity of this small set.
-     * @throws IllegalStateException
-     *           If this small set already contains elements or if its
-     *           capacity has already been defined.
-     */
-    public void setInitialCapacity(int initialCapacity)
-        throws IllegalStateException
-    {
-      Reject.ifFalse(initialCapacity >= 0);
-
-      if (elements != null)
-      {
-        throw new IllegalStateException();
-      }
-
-      if (initialCapacity > 1)
-      {
-        elements = new LinkedHashSet<>(initialCapacity);
-      }
-    }
-
-    @Override
-    public int size()
-    {
-      if (elements != null)
-      {
-        return elements.size();
-      }
-      else if (firstElement != null)
-      {
-        return 1;
-      }
-      else
-      {
-        return 0;
-      }
-    }
-  }
-
-  /**
    * An attribute value which is lazily normalized.
    * <p>
    * Stores the value in user-provided form and a reference to the associated
@@ -755,7 +516,7 @@
   /** The attribute description for this attribute. */
   private AttributeDescription attributeDescription;
   /** The set of attribute values, which are lazily normalized. */
-  private Set<AttributeValue> values = new SmallSet<>();
+  private SmallSet<AttributeValue> values = new SmallSet<>();
 
   /**
    * Creates a new attribute builder with an undefined attribute type
@@ -880,8 +641,7 @@
       // AttributeValue is already present, but the user-provided value may be different
       // There is no direct way to check this, so remove and add to ensure
       // the last user-provided value is recorded
-      values.remove(value);
-      values.add(value);
+      values.addOrReplace(value);
     }
     return isNewValue;
   }

--
Gitblit v1.10.0