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 * https://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.configuration2;
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 import org.apache.commons.configuration2.tree.DefaultConfigurationKey;
29 import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
30
31 /**
32 * <p>
33 * A base class for converters that transform a normal configuration object into a hierarchical configuration.
34 * </p>
35 * <p>
36 * This class provides a default mechanism for iterating over the keys in a configuration and to throw corresponding
37 * element start and end events. By handling these events a hierarchy can be constructed that is equivalent to the keys
38 * in the original configuration.
39 * </p>
40 * <p>
41 * Concrete sub classes will implement event handlers that generate SAX events for XML processing or construct a
42 * {@code HierarchicalConfiguration} root node. All in all with this class it is possible to treat a default
43 * configuration as if it was a hierarchical configuration, which can be sometimes useful.
44 * </p>
45 *
46 * @see HierarchicalConfiguration
47 */
48 abstract class HierarchicalConfigurationConverter {
49 /**
50 * Fires all necessary element end events for the specified keys. This method is called for each key obtained from the
51 * configuration to be converted. It calculates the common part of the actual and the last processed key and thus
52 * determines how many elements must be closed.
53 *
54 * @param keyLast the last processed key
55 * @param keyAct the actual key
56 */
57 protected void closeElements(final DefaultConfigurationKey keyLast, final DefaultConfigurationKey keyAct) {
58 final DefaultConfigurationKey keyDiff = keyAct.differenceKey(keyLast);
59 final Iterator<String> it = reverseIterator(keyDiff);
60 if (it.hasNext()) {
61 // Skip first because it has already been closed by fireValue()
62 it.next();
63 }
64
65 while (it.hasNext()) {
66 elementEnd(it.next());
67 }
68 }
69
70 /**
71 * An event handler method that is called when an element ends. For each call of {@code elementStart()} there will be a
72 * corresponding call of this method. Concrete sub classes must implement it to perform a proper event handling.
73 *
74 * @param name the name of the ending element
75 */
76 protected abstract void elementEnd(String name);
77
78 /**
79 * An event handler method that is called when an element starts. Concrete sub classes must implement it to perform a
80 * proper event handling.
81 *
82 * @param name the name of the new element
83 * @param value the element's value; can be <strong>null</strong> if the element does not have any value
84 */
85 protected abstract void elementStart(String name, Object value);
86
87 /**
88 * Fires all necessary element start events with the actual element values. This method is called for each key obtained
89 * from the configuration to be processed with the last part of the key as argument. The value can be either a single
90 * value or a collection.
91 *
92 * @param name the name of the actual element
93 * @param value the element's value
94 */
95 protected void fireValue(final String name, final Object value) {
96 if (value instanceof Collection) {
97 final Collection<?> valueCol = (Collection<?>) value;
98 valueCol.forEach(v -> fireValue(name, v));
99 } else {
100 elementStart(name, value);
101 elementEnd(name);
102 }
103 }
104
105 /**
106 * Fires all necessary element start events for the specified key. This method is called for each key obtained from the
107 * configuration to be converted. It ensures that all elements "between" the last key and the actual key are opened and
108 * their values are set.
109 *
110 * @param keyLast the last processed key
111 * @param keyAct the actual key
112 * @param config the configuration to process
113 * @param keySet the set with the processed keys
114 * @return the name of the last element on the path
115 */
116 protected String openElements(final DefaultConfigurationKey keyLast, final DefaultConfigurationKey keyAct, final Configuration config,
117 final Set<String> keySet) {
118 final DefaultConfigurationKey.KeyIterator it = keyLast.differenceKey(keyAct).iterator();
119 final DefaultConfigurationKey k = keyLast.commonKey(keyAct);
120 for (it.nextKey(); it.hasNext(); it.nextKey()) {
121 k.append(it.currentKey(true));
122 elementStart(it.currentKey(true), config.getProperty(k.toString()));
123 keySet.add(k.toString());
124 }
125 return it.currentKey(true);
126 }
127
128 /**
129 * Processes the specified configuration object. This method implements the iteration over the configuration's keys. All
130 * defined keys are translated into a set of element start and end events represented by calls to the
131 * {@code elementStart()} and {@code elementEnd()} methods.
132 *
133 * @param config the configuration to be processed
134 */
135 public void process(final Configuration config) {
136 if (config != null) {
137 final DefaultExpressionEngine exprEngine = DefaultExpressionEngine.INSTANCE;
138 final DefaultConfigurationKey keyEmpty = new DefaultConfigurationKey(exprEngine);
139 DefaultConfigurationKey keyLast = keyEmpty;
140 final Set<String> keySet = new HashSet<>();
141
142 for (final Iterator<String> it = config.getKeys(); it.hasNext();) {
143 final String key = it.next();
144 if (keySet.contains(key)) {
145 // this key has already been processed by openElements
146 continue;
147 }
148 final DefaultConfigurationKey keyAct = new DefaultConfigurationKey(exprEngine, key);
149 closeElements(keyLast, keyAct);
150 final String elem = openElements(keyLast, keyAct, config, keySet);
151 fireValue(elem, config.getProperty(key));
152 keyLast = keyAct;
153 }
154
155 // close all open
156 closeElements(keyLast, keyEmpty);
157 }
158 }
159
160 /**
161 * Helper method for determining a reverse iterator for the specified key. This implementation returns an iterator that
162 * returns the parts of the given key in reverse order, ignoring indices.
163 *
164 * @param key the key
165 * @return a reverse iterator for the parts of this key
166 */
167 protected Iterator<String> reverseIterator(final DefaultConfigurationKey key) {
168 final List<String> list = new ArrayList<>();
169 for (final DefaultConfigurationKey.KeyIterator it = key.iterator(); it.hasNext();) {
170 list.add(it.nextKey());
171 }
172 Collections.reverse(list);
173 return list.iterator();
174 }
175 }