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}