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