001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2014-2015 ForgeRock AS.
015 */
016package org.opends.server.types;
017
018import java.util.*;
019
020/**
021 * A small map of values. This map implementation is optimized to use as little
022 * memory as possible in the case where there zero or one elements. In addition,
023 * any normalization of entries is delayed until the second entry is added
024 * (normalization may be triggered by invoking {@link Object#hashCode()} or
025 * {@link Object#equals(Object)}.
026 * <p>
027 * Null keys are not supported by this map.
028 *
029 * @param <K>
030 *          the type of keys maintained by this map
031 * @param <V>
032 *          the type of mapped values
033 */
034public class SmallMap<K, V> extends AbstractMap<K, V>
035{
036
037  /** The map of entries if there are more than one. */
038  private LinkedHashMap<K, V> entries;
039
040  /** The first key of the Map. */
041  private K firstKey;
042  /** The first value of the Map. */
043  private V firstValue;
044
045
046  /** Creates a new small map which is initially empty. */
047  public SmallMap()
048  {
049    // No implementation required.
050  }
051
052  private void rejectIfNull(Object key)
053  {
054    if (key == null)
055    {
056      throw new NullPointerException("null keys are not allowed");
057    }
058  }
059
060  /** {@inheritDoc} */
061  @Override
062  public V get(Object key)
063  {
064    rejectIfNull(key);
065    if (entries != null)
066    { // >= 2 entries case
067      return entries.get(key);
068    }
069    // 0 and 1 case
070    if (firstKey != null && firstKey.equals(key))
071    {
072      return firstValue;
073    }
074    return null;
075  }
076
077  /** {@inheritDoc} */
078  @Override
079  public V put(K key, V value)
080  {
081    rejectIfNull(key);
082    if (entries != null)
083    { // >= 2 entries case
084      return entries.put(key, value);
085    }
086    if (firstKey == null)
087    { // 0 entries case
088      firstKey = key;
089      firstValue = value;
090      return null;
091    }
092    // 1 entry case
093    if (firstKey.equals(key))
094    { // replace value
095      V oldValue = firstValue;
096      firstValue = value;
097      return oldValue;
098    }
099    // overflow to the underlying map
100    entries = new LinkedHashMap<>(2);
101    entries.put(firstKey, firstValue);
102    firstKey = null;
103    firstValue = null;
104    return entries.put(key, value);
105  }
106
107  /** {@inheritDoc} */
108  @Override
109  public void putAll(Map<? extends K, ? extends V> m)
110  {
111    for (Entry<? extends K, ? extends V> entry : m.entrySet())
112    {
113      put(entry.getKey(), entry.getValue());
114    }
115  }
116
117  /** {@inheritDoc} */
118  @Override
119  public V remove(Object key)
120  {
121    if (entries != null)
122    {
123      // Note: if there is one or zero values left we could stop using the map.
124      // However, lets assume that if the map was multi-valued before
125      // then it may become multi-valued again.
126      return entries.remove(key);
127    }
128
129    if (firstKey != null && firstKey.equals(key))
130    {
131      V oldV = firstValue;
132      firstKey = null;
133      firstValue = null;
134      return oldV;
135    }
136    return null;
137  }
138
139  /** {@inheritDoc} */
140  @Override
141  public boolean containsKey(Object key)
142  {
143    rejectIfNull(key);
144    if (entries != null)
145    {
146      return entries.containsKey(key);
147    }
148    return firstKey != null && firstKey.equals(key);
149  }
150
151  /** {@inheritDoc} */
152  @Override
153  public boolean containsValue(Object value)
154  {
155    if (entries != null)
156    {
157      return entries.containsValue(value);
158    }
159    if (firstKey == null)
160    {
161      return false;
162    }
163    if (firstValue == null)
164    {
165      return value == null;
166    }
167    return firstValue.equals(value);
168  }
169
170  /** {@inheritDoc} */
171  @Override
172  public void clear()
173  {
174    firstKey = null;
175    firstValue = null;
176    entries = null;
177  }
178
179  /** {@inheritDoc} */
180  @Override
181  public int size()
182  {
183    if (entries != null)
184    {
185      return entries.size();
186    }
187    return firstKey != null ? 1 : 0;
188  }
189
190  /** {@inheritDoc} */
191  @Override
192  public Set<Entry<K, V>> entrySet()
193  {
194    if (entries != null)
195    {
196      return entries.entrySet();
197    }
198    if (firstKey == null)
199    {
200      return Collections.emptySet();
201    }
202
203    return new AbstractSet<Entry<K, V>>()
204    {
205
206      @Override
207      public Iterator<Entry<K, V>> iterator()
208      {
209        return new Iterator<Entry<K, V>>()
210        {
211
212          private boolean isFirst = true;
213
214          @Override
215          public boolean hasNext()
216          {
217            return isFirst;
218          }
219
220          @Override
221          public Entry<K, V> next()
222          {
223            if (!isFirst)
224            {
225              throw new NoSuchElementException();
226            }
227            isFirst = false;
228            return new SimpleEntry<>(firstKey, firstValue);
229          }
230
231          @Override
232          public void remove()
233          {
234            firstKey = null;
235            firstValue = null;
236          }
237        };
238      }
239
240      @Override
241      public int size()
242      {
243        return 1;
244      }
245    };
246  }
247
248}