001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: HierarchicalProperties.java 3643 2007-01-12 15:29:45Z gbevin $
007: */
008: package com.uwyn.rife.ioc;
009:
010: import java.util.*;
011:
012: import com.uwyn.rife.ioc.HierarchicalProperties;
013: import com.uwyn.rife.ioc.exceptions.IncompatiblePropertyValueTypeException;
014: import com.uwyn.rife.ioc.exceptions.PropertyValueException;
015: import java.text.CharacterIterator;
016: import java.text.StringCharacterIterator;
017:
018: /**
019: * This class allows the creation of a hierarchical tree of named {@link
020: * PropertyValue} instances.
021: * <p>When a property is looked up in a child
022: * <code>HierarchicalProperties</code> instance, the lookup will be propagated
023: * to its parent when it couldn't be found in the child. A single hierarchical
024: * line is thus considered to be one collection that groups all involved
025: * <code>HierarchicalProperties</code> instances. Retrieving the names and the
026: * size will recursively take all the properties of the parents into account
027: * and return the consolidated result. To offer these features, intelligent
028: * caching has been implemented to ensure optimal performance.
029: *
030: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
031: * @version $Revision: 3643 $
032: * @since 1.1
033: */
034: public class HierarchicalProperties {
035: private LinkedHashMap<String, PropertyValue> mProperties;
036:
037: private HierarchicalProperties mParent;
038: private LinkedHashSet<HierarchicalProperties> mChildren;
039: private Set<String> mCachedNames;
040: private Set<String> mCachedInjectableNames;
041:
042: public HierarchicalProperties() {
043: }
044:
045: private HierarchicalProperties(HierarchicalProperties shadow) {
046: mProperties = shadow.mProperties;
047: }
048:
049: /**
050: * Creates a copy of this <code>HierarchicalProperties</code> hierarchy
051: * until a certain instance is reached.
052: * <p>
053: * Each copied instance will shared the datastructure in which the
054: * properties are stored with the original. Creating a shadow is this for
055: * changing the hierarchical structure but maintaining a centralized
056: * management of the properties.
057: *
058: * @param limit the <code>HierarchicalProperties</code> instance that will
059: * not be part of the shadow copy and interrupt the copying process; or
060: * <code>null</code> if the entire hierachy should be copied.
061: * @return the shadow copy of this <code>HierarchicalProperties</code>
062: * hierarchy
063: * hierarchy
064: * @since 1.1
065: */
066: public HierarchicalProperties createShadow(
067: HierarchicalProperties limit) {
068: HierarchicalProperties result = new HierarchicalProperties(this );
069:
070: HierarchicalProperties original = this ;
071: HierarchicalProperties shadow = result;
072: while (original.getParent() != null
073: && original.getParent() != limit) {
074: shadow.setParent(new HierarchicalProperties(original
075: .getParent()));
076: original = original.getParent();
077: shadow = shadow.getParent();
078: }
079: return result;
080: }
081:
082: /**
083: * Retrieves the first parent of this <code>HierarchicalProperties</code>
084: * hierarchy.
085: *
086: * @return the root of this <code>HierarchicalProperties</code>
087: * hierarchy
088: * @since 1.1
089: */
090: public HierarchicalProperties getRoot() {
091: HierarchicalProperties root = this ;
092: while (root.getParent() != null) {
093: root = root.getParent();
094: }
095:
096: return root;
097: }
098:
099: /**
100: * Retrieves the <code>Map</code> with only the properties that are
101: * locally present in this <code>HierarchicalProperties</code> instance.
102: *
103: * @return the local <code>Map</code> of this
104: * <code>HierarchicalProperties</code> instance
105: * @since 1.1
106: */
107: public Map<String, PropertyValue> getLocalMap() {
108: if (null == mProperties) {
109: return Collections.EMPTY_MAP;
110: }
111:
112: return mProperties;
113: }
114:
115: /**
116: * Sets the parent of this <code>HierarchicalProperties</code> instance.
117: *
118: * @param parent the parent of this instance; or <code>null</code> if this
119: * instance should be isolated
120: * @see #getParent
121: * @since 1.1
122: */
123: public void setParent(HierarchicalProperties parent) {
124: clearCaches();
125:
126: if (mParent != null) {
127: mParent.removeChild(this );
128: }
129:
130: mParent = parent;
131:
132: if (mParent != null) {
133: mParent.addChild(this );
134: }
135: }
136:
137: /**
138: * Sets the parent of this <code>HierarchicalProperties</code> instance.
139: *
140: * @param parent the parent of this instance; or <code>null</code> if this
141: * instance should be isolated
142: * @return this <code>HierarchicalProperties</code> instance
143: * @see #getParent
144: * @since 1.1
145: */
146: public HierarchicalProperties parent(HierarchicalProperties parent) {
147: setParent(parent);
148:
149: return this ;
150: }
151:
152: /**
153: * Retrieves the parent of this <code>HierarchicalProperties</code>
154: * instance.
155: *
156: * @return the parent of this <code>HierarchicalProperties</code>
157: * instance; or
158: * <p><code>null</code> if this instance is isolated
159: * @see #parent
160: * @since 1.1
161: */
162: public HierarchicalProperties getParent() {
163: return mParent;
164: }
165:
166: /**
167: * Associates the specified value with the specified name in this
168: * <code>HierarchicalProperties</code> instance. If it previously
169: * contained a mapping for this name, the old value is replaced by the
170: * specified value.
171: *
172: * @param name the name that will be associated with the property
173: * @param value the property value that will be associated with the
174: * specified name
175: * @return this <code>HierarchicalProperties</code> instance
176: * @see #put(String, Object)
177: * @see #putAll
178: * @since 1.1
179: */
180: public HierarchicalProperties put(String name, PropertyValue value) {
181: clearCaches();
182:
183: if (null == mProperties) {
184: mProperties = new LinkedHashMap<String, PropertyValue>();
185: }
186:
187: mProperties.put(name, value);
188:
189: return this ;
190: }
191:
192: /**
193: * Associates the specified fixed object value with the specified name
194: * in this <code>HierarchicalProperties</code> instance. If it previously
195: * contained a mapping for this name, the old value is replaced by the
196: * specified value.
197: *
198: * @param name the name that will be associated with the property
199: * @param value the property value that will be associated with the
200: * specified name, note that this method will create a {@link PropertyValueObject}
201: * instance that will contain the value in a fixed manner
202: * @return this <code>HierarchicalProperties</code> instance
203: * @see #put(String, PropertyValue)
204: * @see #putAll
205: * @since 1.6
206: */
207: public HierarchicalProperties put(String name, Object value) {
208: put(name, new PropertyValueObject(value));
209:
210: return this ;
211: }
212:
213: /**
214: * Removes the mapping for this name from this
215: * <code>HierarchicalProperties</code> instance, if it is present.
216: *
217: * @param name the name that will be removed
218: * @return the previously associated value; or
219: * <p><code>null</code> if the name wasn't found in this
220: * <code>HierarchicalProperties</code> instance
221: * @since 1.1
222: */
223: public PropertyValue remove(String name) {
224: if (null == mProperties) {
225: return null;
226: }
227:
228: clearCaches();
229:
230: return mProperties.remove(name);
231: }
232:
233: /**
234: * Copies all of the named properties from the specified
235: * <code>HierarchicalProperties</code> instance to this
236: * <code>HierarchicalProperties</code> instance. The effect of this call
237: * is equivalent to that of calling {@link #put} on this
238: * <code>HierarchicalProperties</code> once for each mapping from the
239: * specified <code>HierarchicalProperties</code> instance.
240: *
241: * @param source the properties that will be stored in this
242: * <code>HierarchicalProperties</code> instance
243: * @return this <code>HierarchicalProperties</code> instance
244: * @see #put
245: * @since 1.1
246: */
247: public HierarchicalProperties putAll(HierarchicalProperties source) {
248: clearCaches();
249:
250: if (source.mProperties != null) {
251: if (null == mProperties) {
252: mProperties = new LinkedHashMap<String, PropertyValue>();
253: }
254:
255: mProperties.putAll(source.mProperties);
256: }
257:
258: return this ;
259: }
260:
261: /**
262: * Copies all of the entries for a <code>Map</code> instance to this
263: * <code>HierarchicalProperties</code> instance.
264: *
265: * @param source the map entries that will be stored in this
266: * <code>HierarchicalProperties</code> instance
267: * @return this <code>HierarchicalProperties</code> instance
268: * @since 1.5
269: */
270: public HierarchicalProperties putAll(Map source) {
271: if (null == source) {
272: return this ;
273: }
274:
275: clearCaches();
276:
277: if (null == mProperties) {
278: mProperties = new LinkedHashMap<String, PropertyValue>();
279: }
280:
281: for (Map.Entry entry : ((Set<Map.Entry>) source.entrySet())) {
282: mProperties.put(String.valueOf(entry.getKey()),
283: new PropertyValueObject(entry.getValue()));
284: }
285:
286: return this ;
287: }
288:
289: /**
290: * Checks the <code>HierarchicalProperties</code> hierarchy for the
291: * presence of the specified name.
292: *
293: * @param name the name whose presence will be checked
294: * @return <code>true</code> if the name was found; or
295: * <p><code>false</code> otherwise
296: * @see #get
297: * @since 1.1
298: */
299: public boolean contains(String name) {
300: HierarchicalProperties current = this ;
301:
302: LinkedHashMap<String, PropertyValue> properties = null;
303: while (true) {
304: properties = current.mProperties;
305:
306: if (properties != null) {
307: if (properties.containsKey(name)) {
308: return true;
309: }
310: }
311:
312: if (null == current.mParent) {
313: break;
314: }
315:
316: current = current.mParent;
317: }
318:
319: return false;
320: }
321:
322: /**
323: * Retrieves the <code>PropertyValue</code> for a specific name from the
324: * <code>HierarchicalProperties</code> hierarchy.
325: *
326: * @param name the name whose associated value will be returned
327: * @return the associated <code>PropertyValue</code>; or
328: * <p><code>null</code> if the name could not be found
329: * @see #contains
330: * @since 1.1
331: */
332: public PropertyValue get(String name) {
333: HierarchicalProperties current = this ;
334: PropertyValue result;
335:
336: LinkedHashMap<String, PropertyValue> properties = null;
337: while (true) {
338: properties = current.mProperties;
339:
340: if (properties != null) {
341: result = properties.get(name);
342: if (result != null) {
343: return result;
344: }
345: }
346:
347: if (null == current.mParent) {
348: break;
349: }
350:
351: current = current.mParent;
352: }
353:
354: return null;
355: }
356:
357: /**
358: * Retrieves the value of <code>PropertyValue</code> for a specific name from
359: * the <code>HierarchicalProperties</code> hierarchy.
360: *
361: * @param name the name whose associated value will be returned
362: * @return the associated <code>PropertyValue</code>; or
363: * <p><code>null</code> if the name could not be found
364: * @throws PropertyValueException when an error occurred while retrieving the
365: * property value
366: * @see #get
367: * @see #getValue(String, Object)
368: * @since 1.1
369: */
370: public Object getValue(String name) throws PropertyValueException {
371: return getValue(name, null);
372: }
373:
374: /**
375: * Retrieves the value of <code>PropertyValue</code> for a specific name from
376: * the <code>HierarchicalProperties</code> hierarchy. If the property couldn't
377: * be found or if the value was <code>null</code>, the default value will be
378: * returned.
379: *
380: * @param name the name whose associated value will be returned
381: * @param defaultValue the value that should be used as a fallback
382: * @return the associated <code>PropertyValue</code>; or
383: * <p>the <code>defaultValue</code> if the property couldn't be found or if
384: * the value was <code>null</code>
385: * @throws PropertyValueException when an error occurred while retrieving the
386: * property value
387: * @see #get
388: * @see #getValue(String)
389: * @since 1.1
390: */
391: public Object getValue(String name, Object defaultValue)
392: throws PropertyValueException {
393: Object result = null;
394:
395: PropertyValue property = get(name);
396: if (property != null) {
397: result = property.getValue();
398: }
399:
400: if (null == result) {
401: return defaultValue;
402: }
403:
404: return result;
405: }
406:
407: /**
408: * Retrieves the string value of <code>PropertyValue</code> for a specific name from
409: * the <code>HierarchicalProperties</code> hierarchy.
410: *
411: * @param name the name whose associated value will be returned
412: * @return the string value of the retrieved <code>PropertyValue</code>; or
413: * <p><code>null</code> if the name could not be found
414: * @throws PropertyValueException when an error occurred while retrieving the
415: * property value
416: * @see #get
417: * @see #getValueString(String, String)
418: * @see #getValueTyped
419: * @since 1.1
420: */
421: public String getValueString(String name)
422: throws PropertyValueException {
423: return getValueString(name, null);
424: }
425:
426: /**
427: * Retrieves the string value of <code>PropertyValue</code> for a specific name from
428: * the <code>HierarchicalProperties</code> hierarchy. If the property couldn't
429: * be found, if the value was <code>null</code> or if the value was empty, the
430: * default value will be returned.
431: *
432: * @param name the name whose associated value will be returned
433: * @param defaultValue the value that should be used as a fallback
434: * @return the string value of the retrieved <code>PropertyValue</code>; or
435: * <p>the <code>defaultValue</code> if the property couldn't be found or if
436: * the value was <code>null</code> or an empty string
437: * @throws PropertyValueException when an error occurred while retrieving the
438: * property value
439: * @see #get
440: * @see #getValueString(String)
441: * @see #getValueTyped
442: * @since 1.1
443: */
444: public String getValueString(String name, String defaultValue)
445: throws PropertyValueException {
446: String result = null;
447:
448: PropertyValue property = get(name);
449: if (property != null) {
450: result = property.getValueString();
451: }
452:
453: if (null == result || 0 == result.length()) {
454: return defaultValue;
455: }
456:
457: return result;
458: }
459:
460: /**
461: * Retrieves the typed value of <code>PropertyValue</code> for a specific name from
462: * the <code>HierarchicalProperties</code> hierarchy.
463: * <p>
464: * Note that no conversion will occurr, the value is simple verified to be
465: * assignable to the requested type and then casted to it.
466: *
467: * @param name the name whose associated value will be returned
468: * @param type the class that the value has to be retrieved as
469: * @return the associated <code>PropertyValue</code> as an instance of the
470: * provided type; or
471: * <p><code>null</code> if the name could not be found
472: * @throws IncompatiblePropertyValueTypeException when the type of the property
473: * value wasn't compatible with the requested type
474: * @throws PropertyValueException when an error occurred while retrieving the
475: * property value
476: * @see #get
477: * @see #getValueString
478: * @see #getValueTyped(String, Class)
479: * @since 1.6
480: */
481: public <T> T getValueTyped(String name, Class<T> type)
482: throws PropertyValueException {
483: return (T) getValueTyped(name, type, null);
484: }
485:
486: /**
487: * Retrieves the typed value of <code>PropertyValue</code> for a specific name from
488: * the <code>HierarchicalProperties</code> hierarchy.
489: * <p>
490: * Note that no conversion will occurr, the value is simple verified to be
491: * assignable to the requested type and then casted to it.
492: *
493: * @param name the name whose associated value will be returned
494: * @param type the class that the value has to be retrieved as
495: * @param defaultValue the value that should be used as a fallback
496: * @return the associated <code>PropertyValue</code> as an instance of the
497: * provided type; or
498: * <p>the <code>defaultValue</code> if the property couldn't be found or if
499: * the value was <code>null</code>
500: * @throws IncompatiblePropertyValueTypeException when the type of the property
501: * value wasn't compatible with the requested type
502: * @throws PropertyValueException when an error occurred while retrieving the
503: * property value
504: * @see #get
505: * @see #getValueString
506: * @see #getValueTyped(String, Class)
507: * @since 1.6
508: */
509: public <T> T getValueTyped(String name, Class<T> type,
510: T defaultValue) throws PropertyValueException {
511: if (null == name || null == type || 0 == name.length()) {
512: return defaultValue;
513: }
514:
515: Object result = null;
516:
517: PropertyValue property = get(name);
518: if (property != null) {
519: result = property.getValue();
520: }
521:
522: if (null == result) {
523: return defaultValue;
524: }
525:
526: if (!type.isAssignableFrom(result.getClass())) {
527: throw new IncompatiblePropertyValueTypeException(name,
528: type, result.getClass(), null);
529: }
530:
531: return (T) result;
532: }
533:
534: /**
535: * Retrieves the number of unique names in the
536: * <code>HierarchicalProperties</code> hierarchy.
537: *
538: * @return the amount of unique names
539: * @since 1.1
540: */
541: public int size() {
542: return getNames().size();
543: }
544:
545: /**
546: * Retrieves a <code>Set</code> with the unique names that are present in
547: * the <code>HierarchicalProperties</code> hierarchy.
548: *
549: * @return a collection with the unique names
550: * @see #getInjectableNames
551: * @since 1.1
552: */
553: public Collection<String> getNames() {
554: if (mCachedNames != null) {
555: return mCachedNames;
556: }
557:
558: HierarchicalProperties current = this ;
559: Set<String> names = new LinkedHashSet<String>();
560:
561: LinkedHashMap<String, PropertyValue> properties = null;
562: while (true) {
563: properties = current.mProperties;
564:
565: if (properties != null) {
566: names.addAll(properties.keySet());
567: }
568:
569: if (null == current.mParent) {
570: break;
571: }
572:
573: current = current.mParent;
574: }
575:
576: mCachedNames = names;
577:
578: return names;
579: }
580:
581: /**
582: * Retrieves a <code>Set</code> with the unique names that are present in
583: * the <code>HierarchicalProperties</code> hierarchy and that conform to
584: * the <a
585: * href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.8">Java
586: * rules for valid identifiers</a>. The names in this set are thus usable
587: * for injection through bean setters.
588: *
589: * @return a <code>Set</code> with the unique injectable names
590: * @see #getNames
591: * @since 1.1
592: */
593: public Collection<String> getInjectableNames() {
594: if (mCachedInjectableNames != null) {
595: return mCachedInjectableNames;
596: }
597:
598: Set<String> injectable_names = new LinkedHashSet<String>();
599:
600: Collection<String> names = getNames();
601: for (String name : names) {
602: boolean injectable = true;
603: CharacterIterator it = new StringCharacterIterator(name);
604: for (char c = it.first(); c != CharacterIterator.DONE; c = it
605: .next()) {
606: if (!Character.isJavaIdentifierPart(c)) {
607: injectable = false;
608: break;
609: }
610: }
611:
612: if (injectable) {
613: injectable_names.add(name);
614: }
615: }
616:
617: mCachedInjectableNames = injectable_names;
618:
619: return injectable_names;
620: }
621:
622: private void clearCaches() {
623: mCachedNames = null;
624: mCachedInjectableNames = null;
625:
626: if (null == mChildren) {
627: return;
628: }
629:
630: for (HierarchicalProperties child : mChildren) {
631: child.clearCaches();
632: }
633: }
634:
635: private void addChild(HierarchicalProperties child) {
636: if (null == mChildren) {
637: mChildren = new LinkedHashSet<HierarchicalProperties>();
638: }
639: mChildren.add(child);
640: }
641:
642: private void removeChild(HierarchicalProperties child) {
643: if (null == mChildren) {
644: return;
645: }
646: mChildren.remove(child);
647: }
648: }
|