View Javadoc

1   /*
2    * Copyright (C) The DNA Group. All rights reserved.
3    *
4    * This software is published under the terms of the DNA
5    * Software License version 1.1, a copy of which has been included
6    * with this distribution in the LICENSE.txt file.
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                 //Fall through to return defaultValue
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                 //Fall through to return defaultValue
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                 //Fall through to return defaultValue
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 }