View Javadoc

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__ */