1 /*
2 * Copyright (c) 2003-2008 by Cosylab d. d.
3 *
4 * This file is part of Java-Common.
5 *
6 * Java-Common is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Java-Common is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Java-Common. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package com.cosylab.util;
21
22 import java.util.AbstractCollection;
23 import java.util.AbstractSet;
24 import java.util.Collection;
25 import java.util.Iterator;
26 import java.util.Map;
27 import java.util.Set;
28
29
30 /**
31 * A simple name-value list. Names are strings and values any Java objects
32 * (where primitives are packed in their respective encapsulation objects).
33 * Names and values are stored in arrays. The initial dimension of the array
34 * can be specified or a default can be used ( <code>DEFAULT_CAPACITY =
35 * 10</code> elements). If more elements are added, the arrays grow in
36 * <code>DEFAULT_CAPACITY</code> increments. The addition is therefore a fast
37 * operation, if the arrays are not resized. The removal, on the other hand,
38 * resizes the arrays each time and is a much slower operation. Lookup of
39 * value by name uses linear search and string comparison. This object does
40 * not allow duplicate name entries. If a duplicate name is provided, the old
41 * value will be overwritten and returned as a return value from
42 * <code>add</code> method.
43 *
44 * <p>
45 * This object is thread safe.
46 * </p>
47 *
48 * <p>
49 * The primary purpose of this object is to store small (10 or less) name-value
50 * pairs. In this case, the algorithms (including the linear search) are quite
51 * effective. An example would be the use for passing the argument values to
52 * the method calls.
53 * </p>
54 *
55 * <p>
56 * <b>This class does preserve the order during insertion and deletion
57 * operations.</b>
58 * </p>
59 *
60 * <p>
61 * This class implements the collection <code>Map</code> interface.
62 * </p>
63 *
64 * @author <a href="mailto:gasper.tkacik@cosylab.com">Gasper Tkacik</a>
65 * @version $id$
66 */
67 public class NameValueList implements Map
68 {
69 private String[] names = null;
70 private Object[] values = null;
71 private int ix = 0;
72 private KeySet keySet = new KeySet();
73 private EntrySet entrySet = new EntrySet();
74 private ValueCollection valueCollection = new ValueCollection();
75 private static final int DEFAULT_CAPACITY = 10;
76
77 private class EntrySet extends AbstractSet
78 {
79 /**
80 * DOCUMENT ME!
81 *
82 * @return DOCUMENT ME!
83 */
84 public Iterator iterator()
85 {
86 return new EntryIterator();
87 }
88
89 /**
90 * DOCUMENT ME!
91 *
92 * @return DOCUMENT ME!
93 */
94 public int size()
95 {
96 return NameValueList.this.size();
97 }
98 }
99
100 private class EntryIterator implements Iterator
101 {
102 private class Entry implements Map.Entry
103 {
104 private int ix = 0;
105
106 /**
107 * Creates a new Entry object.
108 *
109 * @param ix
110 */
111 public Entry(int ix)
112 {
113 this.ix = ix;
114 }
115
116 /**
117 * DOCUMENT ME!
118 *
119 * @return DOCUMENT ME!
120 */
121 public Object getValue()
122 {
123 return values[ix];
124 }
125
126 /**
127 * DOCUMENT ME!
128 *
129 * @param value DOCUMENT ME!
130 *
131 * @return DOCUMENT ME!
132 */
133 public Object setValue(Object value)
134 {
135 Object retVal = values[ix];
136 values[ix] = value;
137
138 return retVal;
139 }
140
141 /**
142 * DOCUMENT ME!
143 *
144 * @return DOCUMENT ME!
145 */
146 public Object getKey()
147 {
148 return names[ix];
149 }
150 }
151
152 private int ix = 0;
153
154 /**
155 * DOCUMENT ME!
156 *
157 * @return DOCUMENT ME!
158 */
159 public boolean hasNext()
160 {
161 if (ix < NameValueList.this.ix) {
162 return true;
163 } else {
164 return false;
165 }
166 }
167
168 /**
169 * DOCUMENT ME!
170 *
171 * @return DOCUMENT ME!
172 */
173 public Object next()
174 {
175 Entry retVal = new Entry(ix);
176 ix++;
177
178 return retVal;
179 }
180
181 /**
182 * DOCUMENT ME!
183 */
184 public void remove()
185 {
186 NameValueList.this.remove(names[ix]);
187 }
188 }
189
190 private class KeySet extends AbstractSet
191 {
192 /**
193 * DOCUMENT ME!
194 *
195 * @return DOCUMENT ME!
196 */
197 public Iterator iterator()
198 {
199 return new KeyIterator();
200 }
201
202 /**
203 * DOCUMENT ME!
204 *
205 * @return DOCUMENT ME!
206 */
207 public int size()
208 {
209 return NameValueList.this.size();
210 }
211 }
212
213 private class KeyIterator implements Iterator
214 {
215 private int ix = 0;
216
217 /**
218 * DOCUMENT ME!
219 *
220 * @return DOCUMENT ME!
221 */
222 public boolean hasNext()
223 {
224 return (ix < NameValueList.this.ix);
225 }
226
227 /**
228 * DOCUMENT ME!
229 *
230 * @return DOCUMENT ME!
231 */
232 public Object next()
233 {
234 Object retVal = names[ix];
235 ix++;
236
237 return retVal;
238 }
239
240 /**
241 * @see Iterator#remove()
242 */
243 public void remove()
244 {
245 NameValueList.this.remove(names[ix]);
246 }
247 }
248
249 private class CollectionIterator implements Iterator
250 {
251 private int ix = 0;
252
253 /**
254 * DOCUMENT ME!
255 *
256 * @return DOCUMENT ME!
257 */
258 public boolean hasNext()
259 {
260 return (ix < NameValueList.this.ix);
261 }
262
263 /**
264 * DOCUMENT ME!
265 */
266 public void remove()
267 {
268 NameValueList.this.remove(names[ix]);
269 }
270
271 /**
272 * DOCUMENT ME!
273 *
274 * @return DOCUMENT ME!
275 */
276 public Object next()
277 {
278 Object retVal = values[ix];
279 ix++;
280
281 return retVal;
282 }
283 }
284
285 private class ValueCollection extends AbstractCollection
286 {
287 /**
288 * DOCUMENT ME!
289 *
290 * @return DOCUMENT ME!
291 */
292 public Iterator iterator()
293 {
294 return new CollectionIterator();
295 }
296
297 /**
298 * DOCUMENT ME!
299 *
300 * @return DOCUMENT ME!
301 */
302 public int size()
303 {
304 return NameValueList.this.size();
305 }
306 }
307
308 /**
309 * Creates a new instance of this object with the
310 * <code>DEFAULT_CAPACITY</code> equal to 10.
311 */
312 public NameValueList()
313 {
314 this(DEFAULT_CAPACITY);
315 }
316
317 /**
318 * Creates a new instance of this object with the initial capacity
319 * specified by the user.
320 *
321 * @param initialCapacity the size of the names and values array at
322 * construction
323 */
324 public NameValueList(int initialCapacity)
325 {
326 assert (initialCapacity > 0);
327 names = new String[initialCapacity];
328 values = new Object[initialCapacity];
329 }
330
331 /**
332 * Returns the actual number of entries stored in this data structure.
333 *
334 * @return int the size of the name-value list
335 */
336 public synchronized int size()
337 {
338 return ix;
339 }
340
341 /**
342 * Adds a new name-value pair. If the name already exists in the structure,
343 * its value is replaced by the new value and the old value is returned as
344 * a result of this method.
345 *
346 * @param name the name to store the value under
347 * @param value a new value for the name
348 *
349 * @return Object the value that was previously stored under name
350 *
351 * @throws ClassCastException if parameter name is not of type
352 * <code>String</code>
353 */
354 public synchronized Object put(Object name, Object value)
355 {
356 assert (name != null);
357
358 if (!(name instanceof String)) {
359 throw new ClassCastException("name !instanceof String");
360 }
361
362 String cast = (String)name;
363
364 int index = find(cast);
365
366 if (index != -1) {
367 Object retVal = values[index];
368 values[index] = value;
369
370 return retVal;
371 }
372
373 if (ix < names.length) {
374 names[ix] = cast;
375 values[ix] = value;
376 ix++;
377
378 return null;
379 } else {
380 resizeUp();
381
382 return put(cast, value);
383 }
384 }
385
386 /**
387 * Removes a value stored under <code>name</code> and returns it. This
388 * method resizes the arrays by decreasing them by 1.
389 *
390 * @param name the name of the value to remove
391 *
392 * @return Object the removed value or <code>null</code> if the value with
393 * the given name did not exist in the structure
394 *
395 * @throws ClassCastException if parameter name is not of type
396 * <code>String</code>
397 */
398 public synchronized Object remove(Object name)
399 {
400 assert (name != null);
401
402 if (!(name instanceof String)) {
403 throw new ClassCastException("name !instanceof String");
404 }
405
406 int ix = find((String)name);
407
408 if (ix == -1) {
409 return null;
410 }
411
412 Object retVal = values[ix];
413 String[] newNames = new String[names.length - 1];
414 Object[] newValues = new Object[names.length - 1];
415 System.arraycopy(names, 0, newNames, 0, ix);
416 System.arraycopy(values, 0, newValues, 0, ix);
417 System.arraycopy(names, ix + 1, newNames, ix, names.length - ix - 1);
418 System.arraycopy(values, ix + 1, newValues, ix, names.length - ix - 1);
419 values = newValues;
420 names = newNames;
421 this.ix--;
422
423 return retVal;
424 }
425
426 /**
427 * Returns an object stored under <code>name</code>.
428 *
429 * @param name the name to lookup
430 *
431 * @return Object the object with <code>name</code> or <code>null</code> if
432 * such object does not exist
433 *
434 * @throws ClassCastException if parameter name is not of type
435 * <code>String</code>
436 */
437 public synchronized Object get(Object name)
438 {
439 if (!(name instanceof String)) {
440 throw new ClassCastException("name !instanceof String");
441 }
442
443 assert (name != null);
444
445 int ix = find((String)name);
446
447 if (ix == -1) {
448 return null;
449 }
450
451 return values[ix];
452 }
453
454 /**
455 * Returns an array of names stored in this data structure. This involves
456 * creating a new string array, which may be modified by the user. All
457 * values in this array are guaranteed to be non-<code>null</code>.
458 *
459 * @return String[] an array of names of this list
460 */
461 public synchronized String[] getNames()
462 {
463 String[] array = new String[ix];
464 System.arraycopy(names, 0, array, 0, ix);
465
466 return array;
467 }
468
469 private synchronized int find(String name)
470 {
471 assert (name != null);
472
473 for (int i = 0; i < ix; i++) {
474 if (names[i] != null && names[i].equals(name)) {
475 return i;
476 }
477 }
478
479 return -1;
480 }
481
482 private synchronized void resizeUp()
483 {
484 if (ix == names.length) {
485 String[] newNames = new String[names.length + DEFAULT_CAPACITY];
486 Object[] newValues = new Object[names.length + DEFAULT_CAPACITY];
487 System.arraycopy(names, 0, newNames, 0, names.length);
488 System.arraycopy(values, 0, newValues, 0, names.length);
489 names = newNames;
490 values = newValues;
491 }
492 }
493
494 /**
495 * Sets all elements to null, resets the current pointer to the initial
496 * position thereby effectively removing all elements from this map.
497 *
498 * @see Map#clear()
499 */
500 public synchronized void clear()
501 {
502 for (int i = 0; i < names.length; i++) {
503 names[i] = null;
504 values[i] = null;
505 }
506
507 ix = 0;
508 }
509
510 /**
511 * Returns <code>true</code> if this object contains the name-value mapping
512 * with the speficied <code>name</code>.
513 *
514 * @param name the name to lookup
515 *
516 * @return boolean <code>true</code> iff the mapping with the given name
517 * exists
518 *
519 * @throws NullPointerException if parameter name is <code>null</code>
520 *
521 * @see Map#containsKey(Object)
522 */
523 public synchronized boolean containsKey(Object name)
524 {
525 if (name == null) {
526 throw new NullPointerException("name");
527 }
528
529 if (find((String)name) == -1) {
530 return false;
531 } else {
532 return true;
533 }
534 }
535
536 /**
537 * Returns <code>true</code> if this map contains a given value. This is
538 * determined as in <code>java.util.Map</code> specifications.
539 *
540 * @param value test if this map contains the value
541 *
542 * @return <code>true</code> iff the test succeeds
543 *
544 * @see Map#containsValue(Object)
545 */
546 public synchronized boolean containsValue(Object value)
547 {
548 for (int i = 0; i < values.length; i++) {
549 if (value == null && values[i] == null) {
550 return true;
551 }
552
553 if (value != null && values[i] != null && value.equals(values[i])) {
554 return true;
555 }
556 }
557
558 return false;
559 }
560
561 /**
562 * Returns the set of entries, as described in
563 * <code>java.util.Map.Entry</code>.
564 *
565 * @return Set a set of all entries
566 *
567 * @see Map#entrySet()
568 */
569 public Set entrySet()
570 {
571 return entrySet;
572 }
573
574 /**
575 * Returns <code>true</code> iff this map does not contain any name-value
576 * pairs.
577 *
578 * @return boolean <code>true</code> iff empty
579 *
580 * @see Map#isEmpty()
581 */
582 public boolean isEmpty()
583 {
584 return (ix == 0);
585 }
586
587 /**
588 * Returns a set of all names.
589 *
590 * @return Set a set of all names
591 *
592 * @see Map#keySet()
593 */
594 public Set keySet()
595 {
596 return keySet;
597 }
598
599 /**
600 * Puts all nave-value pairs from provided Map to this Map.
601 *
602 * @param t a map argument with which to perform the union operation
603 *
604 * @see Map#putAll(Map)
605 */
606 public synchronized void putAll(Map t)
607 {
608 Iterator it = t.entrySet().iterator();
609
610 while (it.hasNext()) {
611 Entry e = (Entry)it.next();
612 put(e.getKey(), e.getValue());
613 }
614 }
615
616 /**
617 * Returns a collection of all values.
618 *
619 * @return a collection of all values
620 *
621 * @see Map#values()
622 */
623 public Collection values()
624 {
625 return valueCollection;
626 }
627
628 /**
629 * Lists all objects in this list by calling their <code>toString()</code>
630 * methods.
631 *
632 * @return String the contents of this list
633 */
634 public String toString()
635 {
636 StringBuffer sb = new StringBuffer(1000);
637 sb.append('{');
638
639 for (int i = 0; i < size(); i++) {
640 sb.append(' ');
641 sb.append(names[i]);
642 sb.append('=');
643 sb.append(values[i]);
644 }
645
646 sb.append(" }");
647
648 return new String(sb);
649 }
650 }
651
652 /* __oOo__ */