1
2
3
4
5
6
7
8 package org.codehaus.dna.impl;
9
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Set;
15
16 import org.codehaus.dna.Configuration;
17 import org.codehaus.dna.ConfigurationException;
18
19 /***
20 * In memory Configuration implementation.
21 * The developer should create the DefaultConfiguration,
22 * associate value, attributes and/or child elements configuration
23 * and then invoke {@link #makeReadOnly()} before passing the
24 * Configuration to the client component.
25 *
26 * @version $Revision: 1.2 $ $Date: 2004/05/01 09:51:48 $
27 */
28 public class DefaultConfiguration
29 extends AbstractFreezable
30 implements Configuration
31 {
32 /***
33 * Postfix indicating that location is generated.
34 */
35 private static final String AUTOGEN_POSTFIX = "<autogen>";
36
37 /***
38 * The constant that boolean values must equal to be "true".
39 */
40 private static final String TRUE_STRING = "true";
41
42 /***
43 * Constant for empty String array to reduce
44 * creation cost for empty array.
45 */
46 private static final String[] EMPTY_STRING_ARRAY = new String[ 0 ];
47
48 /***
49 * Constant for empty configuration array to reduce
50 * creation cost for empty array.
51 */
52 private static final Configuration[] EMPTY_CONFIG_ARRAY = new Configuration[ 0 ];
53
54 /***
55 * The name of configuration element.
56 */
57 private final String m_name;
58
59 /***
60 * The location of configuration element in source.
61 * May be empty string if unknown.
62 */
63 private final String m_location;
64
65 /***
66 * The path of configuration element in document.
67 * May be empty string if unknown.
68 */
69 private final String m_path;
70
71 /***
72 * The attributes defined by configuration (May be null).
73 */
74 private Map m_attributes;
75
76 /***
77 * The child elements defined by
78 * configuration (May be null). If
79 * {@link #m_value} not null then
80 * m_children must be null.
81 */
82 private List m_children;
83
84 /***
85 * The value contained in configuration
86 * (May be null). If {@link #m_children} not
87 * null then m_value must be null.
88 */
89 private String m_value;
90
91 /***
92 * Create a DefaultConfiguration instance.
93 *
94 * @param name the name of configuration element
95 * @param location the location of configuration element in source
96 * @param path the path of configuration element in document
97 */
98 public DefaultConfiguration( final String name,
99 final String location,
100 final String path )
101 {
102 if( null == name )
103 {
104 throw new NullPointerException( "name" );
105 }
106 if( null == path )
107 {
108 throw new NullPointerException( "path" );
109 }
110 if( null == location )
111 {
112 throw new NullPointerException( "location" );
113 }
114 m_name = name;
115 m_path = path;
116 m_location = location;
117 }
118
119 /***
120 * Return the name of the configuration element.
121 *
122 * @return the name of the configuration element.
123 */
124 public String getName()
125 {
126 return m_name;
127 }
128
129 /***
130 * Return the path to the configuration element.
131 * The path should be in the xpath form but may
132 * be the empty string if unabel to determine path.
133 *
134 * @return the path to the configuration element.
135 */
136 public final String getPath()
137 {
138 return m_path;
139 }
140
141 /***
142 * Return the location of configuration element.
143 * Usually of the form "uri[:line number[:column number]]"
144 * if possible. ie "file:myFile.xml:80:2". However the line
145 * number and column number may be elided if unavailable.
146 *
147 * @return the location of configuration element.
148 */
149 public String getLocation()
150 {
151 return m_location;
152 }
153
154 /***
155 * Return an array of all the child elements.
156 *
157 * @return an array of all the child elements.
158 */
159 public Configuration[] getChildren()
160 {
161 final List childList = getChildList();
162 if( null == childList )
163 {
164 return EMPTY_CONFIG_ARRAY;
165 }
166 else
167 {
168 return (Configuration[])childList.toArray( new Configuration[ childList.size() ] );
169 }
170 }
171
172 /***
173 * Return an array of all the child elements with specified name.
174 *
175 * @param name the name of child configuration objects
176 * @return an array of all the child elements with specified name.
177 */
178 public Configuration[] getChildren( final String name )
179 {
180 if( null == name )
181 {
182 throw new NullPointerException( "name" );
183 }
184 final List children = getChildList();
185 if( null == children )
186 {
187 return EMPTY_CONFIG_ARRAY;
188 }
189 else
190 {
191 final ArrayList results = new ArrayList();
192 final int count = children.size();
193 for( int i = 0; i < count; i++ )
194 {
195 final Configuration child = (Configuration)children.get( i );
196 if( child.getName().equals( name ) )
197 {
198 results.add( child );
199 }
200 }
201 return (Configuration[])results.toArray( new Configuration[ results.size() ] );
202 }
203 }
204
205 /***
206 * Return a child Configuration element with specified name.
207 * If no such element exists an element will be autocreated.
208 *
209 * @param name the name of child configuration object
210 * @return a child Configuration element with specified name.
211 */
212 public Configuration getChild( final String name )
213 {
214 return getChild( name, true );
215 }
216
217 /***
218 * Return a child Configuration element with specified name.
219 * If no such element exists and createChild is true then an
220 * element will be autocreated otherwise null will be returned.
221 *
222 * @param name the name of child configuration object
223 * @param createChild true if child should be created if it does not exist
224 * @return a child Configuration element with specified name.
225 */
226 public Configuration getChild( final String name,
227 final boolean createChild )
228 {
229 if( null == name )
230 {
231 throw new NullPointerException( "name" );
232 }
233 final List children = getChildList();
234 if( null != children )
235 {
236 final int count = children.size();
237 for( int i = 0; i < count; i++ )
238 {
239 final Configuration child = (Configuration)children.get( i );
240 if( child.getName().equals( name ) )
241 {
242 return child;
243 }
244 }
245 }
246 if( createChild )
247 {
248 final String path = getPath() + ConfigurationUtil.PATH_SEPARATOR + getName();
249 return new DefaultConfiguration( name, generateLocation(), path );
250 }
251 else
252 {
253 return null;
254 }
255
256 }
257
258 /***
259 * Return text value of element.
260 *
261 * @return the value
262 * @throws ConfigurationException if no value in element
263 */
264 public String getValue()
265 throws ConfigurationException
266 {
267 if( null != m_value )
268 {
269 return m_value;
270 }
271 else
272 {
273 final String message = "No value specified";
274 throw new ConfigurationException( message, getPath(), getLocation() );
275 }
276 }
277
278 /***
279 * Return text value of element.
280 * Use specified default if no value in element.
281 *
282 * @param defaultValue the default value
283 * @return the value
284 */
285 public String getValue( final String defaultValue )
286 {
287 if( null != m_value )
288 {
289 return m_value;
290 }
291 else
292 {
293 return defaultValue;
294 }
295 }
296
297 /***
298 * Return text value of element as a boolean.
299 *
300 * @return the value
301 * @throws ConfigurationException if no value in element
302 * or value can not be converted to correct type
303 */
304 public boolean getValueAsBoolean()
305 throws ConfigurationException
306 {
307 return getValue().equals( "true" );
308 }
309
310 /***
311 * Return text value of element as a boolean.
312 * Use specified default if no value in element or
313 * value can not be converted to correct type.
314 *
315 * @param defaultValue the default value
316 * @return the value
317 */
318 public boolean getValueAsBoolean( final boolean defaultValue )
319 {
320 if( null == m_value )
321 {
322 return defaultValue;
323 }
324 else
325 {
326 return m_value.equals( TRUE_STRING );
327 }
328 }
329
330 /***
331 * Return text value of element as an integer.
332 *
333 * @return the value
334 * @throws ConfigurationException if no value in element
335 * or value can not be converted to correct type
336 */
337 public int getValueAsInteger()
338 throws ConfigurationException
339 {
340 try
341 {
342 return Integer.parseInt( getValue() );
343 }
344 catch( final NumberFormatException nfe )
345 {
346 final String message =
347 "Unable to parse " + getValue() + " as an integer";
348 throw new ConfigurationException( message, getPath(), getLocation(), nfe );
349 }
350 }
351
352 /***
353 * Return text value of element as an integer.
354 * Use specified default if no value in element or
355 * value can not be converted to correct type.
356 *
357 * @param defaultValue the default value
358 * @return the value
359 */
360 public int getValueAsInteger( final int defaultValue )
361 {
362 if( null == m_value )
363 {
364 return defaultValue;
365 }
366 else
367 {
368 try
369 {
370 return Integer.parseInt( m_value );
371 }
372 catch( final NumberFormatException nfe )
373 {
374 return defaultValue;
375 }
376 }
377 }
378
379 /***
380 * Return text value of element as a long.
381 *
382 * @return the value
383 * @throws ConfigurationException if no value in element
384 * or value can not be converted to correct type
385 */
386 public long getValueAsLong()
387 throws ConfigurationException
388 {
389 try
390 {
391 return Long.parseLong( getValue() );
392 }
393 catch( final NumberFormatException nfe )
394 {
395 final String message =
396 "Unable to parse " + getValue() + " as a Long";
397 throw new ConfigurationException( message, getPath(), getLocation(), nfe );
398 }
399 }
400
401 /***
402 * Return text value of element as a long.
403 * Use specified default if no value in element or
404 * value can not be converted to correct type.
405 *
406 * @param defaultValue the default value
407 * @return the value
408 */
409 public long getValueAsLong( final long defaultValue )
410 {
411 if( null == m_value )
412 {
413 return defaultValue;
414 }
415 else
416 {
417 try
418 {
419 return Long.parseLong( m_value );
420 }
421 catch( final NumberFormatException nfe )
422 {
423 return defaultValue;
424 }
425 }
426 }
427
428 /***
429 * Return text value of element as a float.
430 *
431 * @return the value
432 * @throws ConfigurationException if no value in element
433 * or value can not be converted to correct type
434 */
435 public float getValueAsFloat()
436 throws ConfigurationException
437 {
438 try
439 {
440 return Float.parseFloat( getValue() );
441 }
442 catch( final NumberFormatException nfe )
443 {
444 final String message =
445 "Unable to parse " + getValue() + " as a Long";
446 throw new ConfigurationException( message, getPath(), getLocation(), nfe );
447 }
448 }
449
450 /***
451 * Return text value of element as a float.
452 * Use specified default if no value in element or
453 * value can not be converted to correct type.
454 *
455 * @param defaultValue the default value
456 * @return the value
457 */
458 public float getValueAsFloat( final float defaultValue )
459 {
460 if( null == m_value )
461 {
462 return defaultValue;
463 }
464 else
465 {
466 try
467 {
468 return Float.parseFloat( m_value );
469 }
470 catch( final NumberFormatException nfe )
471 {
472 return defaultValue;
473 }
474 }
475 }
476
477 /***
478 * Return an array of all the attribute names.
479 *
480 * @return an array of all the attribute names.
481 */
482 public String[] getAttributeNames()
483 {
484 final Map attributeMap = getAttributeMap();
485 if( null == attributeMap )
486 {
487 return EMPTY_STRING_ARRAY;
488 }
489 else
490 {
491 final Set keys = attributeMap.keySet();
492 return (String[])attributeMap.keySet().toArray( new String[ keys.size() ] );
493 }
494 }
495
496 /***
497 * Return attribute value with specified name.
498 *
499 * @param name the attribute name
500 * @return the attribute value
501 * @throws ConfigurationException if no attribute with
502 * specified name
503 */
504 public String getAttribute( final String name )
505 throws ConfigurationException
506 {
507 final String value = doGetAttribute( name );
508 if( null != value )
509 {
510 return value;
511 }
512 else
513 {
514 final String message =
515 "Attribute named " + name + " not specified.";
516 throw new ConfigurationException( message, getPath(), getLocation() );
517 }
518 }
519
520 /***
521 * Return attribute value with specified name.
522 * If no attribute with specified name then return
523 * default value.
524 *
525 * @param name the attribute name
526 * @param defaultValue the default value
527 * @return the attribute value
528 */
529 public String getAttribute( final String name,
530 final String defaultValue )
531 {
532 final String value = doGetAttribute( name );
533 if( null != value )
534 {
535 return value;
536 }
537 else
538 {
539 return defaultValue;
540 }
541 }
542
543 /***
544 * Return attribute value with specified name or null
545 * if no such attribute.
546 *
547 * @param name the attribute name
548 * @return the attribute value
549 */
550 private String doGetAttribute( final String name )
551 {
552 if( null == name )
553 {
554 throw new NullPointerException( "name" );
555 }
556 final Map attributeMap = getAttributeMap();
557 if( null != attributeMap )
558 {
559 final String value = (String)attributeMap.get( name );
560 if( null != value )
561 {
562 return value;
563 }
564 }
565 return null;
566 }
567
568 /***
569 * Return attribute value with specified name as a boolean.
570 *
571 * @param name the attribute name
572 * @return the attribute value
573 * @throws ConfigurationException if no attribute with
574 * specified name or attribute can not be converted
575 * to correct type
576 */
577 public boolean getAttributeAsBoolean( final String name )
578 throws ConfigurationException
579 {
580 return getAttribute( name ).equals( TRUE_STRING );
581 }
582
583 /***
584 * Return attribute value with specified name as a boolean.
585 * If no attribute with specified name or attribute can
586 * not be converted to correct type then return
587 * default value.
588 *
589 * @param name the attribute name
590 * @param defaultValue the default value
591 * @return the attribute value
592 */
593 public boolean getAttributeAsBoolean( final String name,
594 final boolean defaultValue )
595 {
596 final String value = getAttribute( name, null );
597 if( null != value )
598 {
599 return value.equals( TRUE_STRING );
600 }
601 return defaultValue;
602 }
603
604 /***
605 * Return attribute value with specified name as an integer.
606 *
607 * @param name the attribute name
608 * @return the attribute value
609 * @throws ConfigurationException if no attribute with
610 * specified name or attribute can not be converted
611 * to correct type
612 */
613 public int getAttributeAsInteger( final String name )
614 throws ConfigurationException
615 {
616 final String value = getAttribute( name );
617 try
618 {
619 return Integer.parseInt( value );
620 }
621 catch( final NumberFormatException nfe )
622 {
623 final String message =
624 "Unable to parse " + value + " as an Integer.";
625 throw new ConfigurationException( message, getPath(), getLocation() );
626 }
627 }
628
629 /***
630 * Return attribute value with specified name as an integer.
631 * If no attribute with specified name or attribute can
632 * not be converted to correct type then return
633 * default value.
634 *
635 * @param name the attribute name
636 * @param defaultValue the default value
637 * @return the attribute value
638 */
639 public int getAttributeAsInteger( final String name,
640 final int defaultValue )
641 {
642 final String value = getAttribute( name, null );
643 if( null != value )
644 {
645 try
646 {
647 return Integer.parseInt( value );
648 }
649 catch( final NumberFormatException nfe )
650 {
651
652 }
653 }
654 return defaultValue;
655 }
656
657 /***
658 * Return attribute value with specified name as a long.
659 *
660 * @param name the attribute name
661 * @return the attribute value
662 * @throws ConfigurationException if no attribute with
663 * specified name or attribute can not be converted
664 * to correct type
665 */
666 public long getAttributeAsLong( final String name )
667 throws ConfigurationException
668 {
669 final String value = getAttribute( name );
670 try
671 {
672 return Long.parseLong( value );
673 }
674 catch( final NumberFormatException nfe )
675 {
676 final String message =
677 "Unable to parse " + value + " as a Long.";
678 throw new ConfigurationException( message, getPath(), getLocation() );
679 }
680 }
681
682 /***
683 * Return attribute value with specified name as a long.
684 * If no attribute with specified name or attribute can
685 * not be converted to correct type then return
686 * default value.
687 *
688 * @param name the attribute name
689 * @param defaultValue the default value
690 * @return the attribute value
691 */
692 public long getAttributeAsLong( final String name,
693 final long defaultValue )
694 {
695 final String value = getAttribute( name, null );
696 if( null != value )
697 {
698 try
699 {
700 return Long.parseLong( value );
701 }
702 catch( final NumberFormatException nfe )
703 {
704
705 }
706 }
707 return defaultValue;
708 }
709
710 /***
711 * Return attribute value with specified name as afloat.
712 *
713 * @param name the attribute name
714 * @return the attribute value
715 * @throws ConfigurationException if no attribute with
716 * specified name or attribute can not be converted
717 * to correct type
718 */
719 public float getAttributeAsFloat( final String name )
720 throws ConfigurationException
721 {
722 final String value = getAttribute( name );
723 try
724 {
725 return Float.parseFloat( value );
726 }
727 catch( final NumberFormatException nfe )
728 {
729 final String message =
730 "Unable to parse " + value + " as a Float.";
731 throw new ConfigurationException( message, getPath(), getLocation() );
732 }
733 }
734
735 /***
736 * Return attribute value with specified name as a float.
737 * If no attribute with specified name or attribute can
738 * not be converted to correct type then return
739 * default value.
740 *
741 * @param name the attribute name
742 * @param defaultValue the default value
743 * @return the attribute value
744 */
745 public float getAttributeAsFloat( final String name,
746 final float defaultValue )
747 {
748 final String value = getAttribute( name, null );
749 if( null != value )
750 {
751 try
752 {
753 return Float.parseFloat( value );
754 }
755 catch( final NumberFormatException nfe )
756 {
757
758 }
759 }
760 return defaultValue;
761 }
762
763 /***
764 * Mark the configuration and child configurations as read only.
765 */
766 public void makeReadOnly()
767 {
768 super.makeReadOnly();
769 final List children = getChildList();
770 if( null != children )
771 {
772 final int count = children.size();
773 for( int i = 0; i < count; i++ )
774 {
775 final Configuration configuration = (Configuration)children.get( i );
776 if( configuration instanceof Freezable )
777 {
778 ( (Freezable)configuration ).makeReadOnly();
779 }
780 }
781 }
782 }
783
784 /***
785 * Set an attribute of configuration.
786 *
787 * @param key the attribute key
788 * @param value the attribute value
789 */
790 public void setAttribute( final String key,
791 final String value )
792 {
793 if( null == key )
794 {
795 throw new NullPointerException( "key" );
796 }
797 if( null == value )
798 {
799 throw new NullPointerException( "value" );
800 }
801 checkWriteable();
802 if( null == m_attributes )
803 {
804 m_attributes = new HashMap();
805 }
806 m_attributes.put( key, value );
807 }
808
809 /***
810 * Add a child configuration element.
811 *
812 * @param configuration the child configuration element.
813 */
814 public void addChild( final Configuration configuration )
815 {
816 if( null == configuration )
817 {
818 throw new NullPointerException( "configuration" );
819 }
820 checkWriteable();
821 if( null != m_value )
822 {
823 throwMixedContentException();
824 }
825 if( null == m_children )
826 {
827 m_children = new ArrayList();
828 }
829 m_children.add( configuration );
830 }
831
832 /***
833 * Set the value of the configuration element.
834 *
835 * @param value the value of the configuration element.
836 */
837 public void setValue( final String value )
838 {
839 if( null == value )
840 {
841 throw new NullPointerException( "value" );
842 }
843 checkWriteable();
844 final List children = getChildList();
845 if( null != children && 0 != children.size() )
846 {
847 throwMixedContentException();
848 }
849 m_value = value;
850 }
851
852 /***
853 * Overide toString to improve ability to debug implementation.
854 *
855 * @return string representation of object
856 */
857 public String toString()
858 {
859 final StringBuffer sb = new StringBuffer();
860 sb.append( "[Configuration name='" );
861 sb.append( getName() );
862 sb.append( "'" );
863 if( null != m_attributes )
864 {
865 sb.append( " attributes=" );
866 sb.append( m_attributes );
867 }
868 sb.append( "]" );
869 return sb.toString();
870 }
871
872 /***
873 * Return the list of child configuration objects.
874 *
875 * @return the list of child configuration objects.
876 */
877 protected final List getChildList()
878 {
879 return m_children;
880 }
881
882 /***
883 * Return the backing map for attributes.
884 *
885 * @return the backing map for attributes.
886 */
887 protected final Map getAttributeMap()
888 {
889 return m_attributes;
890 }
891
892 /***
893 * Generate a location string that postfixes
894 * autogenerated marker.
895 *
896 * @return a autogenerated location string
897 */
898 protected final String generateLocation()
899 {
900 final String location = getLocation();
901 if( !location.endsWith( AUTOGEN_POSTFIX ) )
902 {
903 return location + AUTOGEN_POSTFIX;
904 }
905 else
906 {
907 return location;
908 }
909 }
910
911 /***
912 * Throw an IllegalStateException warning about
913 * mixed content.
914 */
915 protected final void throwMixedContentException()
916 {
917 final String message =
918 "Configuration objects do not support Mixed content. " +
919 "Configuration elements should not have both a value and " +
920 "child elements.";
921 throw new IllegalStateException( message );
922 }
923 }