View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Set;
27  
28  /**
29   * <p>A base class for converters that transform a normal configuration
30   * object into a hierarchical configuration.</p>
31   * <p>This class provides a default mechanism for iterating over the keys in a
32   * configuration and to throw corresponding element start and end events. By
33   * handling these events a hierarchy can be constructed that is equivalent to
34   * the keys in the original configuration.</p>
35   * <p>Concrete sub classes will implement event handlers that generate SAX
36   * events for XML processing or construct a
37   * {@code HierarchicalConfiguration} root node. All in all with this class
38   * it is possible to treat a default configuration as if it was a hierarchical
39   * configuration, which can be sometimes useful.</p>
40   * @see HierarchicalConfiguration
41   *
42   * @author <a
43   * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
44   * @version $Id: HierarchicalConfigurationConverter.java 1234985 2012-01-23 21:09:09Z oheger $
45   */
46  abstract class HierarchicalConfigurationConverter
47  {
48      /**
49       * Processes the specified configuration object. This method implements
50       * the iteration over the configuration's keys. All defined keys are
51       * translated into a set of element start and end events represented by
52       * calls to the {@code elementStart()} and
53       * {@code elementEnd()} methods.
54       *
55       * @param config the configuration to be processed
56       */
57      public void process(Configuration config)
58      {
59          if (config != null)
60          {
61              ConfigurationKey keyEmpty = new ConfigurationKey();
62              ConfigurationKey keyLast = keyEmpty;
63              Set<String> keySet = new HashSet<String>();
64  
65              for (Iterator<String> it = config.getKeys(); it.hasNext();)
66              {
67                  String key = it.next();
68                  if (keySet.contains(key))
69                  {
70                      // this key has already been processed by openElements
71                      continue;
72                  }
73                  ConfigurationKey keyAct = new ConfigurationKey(key);
74                  closeElements(keyLast, keyAct);
75                  String elem = openElements(keyLast, keyAct, config, keySet);
76                  fireValue(elem, config.getProperty(key));
77                  keyLast = keyAct;
78              }
79  
80              // close all open
81              closeElements(keyLast, keyEmpty);
82          }
83      }
84  
85      /**
86       * An event handler method that is called when an element starts.
87       * Concrete sub classes must implement it to perform a proper event
88       * handling.
89       *
90       * @param name the name of the new element
91       * @param value the element's value; can be <b>null</b> if the element
92       * does not have any value
93       */
94      protected abstract void elementStart(String name, Object value);
95  
96      /**
97       * An event handler method that is called when an element ends. For each
98       * call of {@code elementStart()} there will be a corresponding call
99       * of this method. Concrete sub classes must implement it to perform a
100      * proper event handling.
101      *
102      * @param name the name of the ending element
103      */
104     protected abstract void elementEnd(String name);
105 
106     /**
107      * Fires all necessary element end events for the specified keys. This
108      * method is called for each key obtained from the configuration to be
109      * converted. It calculates the common part of the actual and the last
110      * processed key and thus determines how many elements must be
111      * closed.
112      *
113      * @param keyLast the last processed key
114      * @param keyAct the actual key
115      */
116     protected void closeElements(ConfigurationKey keyLast, ConfigurationKey keyAct)
117     {
118         ConfigurationKey keyDiff = keyAct.differenceKey(keyLast);
119         Iterator<String> it = reverseIterator(keyDiff);
120         if (it.hasNext())
121         {
122             // Skip first because it has already been closed by fireValue()
123             it.next();
124         }
125 
126         while (it.hasNext())
127         {
128             elementEnd(it.next());
129         }
130     }
131 
132     /**
133      * Helper method for determining a reverse iterator for the specified key.
134      * This implementation returns an iterator that returns the parts of the
135      * given key in reverse order, ignoring indices.
136      *
137      * @param key the key
138      * @return a reverse iterator for the parts of this key
139      */
140     protected Iterator<String> reverseIterator(ConfigurationKey key)
141     {
142         List<String> list = new ArrayList<String>();
143         for (ConfigurationKey.KeyIterator it = key.iterator(); it.hasNext();)
144         {
145             list.add(it.nextKey());
146         }
147 
148         Collections.reverse(list);
149         return list.iterator();
150     }
151 
152     /**
153      * Fires all necessary element start events for the specified key. This
154      * method is called for each key obtained from the configuration to be
155      * converted. It ensures that all elements "between" the last key and the
156      * actual key are opened and their values are set.
157      *
158      * @param keyLast the last processed key
159      * @param keyAct the actual key
160      * @param config the configuration to process
161      * @param keySet the set with the processed keys
162      * @return the name of the last element on the path
163      */
164     protected String openElements(ConfigurationKey keyLast, ConfigurationKey keyAct,
165             Configuration config, Set<String> keySet)
166     {
167         ConfigurationKey.KeyIterator it = keyLast.differenceKey(keyAct).iterator();
168         ConfigurationKey k = keyLast.commonKey(keyAct);
169         for (it.nextKey(); it.hasNext(); it.nextKey())
170         {
171             k.append(it.currentKey(true));
172             elementStart(it.currentKey(true), config.getProperty(k.toString()));
173             keySet.add(k.toString());
174         }
175         return it.currentKey(true);
176     }
177 
178     /**
179      * Fires all necessary element start events with the actual element values.
180      * This method is called for each key obtained from the configuration to be
181      * processed with the last part of the key as argument. The value can be
182      * either a single value or a collection.
183      *
184      * @param name the name of the actual element
185      * @param value the element's value
186      */
187     protected void fireValue(String name, Object value)
188     {
189         if (value instanceof Collection)
190         {
191             Collection<?> valueCol = (Collection<?>) value;
192             for (Object v : valueCol)
193             {
194                 fireValue(name, v);
195             }
196         }
197         else
198         {
199             elementStart(name, value);
200             elementEnd(name);
201         }
202     }
203 }