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}