001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration2;
019
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027import java.util.stream.Collectors;
028
029import org.apache.commons.configuration2.event.ConfigurationEvent;
030import org.apache.commons.configuration2.event.EventListener;
031import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
032import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
033import org.apache.commons.configuration2.tree.ConfigurationNodeVisitorAdapter;
034import org.apache.commons.configuration2.tree.ImmutableNode;
035import org.apache.commons.configuration2.tree.InMemoryNodeModel;
036import org.apache.commons.configuration2.tree.InMemoryNodeModelSupport;
037import org.apache.commons.configuration2.tree.NodeHandler;
038import org.apache.commons.configuration2.tree.NodeModel;
039import org.apache.commons.configuration2.tree.NodeSelector;
040import org.apache.commons.configuration2.tree.NodeTreeWalker;
041import org.apache.commons.configuration2.tree.QueryResult;
042import org.apache.commons.configuration2.tree.ReferenceNodeHandler;
043import org.apache.commons.configuration2.tree.TrackedNodeModel;
044import org.apache.commons.lang3.ObjectUtils;
045
046/**
047 * <p>
048 * A specialized hierarchical configuration implementation that is based on a structure of {@link ImmutableNode}
049 * objects.
050 * </p>
051 */
052public class BaseHierarchicalConfiguration extends AbstractHierarchicalConfiguration<ImmutableNode> implements InMemoryNodeModelSupport {
053
054    /** A listener for reacting on changes caused by sub configurations. */
055    private final EventListener<ConfigurationEvent> changeListener;
056
057    /**
058     * Creates a new instance of {@code BaseHierarchicalConfiguration}.
059     */
060    public BaseHierarchicalConfiguration() {
061        this((HierarchicalConfiguration<ImmutableNode>) null);
062    }
063
064    /**
065     * Creates a new instance of {@code BaseHierarchicalConfiguration} and copies all data contained in the specified
066     * configuration into the new one.
067     *
068     * @param c the configuration that is to be copied (if <b>null</b>, this constructor will behave like the standard
069     *        constructor)
070     * @since 1.4
071     */
072    public BaseHierarchicalConfiguration(final HierarchicalConfiguration<ImmutableNode> c) {
073        this(createNodeModel(c));
074    }
075
076    /**
077     * Creates a new instance of {@code BaseHierarchicalConfiguration} and initializes it with the given {@code NodeModel}.
078     *
079     * @param model the {@code NodeModel}
080     */
081    protected BaseHierarchicalConfiguration(final NodeModel<ImmutableNode> model) {
082        super(model);
083        changeListener = createChangeListener();
084    }
085
086    /**
087     * {@inheritDoc} This implementation returns the {@code InMemoryNodeModel} used by this configuration.
088     */
089    @Override
090    public InMemoryNodeModel getNodeModel() {
091        return (InMemoryNodeModel) super.getNodeModel();
092    }
093
094    /**
095     * Creates a new {@code Configuration} object containing all keys that start with the specified prefix. This
096     * implementation will return a {@code BaseHierarchicalConfiguration} object so that the structure of the keys will be
097     * saved. The nodes selected by the prefix (it is possible that multiple nodes are selected) are mapped to the root node
098     * of the returned configuration, i.e. their children and attributes will become children and attributes of the new root
099     * node. However, a value of the root node is only set if exactly one of the selected nodes contain a value (if multiple
100     * nodes have a value, there is simply no way to decide how these values are merged together). Note that the returned
101     * {@code Configuration} object is not connected to its source configuration: updates on the source configuration are
102     * not reflected in the subset and vice versa. The returned configuration uses the same {@code Synchronizer} as this
103     * configuration.
104     *
105     * @param prefix the prefix of the keys for the subset
106     * @return a new configuration object representing the selected subset
107     */
108    @Override
109    public Configuration subset(final String prefix) {
110        beginRead(false);
111        try {
112            final List<QueryResult<ImmutableNode>> results = fetchNodeList(prefix);
113            if (results.isEmpty()) {
114                return new BaseHierarchicalConfiguration();
115            }
116
117            final BaseHierarchicalConfiguration parent = this;
118            final BaseHierarchicalConfiguration result = new BaseHierarchicalConfiguration() {
119                // Override interpolate to always interpolate on the parent
120                @Override
121                protected Object interpolate(final Object value) {
122                    return parent.interpolate(value);
123                }
124
125                @Override
126                public ConfigurationInterpolator getInterpolator() {
127                    return parent.getInterpolator();
128                }
129            };
130            result.getModel().setRootNode(createSubsetRootNode(results));
131
132            if (result.isEmpty()) {
133                return new BaseHierarchicalConfiguration();
134            }
135            result.setSynchronizer(getSynchronizer());
136            return result;
137        } finally {
138            endRead();
139        }
140    }
141
142    /**
143     * Creates a root node for a subset configuration based on the passed in query results. This method creates a new root
144     * node and adds the children and attributes of all result nodes to it. If only a single node value is defined, it is
145     * assigned as value of the new root node.
146     *
147     * @param results the collection of query results
148     * @return the root node for the subset configuration
149     */
150    private ImmutableNode createSubsetRootNode(final Collection<QueryResult<ImmutableNode>> results) {
151        final ImmutableNode.Builder builder = new ImmutableNode.Builder();
152        Object value = null;
153        int valueCount = 0;
154
155        for (final QueryResult<ImmutableNode> result : results) {
156            if (result.isAttributeResult()) {
157                builder.addAttribute(result.getAttributeName(), result.getAttributeValue(getModel().getNodeHandler()));
158            } else {
159                if (result.getNode().getValue() != null) {
160                    value = result.getNode().getValue();
161                    valueCount++;
162                }
163                builder.addChildren(result.getNode().getChildren());
164                builder.addAttributes(result.getNode().getAttributes());
165            }
166        }
167
168        if (valueCount == 1) {
169            builder.value(value);
170        }
171        return builder.create();
172    }
173
174    /**
175     * {@inheritDoc} The result of this implementation depends on the {@code supportUpdates} flag: If it is <b>false</b>, a
176     * plain {@code BaseHierarchicalConfiguration} is returned using the selected node as root node. This is suitable for
177     * read-only access to properties. Because the configuration returned in this case is not connected to the parent
178     * configuration, updates on properties made by one configuration are not reflected by the other one. A value of
179     * <b>true</b> for this parameter causes a tracked node to be created, and result is a {@link SubnodeConfiguration}
180     * based on this tracked node. This configuration is really connected to its parent, so that updated properties are
181     * visible on both.
182     *
183     * @see SubnodeConfiguration
184     * @throws ConfigurationRuntimeException if the key does not select a single node
185     */
186    @Override
187    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) {
188        beginRead(false);
189        try {
190            return supportUpdates ? createConnectedSubConfiguration(key) : createIndependentSubConfiguration(key);
191        } finally {
192            endRead();
193        }
194    }
195
196    /**
197     * Gets the {@code InMemoryNodeModel} to be used as parent model for a new sub configuration. This method is called
198     * whenever a sub configuration is to be created. This base implementation returns the model of this configuration. Sub
199     * classes with different requirements for the parent models of sub configurations have to override it.
200     *
201     * @return the parent model for a new sub configuration
202     */
203    protected InMemoryNodeModel getSubConfigurationParentModel() {
204        return (InMemoryNodeModel) getModel();
205    }
206
207    /**
208     * Gets the {@code NodeSelector} to be used for a sub configuration based on the passed in key. This method is called
209     * whenever a sub configuration is to be created. This base implementation returns a new {@code NodeSelector}
210     * initialized with the passed in key. Sub classes may override this method if they have a different strategy for
211     * creating a selector.
212     *
213     * @param key the key of the sub configuration
214     * @return a {@code NodeSelector} for initializing a sub configuration
215     * @since 2.0
216     */
217    protected NodeSelector getSubConfigurationNodeSelector(final String key) {
218        return new NodeSelector(key);
219    }
220
221    /**
222     * Creates a connected sub configuration based on a selector for a tracked node.
223     *
224     * @param selector the {@code NodeSelector}
225     * @param parentModelSupport the {@code InMemoryNodeModelSupport} object for the parent node model
226     * @return the newly created sub configuration
227     * @since 2.0
228     */
229    protected SubnodeConfiguration createSubConfigurationForTrackedNode(final NodeSelector selector, final InMemoryNodeModelSupport parentModelSupport) {
230        final SubnodeConfiguration subConfig = new SubnodeConfiguration(this, new TrackedNodeModel(parentModelSupport, selector, true));
231        initSubConfigurationForThisParent(subConfig);
232        return subConfig;
233    }
234
235    /**
236     * Initializes a {@code SubnodeConfiguration} object. This method should be called for each sub configuration created
237     * for this configuration. It ensures that the sub configuration is correctly connected to its parent instance and that
238     * update events are correctly propagated.
239     *
240     * @param subConfig the sub configuration to be initialized
241     * @since 2.0
242     */
243    protected void initSubConfigurationForThisParent(final SubnodeConfiguration subConfig) {
244        initSubConfiguration(subConfig);
245        subConfig.addEventListener(ConfigurationEvent.ANY, changeListener);
246    }
247
248    /**
249     * Creates a sub configuration from the specified key which is connected to this configuration. This implementation
250     * creates a {@link SubnodeConfiguration} with a tracked node identified by the passed in key.
251     *
252     * @param key the key of the sub configuration
253     * @return the new sub configuration
254     */
255    private BaseHierarchicalConfiguration createConnectedSubConfiguration(final String key) {
256        final NodeSelector selector = getSubConfigurationNodeSelector(key);
257        getSubConfigurationParentModel().trackNode(selector, this);
258        return createSubConfigurationForTrackedNode(selector, this);
259    }
260
261    /**
262     * Creates a list of connected sub configurations based on a passed in list of node selectors.
263     *
264     * @param parentModelSupport the parent node model support object
265     * @param selectors the list of {@code NodeSelector} objects
266     * @return the list with sub configurations
267     */
268    private List<HierarchicalConfiguration<ImmutableNode>> createConnectedSubConfigurations(final InMemoryNodeModelSupport parentModelSupport,
269        final Collection<NodeSelector> selectors) {
270        return selectors.stream().map(sel -> createSubConfigurationForTrackedNode(sel, parentModelSupport)).collect(Collectors.toList());
271    }
272
273    /**
274     * Creates a sub configuration from the specified key which is independent on this configuration. This means that the
275     * sub configuration operates on a separate node model (although the nodes are initially shared).
276     *
277     * @param key the key of the sub configuration
278     * @return the new sub configuration
279     */
280    private BaseHierarchicalConfiguration createIndependentSubConfiguration(final String key) {
281        final List<ImmutableNode> targetNodes = fetchFilteredNodeResults(key);
282        final int size = targetNodes.size();
283        if (size != 1) {
284            throw new ConfigurationRuntimeException("Passed in key must select exactly one node (found %,d): %s", size, key);
285        }
286        final BaseHierarchicalConfiguration sub = new BaseHierarchicalConfiguration(new InMemoryNodeModel(targetNodes.get(0)));
287        initSubConfiguration(sub);
288        return sub;
289    }
290
291    /**
292     * Returns an initialized sub configuration for this configuration that is based on another
293     * {@code BaseHierarchicalConfiguration}. Thus, it is independent from this configuration.
294     *
295     * @param node the root node for the sub configuration
296     * @return the initialized sub configuration
297     */
298    private BaseHierarchicalConfiguration createIndependentSubConfigurationForNode(final ImmutableNode node) {
299        final BaseHierarchicalConfiguration sub = new BaseHierarchicalConfiguration(new InMemoryNodeModel(node));
300        initSubConfiguration(sub);
301        return sub;
302    }
303
304    /**
305     * Executes a query on the specified key and filters it for node results.
306     *
307     * @param key the key
308     * @return the filtered list with result nodes
309     */
310    private List<ImmutableNode> fetchFilteredNodeResults(final String key) {
311        final NodeHandler<ImmutableNode> handler = getModel().getNodeHandler();
312        return resolveNodeKey(handler.getRootNode(), key, handler);
313    }
314
315    /**
316     * {@inheritDoc} This implementation creates a {@code SubnodeConfiguration} by delegating to {@code configurationAt()}.
317     * Then an immutable wrapper is created and returned.
318     */
319    @Override
320    public ImmutableHierarchicalConfiguration immutableConfigurationAt(final String key, final boolean supportUpdates) {
321        return ConfigurationUtils.unmodifiableConfiguration(configurationAt(key, supportUpdates));
322    }
323
324    /**
325     * {@inheritDoc} This is a short form for {@code configurationAt(key,
326     * <b>false</b>)}.
327     *
328     * @throws ConfigurationRuntimeException if the key does not select a single node
329     */
330    @Override
331    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
332        return configurationAt(key, false);
333    }
334
335    /**
336     * {@inheritDoc} This implementation creates a {@code SubnodeConfiguration} by delegating to {@code configurationAt()}.
337     * Then an immutable wrapper is created and returned.
338     *
339     * @throws ConfigurationRuntimeException if the key does not select a single node
340     */
341    @Override
342    public ImmutableHierarchicalConfiguration immutableConfigurationAt(final String key) {
343        return ConfigurationUtils.unmodifiableConfiguration(configurationAt(key));
344    }
345
346    /**
347     * {@inheritDoc} This implementation creates sub configurations in the same way as described for
348     * {@link #configurationAt(String)}.
349     */
350    @Override
351    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
352        List<ImmutableNode> nodes;
353        beginRead(false);
354        try {
355            nodes = fetchFilteredNodeResults(key);
356        } finally {
357            endRead();
358        }
359        return nodes.stream().map(this::createIndependentSubConfigurationForNode).collect(Collectors.toList());
360    }
361
362    /**
363     * {@inheritDoc} This implementation creates tracked nodes for the specified key. Then sub configurations for these
364     * nodes are created and returned.
365     */
366    @Override
367    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key, final boolean supportUpdates) {
368        if (!supportUpdates) {
369            return configurationsAt(key);
370        }
371
372        InMemoryNodeModel parentModel;
373        beginRead(false);
374        try {
375            parentModel = getSubConfigurationParentModel();
376        } finally {
377            endRead();
378        }
379
380        final Collection<NodeSelector> selectors = parentModel.selectAndTrackNodes(key, this);
381        return createConnectedSubConfigurations(this, selectors);
382    }
383
384    /**
385     * {@inheritDoc} This implementation first delegates to {@code configurationsAt()} to create a list of
386     * {@code SubnodeConfiguration} objects. Then for each element of this list an unmodifiable wrapper is created.
387     */
388    @Override
389    public List<ImmutableHierarchicalConfiguration> immutableConfigurationsAt(final String key) {
390        return toImmutable(configurationsAt(key));
391    }
392
393    /**
394     * {@inheritDoc} This implementation resolves the node(s) selected by the given key. If not a single node is selected,
395     * an empty list is returned. Otherwise, sub configurations for each child of the node are created.
396     */
397    @Override
398    public List<HierarchicalConfiguration<ImmutableNode>> childConfigurationsAt(final String key) {
399        List<ImmutableNode> nodes;
400        beginRead(false);
401        try {
402            nodes = fetchFilteredNodeResults(key);
403        } finally {
404            endRead();
405        }
406
407        if (nodes.size() != 1) {
408            return Collections.emptyList();
409        }
410
411        return nodes.get(0).stream().map(this::createIndependentSubConfigurationForNode).collect(Collectors.toList());
412    }
413
414    /**
415     * {@inheritDoc} This method works like {@link #childConfigurationsAt(String)}; however, depending on the value of the
416     * {@code supportUpdates} flag, connected sub configurations may be created.
417     */
418    @Override
419    public List<HierarchicalConfiguration<ImmutableNode>> childConfigurationsAt(final String key, final boolean supportUpdates) {
420        if (!supportUpdates) {
421            return childConfigurationsAt(key);
422        }
423
424        final InMemoryNodeModel parentModel = getSubConfigurationParentModel();
425        return createConnectedSubConfigurations(this, parentModel.trackChildNodes(key, this));
426    }
427
428    /**
429     * {@inheritDoc} This implementation first delegates to {@code childConfigurationsAt()} to create a list of mutable
430     * child configurations. Then a list with immutable wrapper configurations is created.
431     */
432    @Override
433    public List<ImmutableHierarchicalConfiguration> immutableChildConfigurationsAt(final String key) {
434        return toImmutable(childConfigurationsAt(key));
435    }
436
437    /**
438     * This method is always called when a subnode configuration created from this configuration has been modified. This
439     * implementation transforms the received event into an event of type {@code SUBNODE_CHANGED} and notifies the
440     * registered listeners.
441     *
442     * @param event the event describing the change
443     * @since 1.5
444     */
445    protected void subnodeConfigurationChanged(final ConfigurationEvent event) {
446        fireEvent(ConfigurationEvent.SUBNODE_CHANGED, null, event, event.isBeforeUpdate());
447    }
448
449    /**
450     * Initializes properties of a sub configuration. A sub configuration inherits some settings from its parent, e.g. the
451     * expression engine or the synchronizer. The corresponding values are copied by this method.
452     *
453     * @param sub the sub configuration to be initialized
454     */
455    private void initSubConfiguration(final BaseHierarchicalConfiguration sub) {
456        sub.setSynchronizer(getSynchronizer());
457        sub.setExpressionEngine(getExpressionEngine());
458        sub.setListDelimiterHandler(getListDelimiterHandler());
459        sub.setThrowExceptionOnMissing(isThrowExceptionOnMissing());
460        sub.getInterpolator().setParentInterpolator(getInterpolator());
461    }
462
463    /**
464     * Creates a listener which reacts on all changes on this configuration or one of its {@code SubnodeConfiguration}
465     * instances. If such a change is detected, some updates have to be performed.
466     *
467     * @return the newly created change listener
468     */
469    private EventListener<ConfigurationEvent> createChangeListener() {
470        return this::subnodeConfigurationChanged;
471    }
472
473    /**
474     * Returns a configuration with the same content as this configuration, but with all variables replaced by their actual
475     * values. This implementation is specific for hierarchical configurations. It clones the current configuration and runs
476     * a specialized visitor on the clone, which performs interpolation on the single configuration nodes.
477     *
478     * @return a configuration with all variables interpolated
479     * @since 1.5
480     */
481    @Override
482    public Configuration interpolatedConfiguration() {
483        final InterpolatedVisitor visitor = new InterpolatedVisitor();
484        final NodeHandler<ImmutableNode> handler = getModel().getNodeHandler();
485        NodeTreeWalker.INSTANCE.walkDFS(handler.getRootNode(), visitor, handler);
486
487        final BaseHierarchicalConfiguration c = (BaseHierarchicalConfiguration) clone();
488        c.getNodeModel().setRootNode(visitor.getInterpolatedRoot());
489        return c;
490    }
491
492    /**
493     * {@inheritDoc} This implementation creates a new instance of {@link InMemoryNodeModel}, initialized with this
494     * configuration's root node. This has the effect that although the same nodes are used, the original and copied
495     * configurations are independent on each other.
496     */
497    @Override
498    protected NodeModel<ImmutableNode> cloneNodeModel() {
499        return new InMemoryNodeModel(getModel().getNodeHandler().getRootNode());
500    }
501
502    /**
503     * Creates a list with immutable configurations from the given input list.
504     *
505     * @param subs a list with mutable configurations
506     * @return a list with corresponding immutable configurations
507     */
508    private static List<ImmutableHierarchicalConfiguration> toImmutable(final List<? extends HierarchicalConfiguration<?>> subs) {
509        return subs.stream().map(ConfigurationUtils::unmodifiableConfiguration).collect(Collectors.toList());
510    }
511
512    /**
513     * Creates the {@code NodeModel} for this configuration based on a passed in source configuration. This implementation
514     * creates an {@link InMemoryNodeModel}. If the passed in source configuration is defined, its root node also becomes
515     * the root node of this configuration. Otherwise, a new, empty root node is used.
516     *
517     * @param c the configuration that is to be copied
518     * @return the {@code NodeModel} for the new configuration
519     */
520    private static NodeModel<ImmutableNode> createNodeModel(final HierarchicalConfiguration<ImmutableNode> c) {
521        final ImmutableNode root = c != null ? obtainRootNode(c) : null;
522        return new InMemoryNodeModel(root);
523    }
524
525    /**
526     * Obtains the root node from a configuration whose data is to be copied. It has to be ensured that the synchronizer is
527     * called correctly.
528     *
529     * @param c the configuration that is to be copied
530     * @return the root node of this configuration
531     */
532    private static ImmutableNode obtainRootNode(final HierarchicalConfiguration<ImmutableNode> c) {
533        return c.getNodeModel().getNodeHandler().getRootNode();
534    }
535
536    /**
537     * A specialized visitor base class that can be used for storing the tree of configuration nodes. The basic idea is that
538     * each node can be associated with a reference object. This reference object has a concrete meaning in a derived class,
539     * e.g. an entry in a JNDI context or an XML element. When the configuration tree is set up, the {@code load()} method
540     * is responsible for setting the reference objects. When the configuration tree is later modified, new nodes do not
541     * have a defined reference object. This visitor class processes all nodes and finds the ones without a defined
542     * reference object. For those nodes the {@code insert()} method is called, which must be defined in concrete sub
543     * classes. This method can perform all steps to integrate the new node into the original structure.
544     */
545    protected abstract static class BuilderVisitor extends ConfigurationNodeVisitorAdapter<ImmutableNode> {
546        @Override
547        public void visitBeforeChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
548            final ReferenceNodeHandler refHandler = (ReferenceNodeHandler) handler;
549            updateNode(node, refHandler);
550            insertNewChildNodes(node, refHandler);
551        }
552
553        /**
554         * Inserts a new node into the structure constructed by this builder. This method is called for each node that has been
555         * added to the configuration tree after the configuration has been loaded from its source. These new nodes have to be
556         * inserted into the original structure. The passed in nodes define the position of the node to be inserted: its parent
557         * and the siblings between to insert.
558         *
559         * @param newNode the node to be inserted
560         * @param parent the parent node
561         * @param sibling1 the sibling after which the node is to be inserted; can be <b>null</b> if the new node is going to be
562         *        the first child node
563         * @param sibling2 the sibling before which the node is to be inserted; can be <b>null</b> if the new node is going to
564         *        be the last child node
565         * @param refHandler the {@code ReferenceNodeHandler}
566         */
567        protected abstract void insert(ImmutableNode newNode, ImmutableNode parent, ImmutableNode sibling1, ImmutableNode sibling2,
568            ReferenceNodeHandler refHandler);
569
570        /**
571         * Updates a node that already existed in the original hierarchy. This method is called for each node that has an
572         * assigned reference object. A concrete implementation should update the reference according to the node's current
573         * value.
574         *
575         * @param node the current node to be processed
576         * @param reference the reference object for this node
577         * @param refHandler the {@code ReferenceNodeHandler}
578         */
579        protected abstract void update(ImmutableNode node, Object reference, ReferenceNodeHandler refHandler);
580
581        /**
582         * Updates the value of a node. If this node is associated with a reference object, the {@code update()} method is
583         * called.
584         *
585         * @param node the current node to be processed
586         * @param refHandler the {@code ReferenceNodeHandler}
587         */
588        private void updateNode(final ImmutableNode node, final ReferenceNodeHandler refHandler) {
589            final Object reference = refHandler.getReference(node);
590            if (reference != null) {
591                update(node, reference, refHandler);
592            }
593        }
594
595        /**
596         * Inserts new children that have been added to the specified node.
597         *
598         * @param node the current node to be processed
599         * @param refHandler the {@code ReferenceNodeHandler}
600         */
601        private void insertNewChildNodes(final ImmutableNode node, final ReferenceNodeHandler refHandler) {
602            final Collection<ImmutableNode> subNodes = new LinkedList<>(refHandler.getChildren(node));
603            final Iterator<ImmutableNode> children = subNodes.iterator();
604            ImmutableNode sibling1;
605            ImmutableNode nd = null;
606
607            while (children.hasNext()) {
608                // find the next new node
609                do {
610                    sibling1 = nd;
611                    nd = children.next();
612                } while (refHandler.getReference(nd) != null && children.hasNext());
613
614                if (refHandler.getReference(nd) == null) {
615                    // find all following new nodes
616                    final List<ImmutableNode> newNodes = new LinkedList<>();
617                    newNodes.add(nd);
618                    while (children.hasNext()) {
619                        nd = children.next();
620                        if (refHandler.getReference(nd) != null) {
621                            break;
622                        }
623                        newNodes.add(nd);
624                    }
625
626                    // Insert all new nodes
627                    final ImmutableNode sibling2 = refHandler.getReference(nd) == null ? null : nd;
628                    for (final ImmutableNode insertNode : newNodes) {
629                        if (refHandler.getReference(insertNode) == null) {
630                            insert(insertNode, node, sibling1, sibling2, refHandler);
631                            sibling1 = insertNode;
632                        }
633                    }
634                }
635            }
636        }
637    }
638
639    /**
640     * A specialized visitor implementation which constructs the root node of a configuration with all variables replaced by
641     * their interpolated values.
642     */
643    private final class InterpolatedVisitor extends ConfigurationNodeVisitorAdapter<ImmutableNode> {
644        /** A stack for managing node builder instances. */
645        private final List<ImmutableNode.Builder> builderStack;
646
647        /** The resulting root node. */
648        private ImmutableNode interpolatedRoot;
649
650        /**
651         * Creates a new instance of {@code InterpolatedVisitor}.
652         */
653        public InterpolatedVisitor() {
654            builderStack = new LinkedList<>();
655        }
656
657        /**
658         * Gets the result of this builder: the root node of the interpolated nodes hierarchy.
659         *
660         * @return the resulting root node
661         */
662        public ImmutableNode getInterpolatedRoot() {
663            return interpolatedRoot;
664        }
665
666        @Override
667        public void visitBeforeChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
668            if (isLeafNode(node, handler)) {
669                handleLeafNode(node, handler);
670            } else {
671                final ImmutableNode.Builder builder = new ImmutableNode.Builder(handler.getChildrenCount(node, null)).name(handler.nodeName(node))
672                    .value(interpolate(handler.getValue(node))).addAttributes(interpolateAttributes(node, handler));
673                push(builder);
674            }
675        }
676
677        @Override
678        public void visitAfterChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
679            if (!isLeafNode(node, handler)) {
680                final ImmutableNode newNode = pop().create();
681                storeInterpolatedNode(newNode);
682            }
683        }
684
685        /**
686         * Pushes a new builder on the stack.
687         *
688         * @param builder the builder
689         */
690        private void push(final ImmutableNode.Builder builder) {
691            builderStack.add(0, builder);
692        }
693
694        /**
695         * Pops the top-level element from the stack.
696         *
697         * @return the element popped from the stack
698         */
699        private ImmutableNode.Builder pop() {
700            return builderStack.remove(0);
701        }
702
703        /**
704         * Returns the top-level element from the stack without removing it.
705         *
706         * @return the top-level element from the stack
707         */
708        private ImmutableNode.Builder peek() {
709            return builderStack.get(0);
710        }
711
712        /**
713         * Returns a flag whether the given node is a leaf. This is the case if it does not have children.
714         *
715         * @param node the node in question
716         * @param handler the {@code NodeHandler}
717         * @return a flag whether this is a leaf node
718         */
719        private boolean isLeafNode(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
720            return handler.getChildren(node).isEmpty();
721        }
722
723        /**
724         * Handles interpolation for a node with no children. If interpolation does not change this node, it is copied as is to
725         * the resulting structure. Otherwise, a new node is created with the interpolated values.
726         *
727         * @param node the current node to be processed
728         * @param handler the {@code NodeHandler}
729         */
730        private void handleLeafNode(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
731            final Object value = interpolate(node.getValue());
732            final Map<String, Object> interpolatedAttributes = new HashMap<>();
733            final boolean attributeChanged = interpolateAttributes(node, handler, interpolatedAttributes);
734            final ImmutableNode newNode = valueChanged(value, handler.getValue(node)) || attributeChanged
735                ? new ImmutableNode.Builder().name(handler.nodeName(node)).value(value).addAttributes(interpolatedAttributes).create()
736                : node;
737            storeInterpolatedNode(newNode);
738        }
739
740        /**
741         * Stores a processed node. Per default, the node is added to the current builder on the stack. If no such builder
742         * exists, this is the result node.
743         *
744         * @param node the node to be stored
745         */
746        private void storeInterpolatedNode(final ImmutableNode node) {
747            if (builderStack.isEmpty()) {
748                interpolatedRoot = node;
749            } else {
750                peek().addChild(node);
751            }
752        }
753
754        /**
755         * Populates a map with interpolated attributes of the passed in node.
756         *
757         * @param node the current node to be processed
758         * @param handler the {@code NodeHandler}
759         * @param interpolatedAttributes a map for storing the results
760         * @return a flag whether an attribute value was changed by interpolation
761         */
762        private boolean interpolateAttributes(final ImmutableNode node, final NodeHandler<ImmutableNode> handler,
763            final Map<String, Object> interpolatedAttributes) {
764            boolean attributeChanged = false;
765            for (final String attr : handler.getAttributes(node)) {
766                final Object attrValue = interpolate(handler.getAttributeValue(node, attr));
767                if (valueChanged(attrValue, handler.getAttributeValue(node, attr))) {
768                    attributeChanged = true;
769                }
770                interpolatedAttributes.put(attr, attrValue);
771            }
772            return attributeChanged;
773        }
774
775        /**
776         * Returns a map with interpolated attributes of the passed in node.
777         *
778         * @param node the current node to be processed
779         * @param handler the {@code NodeHandler}
780         * @return the map with interpolated attributes
781         */
782        private Map<String, Object> interpolateAttributes(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
783            final Map<String, Object> attributes = new HashMap<>();
784            interpolateAttributes(node, handler, attributes);
785            return attributes;
786        }
787
788        /**
789         * Tests whether a value is changed because of interpolation.
790         *
791         * @param interpolatedValue the interpolated value
792         * @param value the original value
793         * @return a flag whether the value was changed
794         */
795        private boolean valueChanged(final Object interpolatedValue, final Object value) {
796            return ObjectUtils.notEqual(interpolatedValue, value);
797        }
798    }
799}