KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > chain > impl > ContextBase


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.commons.chain.impl;
17
18
19 import java.beans.IntrospectionException JavaDoc;
20 import java.beans.Introspector JavaDoc;
21 import java.beans.PropertyDescriptor JavaDoc;
22 import java.lang.reflect.Method JavaDoc;
23 import java.util.AbstractCollection JavaDoc;
24 import java.util.AbstractSet JavaDoc;
25 import java.util.Collection JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.Map JavaDoc;
29 import java.util.Set JavaDoc;
30 import org.apache.commons.chain.Context;
31
32
33 /**
34  * <p>Convenience base class for {@link Context} implementations.</p>
35  *
36  * <p>In addition to the minimal functionality required by the {@link Context}
37  * interface, this class implements the recommended support for
38  * <em>Attribute-Property Transparency</p>. This is implemented by
39  * analyzing the available JavaBeans properties of this class (or its
40  * subclass), exposes them as key-value pairs in the <code>Map</code>,
41  * with the key being the name of the property itself.</p>
42  *
43  * <p><strong>IMPLEMENTATION NOTE</strong> - Because <code>empty</code> is a
44  * read-only property defined by the <code>Map</code> interface, it may not
45  * be utilized as an attribute key or property name.</p>
46  *
47  * @author Craig R. McClanahan
48  * @version $Revision: 1.7 $ $Date: 2004/11/30 05:52:23 $
49  */

50
51 public class ContextBase extends HashMap JavaDoc implements Context {
52
53
54     // ------------------------------------------------------------ Constructors
55

56
57     /**
58      * Default, no argument constructor.
59      */

60     public ContextBase() {
61
62         super();
63         initialize();
64
65     }
66
67
68     /**
69      * <p>Initialize the contents of this {@link Context} by copying the
70      * values from the specified <code>Map</code>. Any keys in <code>map</code>
71      * that correspond to local properties will cause the setter method for
72      * that property to be called.</p>
73      *
74      * @param map Map whose key-value pairs are added
75      *
76      * @exception IllegalArgumentException if an exception is thrown
77      * writing a local property value
78      * @exception UnsupportedOperationException if a local property does not
79      * have a write method.
80      */

81     public ContextBase(Map JavaDoc map) {
82
83         super(map);
84         initialize();
85         putAll(map);
86
87     }
88
89
90     // ------------------------------------------------------ Instance Variables
91

92
93     /**
94      * <p>The <code>PropertyDescriptor</code>s for all JavaBeans properties
95      * of this {@link Context} implementation class, keyed by property name.
96      * This collection is allocated only if there are any JavaBeans
97      * properties.</p>
98      */

99     private Map JavaDoc descriptors = null;
100
101
102     /**
103      * <p>The same <code>PropertyDescriptor</code>s as an array.</p>
104      */

105     private PropertyDescriptor JavaDoc[] pd = null;
106
107
108     /**
109      * <p>Distinguished singleton value that is stored in the map for each
110      * key that is actually a property. This value is used to ensure that
111      * <code>equals()</code> comparisons will always fail.</p>
112      */

113     private static Object JavaDoc singleton;
114
115     static {
116
117         singleton = new Object JavaDoc() {
118                 public boolean equals(Object JavaDoc object) {
119                     return (false);
120                 }
121             };
122
123     }
124
125
126     /**
127      * <p>Zero-length array of parameter values for calling property getters.
128      * </p>
129      */

130     private static Object JavaDoc[] zeroParams = new Object JavaDoc[0];
131
132
133     // ------------------------------------------------------------- Map Methods
134

135
136     /**
137      * <p>Override the default <code>Map</code> behavior to clear all keys and
138      * values except those corresponding to JavaBeans properties.</p>
139      */

140     public void clear() {
141
142         if (descriptors == null) {
143             super.clear();
144         } else {
145             Iterator JavaDoc keys = keySet().iterator();
146             while (keys.hasNext()) {
147                 Object JavaDoc key = keys.next();
148                 if (!descriptors.containsKey(key)) {
149                     keys.remove();
150                 }
151             }
152         }
153
154     }
155
156
157     /**
158      * <p>Override the default <code>Map</code> behavior to return
159      * <code>true</code> if the specified value is present in either the
160      * underlying <code>Map</code> or one of the local property values.</p>
161      *
162      * @exception IllegalArgumentException if a property getter
163      * throws an exception
164      */

165     public boolean containsValue(Object JavaDoc value) {
166
167         // Case 1 -- no local properties
168
if (descriptors == null) {
169             return (super.containsValue(value));
170         }
171
172         // Case 2 -- value found in the underlying Map
173
else if (super.containsValue(value)) {
174             return (true);
175         }
176
177         // Case 3 -- check the values of our readable properties
178
for (int i = 0; i < pd.length; i++) {
179             if (pd[i].getReadMethod() != null) {
180                 Object JavaDoc prop = readProperty(pd[i]);
181                 if (value == null) {
182                     if (prop == null) {
183                         return (true);
184                     }
185                 } else if (value.equals(prop)) {
186                     return (true);
187                 }
188             }
189         }
190         return (false);
191
192     }
193
194
195     /**
196      * <p>Override the default <code>Map</code> behavior to return a
197      * <code>Set</code> that meets the specified default behavior except
198      * for attempts to remove the key for a property of the {@link Context}
199      * implementation class, which will throw
200      * <code>UnsupportedOperationException</code>.</p>
201      */

202     public Set JavaDoc entrySet() {
203
204         return (new EntrySetImpl());
205
206     }
207
208
209     /**
210      * <p>Override the default <code>Map</code> behavior to return the value
211      * of a local property if the specified key matches a local property name.
212      * </p>
213      *
214      * <p><strong>IMPLEMENTATION NOTE</strong> - If the specified
215      * <code>key</code> identifies a write-only property, <code>null</code>
216      * will arbitrarily be returned, in order to avoid difficulties implementing
217      * the contracts of the <code>Map</code> interface.</p>
218      *
219      * @param key Key of the value to be returned
220      *
221      * @exception IllegalArgumentException if an exception is thrown
222      * reading this local property value
223      * @exception UnsupportedOperationException if this local property does not
224      * have a read method.
225      */

226     public Object JavaDoc get(Object JavaDoc key) {
227
228         // Case 1 -- no local properties
229
if (descriptors == null) {
230             return (super.get(key));
231         }
232
233         // Case 2 -- this is a local property
234
if (key != null) {
235             PropertyDescriptor JavaDoc descriptor =
236                 (PropertyDescriptor JavaDoc) descriptors.get(key);
237             if (descriptor != null) {
238                 if (descriptor.getReadMethod() != null) {
239                     return (readProperty(descriptor));
240                 } else {
241                     return (null);
242                 }
243             }
244         }
245
246         // Case 3 -- retrieve value from our underlying Map
247
return (super.get(key));
248
249     }
250
251
252     /**
253      * <p>Override the default <code>Map</code> behavior to return
254      * <code>true</code> if the underlying <code>Map</code> only contains
255      * key-value pairs for local properties (if any).</p>
256      */

257     public boolean isEmpty() {
258
259         // Case 1 -- no local properties
260
if (descriptors == null) {
261             return (super.isEmpty());
262         }
263
264         // Case 2 -- compare key count to property count
265
return (super.size() <= descriptors.size());
266
267     }
268
269
270     /**
271      * <p>Override the default <code>Map</code> behavior to return a
272      * <code>Set</code> that meets the specified default behavior except
273      * for attempts to remove the key for a property of the {@link Context}
274      * implementation class, which will throw
275      * <code>UnsupportedOperationException</code>.</p>
276      */

277     public Set JavaDoc keySet() {
278
279
280         return (super.keySet());
281
282     }
283
284
285     /**
286      * <p>Override the default <code>Map</code> behavior to set the value
287      * of a local property if the specified key matches a local property name.
288      * </p>
289      *
290      * @param key Key of the value to be stored or replaced
291      * @param value New value to be stored
292      *
293      * @exception IllegalArgumentException if an exception is thrown
294      * reading or wrting this local property value
295      * @exception UnsupportedOperationException if this local property does not
296      * have both a read method and a write method
297      */

298     public Object JavaDoc put(Object JavaDoc key, Object JavaDoc value) {
299
300         // Case 1 -- no local properties
301
if (descriptors == null) {
302             return (super.put(key, value));
303         }
304
305         // Case 2 -- this is a local property
306
if (key != null) {
307             PropertyDescriptor JavaDoc descriptor =
308                 (PropertyDescriptor JavaDoc) descriptors.get(key);
309             if (descriptor != null) {
310                 Object JavaDoc previous = null;
311                 if (descriptor.getReadMethod() != null) {
312                     previous = readProperty(descriptor);
313                 }
314                 writeProperty(descriptor, value);
315                 return (previous);
316             }
317         }
318
319         // Case 3 -- store or replace value in our underlying map
320
return (super.put(key, value));
321
322     }
323
324
325     /**
326      * <p>Override the default <code>Map</code> behavior to call the
327      * <code>put()</code> method individually for each key-value pair
328      * in the specified <code>Map</code>.</p>
329      *
330      * @param map <code>Map</code> containing key-value pairs to store
331      * (or replace)
332      *
333      * @exception IllegalArgumentException if an exception is thrown
334      * reading or wrting a local property value
335      * @exception UnsupportedOperationException if a local property does not
336      * have both a read method and a write method
337      */

338     public void putAll(Map JavaDoc map) {
339
340         Iterator JavaDoc pairs = map.entrySet().iterator();
341         while (pairs.hasNext()) {
342             Map.Entry JavaDoc pair = (Map.Entry JavaDoc) pairs.next();
343             put(pair.getKey(), pair.getValue());
344         }
345
346     }
347
348
349     /**
350      * <p>Override the default <code>Map</code> behavior to throw
351      * <code>UnsupportedOperationException</code> on any attempt to
352      * remove a key that is the name of a local property.</p>
353      *
354      * @param key Key to be removed
355      *
356      * @exception UnsupportedOperationException if the specified
357      * <code>key</code> matches the name of a local property
358      */

359     public Object JavaDoc remove(Object JavaDoc key) {
360
361         // Case 1 -- no local properties
362
if (descriptors == null) {
363             return (super.remove(key));
364         }
365
366         // Case 2 -- this is a local property
367
if (key != null) {
368             PropertyDescriptor JavaDoc descriptor =
369                 (PropertyDescriptor JavaDoc) descriptors.get(key);
370             if (descriptor != null) {
371                 throw new UnsupportedOperationException JavaDoc
372                     ("Local property '" + key + "' cannot be removed");
373             }
374         }
375
376         // Case 3 -- remove from underlying Map
377
return (super.remove(key));
378
379     }
380
381
382     /**
383      * <p>Override the default <code>Map</code> behavior to return a
384      * <code>Collection</code> that meets the specified default behavior except
385      * for attempts to remove the key for a property of the {@link Context}
386      * implementation class, which will throw
387      * <code>UnsupportedOperationException</code>.</p>
388      */

389     public Collection JavaDoc values() {
390
391         return (new ValuesImpl());
392
393     }
394
395
396     // --------------------------------------------------------- Private Methods
397

398
399     /**
400      * <p>Eliminate the specified property descriptor from the list of
401      * property descriptors in <code>pd</code>.</p>
402      *
403      * @param name Name of the property to eliminate
404      *
405      * @exception IllegalArgumentException if the specified property name
406      * is not present
407      */

408     private void eliminate(String JavaDoc name) {
409
410         int j = -1;
411         for (int i = 0; i < pd.length; i++) {
412             if (name.equals(pd[i].getName())) {
413                 j = i;
414                 break;
415             }
416         }
417         if (j < 0) {
418             throw new IllegalArgumentException JavaDoc("Property '" + name
419                                                + "' is not present");
420         }
421         PropertyDescriptor JavaDoc[] results = new PropertyDescriptor JavaDoc[pd.length - 1];
422         System.arraycopy(pd, 0, results, 0, j);
423         System.arraycopy(pd, j + 1, results, j, pd.length - (j + 1));
424         pd = results;
425
426     }
427
428
429     /**
430      * <p>Return an <code>Iterator</code> over the set of <code>Map.Entry</code>
431      * objects representing our key-value pairs.</p>
432      */

433     private Iterator JavaDoc entriesIterator() {
434
435         return (new EntrySetIterator());
436
437     }
438
439
440     /**
441      * <p>Return a <code>Map.Entry</code> for the specified key value, if it
442      * is present; otherwise, return <code>null</code>.</p>
443      *
444      * @param key Attribute key or property name
445      */

446     private Map.Entry JavaDoc entry(Object JavaDoc key) {
447
448         if (containsKey(key)) {
449             return (new MapEntryImpl(key, get(key)));
450         } else {
451             return (null);
452         }
453
454     }
455
456
457     /**
458      * <p>Customize the contents of our underlying <code>Map</code> so that
459      * it contains keys corresponding to all of the JavaBeans properties of
460      * the {@link Context} implementation class.</p>
461      *
462      *
463      * @exception IllegalArgumentException if an exception is thrown
464      * writing this local property value
465      * @exception UnsupportedOperationException if this local property does not
466      * have a write method.
467      */

468     private void initialize() {
469
470         // Retrieve the set of property descriptors for this Context class
471
try {
472             pd = Introspector.getBeanInfo
473                 (getClass()).getPropertyDescriptors();
474         } catch (IntrospectionException JavaDoc e) {
475             pd = new PropertyDescriptor JavaDoc[0]; // Should never happen
476
}
477         eliminate("class"); // Because of "getClass()"
478
eliminate("empty"); // Because of "isEmpty()"
479

480         // Initialize the underlying Map contents
481
if (pd.length > 0) {
482             descriptors = new HashMap JavaDoc();
483             for (int i = 0; i < pd.length; i++) {
484                 descriptors.put(pd[i].getName(), pd[i]);
485                 super.put(pd[i].getName(), singleton);
486             }
487         }
488
489     }
490
491
492     /**
493      * <p>Get and return the value for the specified property.</p>
494      *
495      * @param descriptor <code>PropertyDescriptor</code> for the
496      * specified property
497      *
498      * @exception IllegalArgumentException if an exception is thrown
499      * reading this local property value
500      * @exception UnsupportedOperationException if this local property does not
501      * have a read method.
502      */

503     private Object JavaDoc readProperty(PropertyDescriptor JavaDoc descriptor) {
504
505         try {
506             Method JavaDoc method = descriptor.getReadMethod();
507             if (method == null) {
508                 throw new UnsupportedOperationException JavaDoc
509                     ("Property '" + descriptor.getName()
510                      + "' is not readable");
511             }
512             return (method.invoke(this, zeroParams));
513         } catch (Exception JavaDoc e) {
514             throw new UnsupportedOperationException JavaDoc
515                 ("Exception reading property '" + descriptor.getName()
516                  + "': " + e.getMessage());
517         }
518
519     }
520
521
522     /**
523      * <p>Remove the specified key-value pair, if it exists, and return
524      * <code>true</code>. If this pair does not exist, return
525      * <code>false</code>.</p>
526      *
527      * @param entry Key-value pair to be removed
528      *
529      * @exception UnsupportedOperationException if the specified key
530      * identifies a property instead of an attribute
531      */

532     private boolean remove(Map.Entry JavaDoc entry) {
533
534         Map.Entry JavaDoc actual = entry(entry.getKey());
535         if (actual == null) {
536             return (false);
537         } else if (!entry.equals(actual)) {
538             return (false);
539         } else {
540             remove(entry.getKey());
541             return (true);
542         }
543
544     }
545
546
547     /**
548      * <p>Return an <code>Iterator</code> over the set of values in this
549      * <code>Map</code>.</p>
550      */

551     private Iterator JavaDoc valuesIterator() {
552
553         return (new ValuesIterator());
554
555     }
556
557
558     /**
559      * <p>Set the value for the specified property.</p>
560      *
561      * @param descriptor <code>PropertyDescriptor</code> for the
562      * specified property
563      * @param value The new value for this property (must be of the
564      * correct type)
565      *
566      * @exception IllegalArgumentException if an exception is thrown
567      * writing this local property value
568      * @exception UnsupportedOperationException if this local property does not
569      * have a write method.
570      */

571     private void writeProperty(PropertyDescriptor JavaDoc descriptor, Object JavaDoc value) {
572
573         try {
574             Method JavaDoc method = descriptor.getWriteMethod();
575             if (method == null) {
576                 throw new UnsupportedOperationException JavaDoc
577                     ("Property '" + descriptor.getName()
578                      + "' is not writeable");
579             }
580             method.invoke(this, new Object JavaDoc[] { value });
581         } catch (Exception JavaDoc e) {
582             throw new UnsupportedOperationException JavaDoc
583                 ("Exception writing property '" + descriptor.getName()
584                  + "': " + e.getMessage());
585         }
586
587     }
588
589
590     // --------------------------------------------------------- Private Classes
591

592
593     /**
594      * <p>Private implementation of <code>Set</code> that implements the
595      * semantics required for the value returned by <code>entrySet()</code>.</p>
596      */

597     private class EntrySetImpl extends AbstractSet JavaDoc {
598
599         public void clear() {
600             ContextBase.this.clear();
601         }
602
603         public boolean contains(Object JavaDoc obj) {
604             if (!(obj instanceof Map.Entry JavaDoc)) {
605                 return (false);
606             }
607             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) obj;
608             Entry actual = ContextBase.this.entry(entry.getKey());
609             if (actual != null) {
610                 return (actual.equals(entry));
611             } else {
612                 return (false);
613             }
614         }
615
616         public boolean isEmpty() {
617             return (ContextBase.this.isEmpty());
618         }
619
620         public Iterator JavaDoc iterator() {
621             return (ContextBase.this.entriesIterator());
622         }
623
624         public boolean remove(Object JavaDoc obj) {
625             if (obj instanceof Map.Entry JavaDoc) {
626                 return (ContextBase.this.remove((Map.Entry JavaDoc) obj));
627             } else {
628                 return (false);
629             }
630         }
631
632         public int size() {
633             return (ContextBase.this.size());
634         }
635
636     }
637
638
639     /**
640      * <p>Private implementation of <code>Iterator</code> for the
641      * <code>Set</code> returned by <code>entrySet()</code>.</p>
642      */

643     private class EntrySetIterator implements Iterator JavaDoc {
644
645         Map.Entry JavaDoc entry = null;
646         private Iterator JavaDoc keys = ContextBase.this.keySet().iterator();
647
648         public boolean hasNext() {
649             return (keys.hasNext());
650         }
651
652         public Object JavaDoc next() {
653             entry = ContextBase.this.entry(keys.next());
654             return (entry);
655         }
656
657         public void remove() {
658             ContextBase.this.remove(entry);
659         }
660
661     }
662
663
664     /**
665      * <p>Private implementation of <code>Map.Entry</code> for each item in
666      * <code>EntrySetImpl</code>.</p>
667      */

668     private class MapEntryImpl implements Map.Entry JavaDoc {
669
670         MapEntryImpl(Object JavaDoc key, Object JavaDoc value) {
671             this.key = key;
672             this.value = value;
673         }
674
675         private Object JavaDoc key;
676         private Object JavaDoc value;
677
678         public boolean equals(Object JavaDoc obj) {
679             if (obj == null) {
680                 return (false);
681             } else if (!(obj instanceof Map.Entry JavaDoc)) {
682                 return (false);
683             }
684             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) obj;
685             if (key == null) {
686                 return (entry.getKey() == null);
687             }
688             if (key.equals(entry.getKey())) {
689                 if (value == null) {
690                     return (entry.getValue() == null);
691                 } else {
692                     return (value.equals(entry.getValue()));
693                 }
694             } else {
695                 return (false);
696             }
697         }
698
699         public Object JavaDoc getKey() {
700             return (this.key);
701         }
702
703         public Object JavaDoc getValue() {
704             return (this.value);
705         }
706
707         public int hashCode() {
708             return (((key == null) ? 0 : key.hashCode())
709                    ^ ((value == null) ? 0 : value.hashCode()));
710         }
711
712         public Object JavaDoc setValue(Object JavaDoc value) {
713             Object JavaDoc previous = this.value;
714             ContextBase.this.put(this.key, value);
715             this.value = value;
716             return (previous);
717         }
718
719
720     }
721
722
723     /**
724      * <p>Private implementation of <code>Collection</code> that implements the
725      * semantics required for the value returned by <code>values()</code>.</p>
726      */

727     private class ValuesImpl extends AbstractCollection JavaDoc {
728
729         public void clear() {
730             ContextBase.this.clear();
731         }
732
733         public boolean contains(Object JavaDoc obj) {
734             if (!(obj instanceof Map.Entry JavaDoc)) {
735                 return (false);
736             }
737             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) obj;
738             return (ContextBase.this.containsValue(entry.getValue()));
739         }
740
741         public boolean isEmpty() {
742             return (ContextBase.this.isEmpty());
743         }
744
745         public Iterator JavaDoc iterator() {
746             return (ContextBase.this.valuesIterator());
747         }
748
749         public boolean remove(Object JavaDoc obj) {
750             if (obj instanceof Map.Entry JavaDoc) {
751                 return (ContextBase.this.remove((Map.Entry JavaDoc) obj));
752             } else {
753                 return (false);
754             }
755         }
756
757         public int size() {
758             return (ContextBase.this.size());
759         }
760
761     }
762
763
764     /**
765      * <p>Private implementation of <code>Iterator</code> for the
766      * <code>Collection</code> returned by <code>values()</code>.</p>
767      */

768     private class ValuesIterator implements Iterator JavaDoc {
769
770         Map.Entry JavaDoc entry = null;
771         private Iterator JavaDoc keys = ContextBase.this.keySet().iterator();
772
773         public boolean hasNext() {
774             return (keys.hasNext());
775         }
776
777         public Object JavaDoc next() {
778             entry = ContextBase.this.entry(keys.next());
779             return (entry.getValue());
780         }
781
782         public void remove() {
783             ContextBase.this.remove(entry);
784         }
785
786     }
787
788
789 }
790
Popular Tags