001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 * $Id$ 020 */ 021 022package org.nuxeo.common.collections; 023 024import java.util.Map; 025 026/** 027 * A mixture of an array list and a map used to store small table of elements using both indices and keys. 028 * <p> 029 * This map accepts null values. 030 * <p> 031 * The map is implemented using an array of successive [key, value] pairs. 032 * 033 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 034 */ 035@SuppressWarnings({ "ClassWithoutToString" }) 036public class ArrayMap<K, V> { 037 038 // 4 keys, 4 values 039 protected static final int DEFAULT_SIZE = 8; 040 041 protected static final int GROW_SIZE = 10; 042 043 protected int count = 0; 044 045 protected Object[] elements; 046 047 public ArrayMap() { 048 } 049 050 public ArrayMap(int initialCapacity) { 051 elements = new Object[initialCapacity == 0 ? DEFAULT_SIZE : initialCapacity * 2]; 052 } 053 054 public ArrayMap(Map<K, V> map) { 055 this(map.size()); 056 putAll(map); 057 } 058 059 public ArrayMap(ArrayMap<K, V> map) { 060 count = map.count; 061 elements = new Object[map.elements.length]; 062 System.arraycopy(map.elements, 0, elements, 0, count * 2); 063 } 064 065 public void putAll(Map<K, V> map) { 066 for (Map.Entry<K, V> entry : map.entrySet()) { 067 K key = entry.getKey(); 068 V value = entry.getValue(); 069 add(key, value); 070 } 071 } 072 073 public V remove(K key) { 074 if (elements == null || count == 0) { 075 return null; 076 } 077 for (int i = 0; i < elements.length; i += 2) { 078 if (elements[i] != null && elements[i].equals(key)) { 079 return _remove(i); 080 } 081 } 082 return null; 083 } 084 085 public V remove(int index) { 086 if (elements == null || count == 0) { 087 return null; 088 } 089 return _remove(index << 1); 090 } 091 092 protected final V _remove(int i) { 093 V result = (V) elements[i + 1]; 094 int len = count * 2; 095 if (i + 2 == len) { 096 elements[i] = null; 097 elements[i + 1] = null; 098 } else { 099 int k = i + 2; 100 System.arraycopy(elements, k, elements, i, len - k); 101 } 102 count--; 103 return result; 104 } 105 106 public V get(K key) { 107 if (elements == null || count == 0) { 108 return null; 109 } 110 for (int i = 0; i < elements.length; i += 2) { 111 if (elements[i] != null && elements[i].equals(key)) { 112 return (V) elements[i + 1]; 113 } 114 } 115 return null; 116 } 117 118 public V get(int i) { 119 if (elements == null || i >= count) { 120 throw new ArrayIndexOutOfBoundsException(i); 121 } 122 return (V) elements[(i << 1) + 1]; 123 } 124 125 public K getKey(Object value) { 126 if (elements == null || count == 0) { 127 return null; 128 } 129 for (int i = 1; i < elements.length; i += 2) { 130 if (elements[i] != null && elements[i].equals(value)) { 131 return (K) elements[i - 1]; 132 } 133 } 134 return null; 135 } 136 137 public K getKey(int i) { 138 if (elements == null || i >= count) { 139 throw new ArrayIndexOutOfBoundsException(i); 140 } 141 return (K) elements[i << 1]; 142 } 143 144 public V put(K key, V value) { 145 // handle the case where we don't have any attributes yet 146 if (elements == null) { 147 elements = new Object[DEFAULT_SIZE]; 148 } 149 if (count == 0) { 150 elements[0] = key; 151 elements[1] = value; 152 count++; 153 return null; 154 } 155 156 int insertIndex = count * 2; 157 158 // replace existing value if it exists 159 for (int i = 0; i < insertIndex; i += 2) { 160 if (elements[i].equals(key)) { 161 Object oldValue = elements[i + 1]; 162 elements[i + 1] = value; 163 return (V) oldValue; 164 } 165 } 166 167 if (elements.length <= insertIndex) { 168 grow(); 169 } 170 elements[insertIndex] = key; 171 elements[insertIndex + 1] = value; 172 count++; 173 174 return null; 175 } 176 177 public void add(K key, V value) { 178 // handle the case where we don't have any attributes yet 179 int insertIndex; 180 if (elements == null) { 181 elements = new Object[DEFAULT_SIZE]; 182 insertIndex = 0; 183 } else { 184 insertIndex = count * 2; 185 if (elements.length <= insertIndex) { 186 grow(); 187 } 188 } 189 elements[insertIndex] = key; 190 elements[insertIndex + 1] = value; 191 count++; 192 } 193 194 public void trimToSize() { 195 int len = count * 2; 196 if (len < elements.length) { 197 Object[] tmp = new Object[len]; 198 System.arraycopy(elements, 0, tmp, 0, len); 199 elements = tmp; 200 } 201 } 202 203 public int size() { 204 return count; 205 } 206 207 public boolean isEmpty() { 208 return count == 0; 209 } 210 211 public void clear() { 212 elements = null; 213 count = 0; 214 } 215 216 protected void grow() { 217 Object[] expanded = new Object[elements.length + GROW_SIZE]; 218 System.arraycopy(elements, 0, expanded, 0, elements.length); 219 elements = expanded; 220 } 221 222 public Object[] getArray() { 223 return elements; 224 } 225 226 @Override 227 public boolean equals(Object obj) { 228 if (obj == this) { 229 return true; 230 } 231 if (obj instanceof ArrayMap) { 232 ArrayMap map = (ArrayMap) obj; 233 if (count != map.count) { 234 return false; 235 } 236 int len = count << 1; 237 for (int i = 0; i < len; i += 2) { 238 Object key1 = elements[i]; 239 Object key2 = map.elements[i]; 240 if (!key1.equals(key2)) { 241 return false; 242 } 243 Object val1 = elements[i + 1]; 244 Object val2 = map.elements[i + 1]; 245 if (val1 == null) { 246 if (val1 != val2) { 247 return false; 248 } 249 } else if (!val1.equals(val2)) { 250 return false; 251 } 252 } 253 return true; 254 } 255 return false; 256 } 257 258 @Override 259 public int hashCode() { 260 int result = 17; 261 for (int i = 0; i < count * 2; i++) { 262 result = result * 37 + elements[i].hashCode(); 263 } 264 return result; 265 } 266 267}