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 * 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 }