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.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.Map;
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 *
052 */
053public class BaseHierarchicalConfiguration extends AbstractHierarchicalConfiguration<ImmutableNode> implements InMemoryNodeModelSupport {
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     * Returns 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     * Returns 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        final List<HierarchicalConfiguration<ImmutableNode>> configs = new ArrayList<>(selectors.size());
271        for (final NodeSelector selector : selectors) {
272            configs.add(createSubConfigurationForTrackedNode(selector, parentModelSupport));
273        }
274        return configs;
275    }
276
277    /**
278     * Creates a sub configuration from the specified key which is independent on this configuration. This means that the
279     * sub configuration operates on a separate node model (although the nodes are initially shared).
280     *
281     * @param key the key of the sub configuration
282     * @return the new sub configuration
283     */
284    private BaseHierarchicalConfiguration createIndependentSubConfiguration(final String key) {
285        final List<ImmutableNode> targetNodes = fetchFilteredNodeResults(key);
286        final int size = targetNodes.size();
287        if (size != 1) {
288            throw new ConfigurationRuntimeException("Passed in key must select exactly one node (found %,d): %s", size, key);
289        }
290        final BaseHierarchicalConfiguration sub = new BaseHierarchicalConfiguration(new InMemoryNodeModel(targetNodes.get(0)));
291        initSubConfiguration(sub);
292        return sub;
293    }
294
295    /**
296     * Returns an initialized sub configuration for this configuration that is based on another
297     * {@code BaseHierarchicalConfiguration}. Thus, it is independent from this configuration.
298     *
299     * @param node the root node for the sub configuration
300     * @return the initialized sub configuration
301     */
302    private BaseHierarchicalConfiguration createIndependentSubConfigurationForNode(final ImmutableNode node) {
303        final BaseHierarchicalConfiguration sub = new BaseHierarchicalConfiguration(new InMemoryNodeModel(node));
304        initSubConfiguration(sub);
305        return sub;
306    }
307
308    /**
309     * Executes a query on the specified key and filters it for node results.
310     *
311     * @param key the key
312     * @return the filtered list with result nodes
313     */
314    private List<ImmutableNode> fetchFilteredNodeResults(final String key) {
315        final NodeHandler<ImmutableNode> handler = getModel().getNodeHandler();
316        return resolveNodeKey(handler.getRootNode(), key, handler);
317    }
318
319    /**
320     * {@inheritDoc} This implementation creates a {@code SubnodeConfiguration} by delegating to {@code configurationAt()}.
321     * Then an immutable wrapper is created and returned.
322     */
323    @Override
324    public ImmutableHierarchicalConfiguration immutableConfigurationAt(final String key, final boolean supportUpdates) {
325        return ConfigurationUtils.unmodifiableConfiguration(configurationAt(key, supportUpdates));
326    }
327
328    /**
329     * {@inheritDoc} This is a short form for {@code configurationAt(key,
330     * <b>false</b>)}.
331     *
332     * @throws ConfigurationRuntimeException if the key does not select a single node
333     */
334    @Override
335    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
336        return configurationAt(key, false);
337    }
338
339    /**
340     * {@inheritDoc} This implementation creates a {@code SubnodeConfiguration} by delegating to {@code configurationAt()}.
341     * Then an immutable wrapper is created and returned.
342     *
343     * @throws ConfigurationRuntimeException if the key does not select a single node
344     */
345    @Override
346    public ImmutableHierarchicalConfiguration immutableConfigurationAt(final String key) {
347        return ConfigurationUtils.unmodifiableConfiguration(configurationAt(key));
348    }
349
350    /**
351     * {@inheritDoc} This implementation creates sub configurations in the same way as described for
352     * {@link #configurationAt(String)}.
353     */
354    @Override
355    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
356        List<ImmutableNode> nodes;
357        beginRead(false);
358        try {
359            nodes = fetchFilteredNodeResults(key);
360        } finally {
361            endRead();
362        }
363
364        final List<HierarchicalConfiguration<ImmutableNode>> results = new ArrayList<>(nodes.size());
365        for (final ImmutableNode node : nodes) {
366            final BaseHierarchicalConfiguration sub = createIndependentSubConfigurationForNode(node);
367            results.add(sub);
368        }
369
370        return results;
371    }
372
373    /**
374     * {@inheritDoc} This implementation creates tracked nodes for the specified key. Then sub configurations for these
375     * nodes are created and returned.
376     */
377    @Override
378    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key, final boolean supportUpdates) {
379        if (!supportUpdates) {
380            return configurationsAt(key);
381        }
382
383        InMemoryNodeModel parentModel;
384        beginRead(false);
385        try {
386            parentModel = getSubConfigurationParentModel();
387        } finally {
388            endRead();
389        }
390
391        final Collection<NodeSelector> selectors = parentModel.selectAndTrackNodes(key, this);
392        return createConnectedSubConfigurations(this, selectors);
393    }
394
395    /**
396     * {@inheritDoc} This implementation first delegates to {@code configurationsAt()} to create a list of
397     * {@code SubnodeConfiguration} objects. Then for each element of this list an unmodifiable wrapper is created.
398     */
399    @Override
400    public List<ImmutableHierarchicalConfiguration> immutableConfigurationsAt(final String key) {
401        return toImmutable(configurationsAt(key));
402    }
403
404    /**
405     * {@inheritDoc} This implementation resolves the node(s) selected by the given key. If not a single node is selected,
406     * an empty list is returned. Otherwise, sub configurations for each child of the node are created.
407     */
408    @Override
409    public List<HierarchicalConfiguration<ImmutableNode>> childConfigurationsAt(final String key) {
410        List<ImmutableNode> nodes;
411        beginRead(false);
412        try {
413            nodes = fetchFilteredNodeResults(key);
414        } finally {
415            endRead();
416        }
417
418        if (nodes.size() != 1) {
419            return Collections.emptyList();
420        }
421
422        final ImmutableNode parent = nodes.get(0);
423        final List<HierarchicalConfiguration<ImmutableNode>> subs = new ArrayList<>(parent.getChildren().size());
424        for (final ImmutableNode node : parent) {
425            subs.add(createIndependentSubConfigurationForNode(node));
426        }
427
428        return subs;
429    }
430
431    /**
432     * {@inheritDoc} This method works like {@link #childConfigurationsAt(String)}; however, depending on the value of the
433     * {@code supportUpdates} flag, connected sub configurations may be created.
434     */
435    @Override
436    public List<HierarchicalConfiguration<ImmutableNode>> childConfigurationsAt(final String key, final boolean supportUpdates) {
437        if (!supportUpdates) {
438            return childConfigurationsAt(key);
439        }
440
441        final InMemoryNodeModel parentModel = getSubConfigurationParentModel();
442        return createConnectedSubConfigurations(this, parentModel.trackChildNodes(key, this));
443    }
444
445    /**
446     * {@inheritDoc} This implementation first delegates to {@code childConfigurationsAt()} to create a list of mutable
447     * child configurations. Then a list with immutable wrapper configurations is created.
448     */
449    @Override
450    public List<ImmutableHierarchicalConfiguration> immutableChildConfigurationsAt(final String key) {
451        return toImmutable(childConfigurationsAt(key));
452    }
453
454    /**
455     * This method is always called when a subnode configuration created from this configuration has been modified. This
456     * implementation transforms the received event into an event of type {@code SUBNODE_CHANGED} and notifies the
457     * registered listeners.
458     *
459     * @param event the event describing the change
460     * @since 1.5
461     */
462    protected void subnodeConfigurationChanged(final ConfigurationEvent event) {
463        fireEvent(ConfigurationEvent.SUBNODE_CHANGED, null, event, event.isBeforeUpdate());
464    }
465
466    /**
467     * Initializes properties of a sub configuration. A sub configuration inherits some settings from its parent, e.g. the
468     * expression engine or the synchronizer. The corresponding values are copied by this method.
469     *
470     * @param sub the sub configuration to be initialized
471     */
472    private void initSubConfiguration(final BaseHierarchicalConfiguration sub) {
473        sub.setSynchronizer(getSynchronizer());
474        sub.setExpressionEngine(getExpressionEngine());
475        sub.setListDelimiterHandler(getListDelimiterHandler());
476        sub.setThrowExceptionOnMissing(isThrowExceptionOnMissing());
477        sub.getInterpolator().setParentInterpolator(getInterpolator());
478    }
479
480    /**
481     * Creates a listener which reacts on all changes on this configuration or one of its {@code SubnodeConfiguration}
482     * instances. If such a change is detected, some updates have to be performed.
483     *
484     * @return the newly created change listener
485     */
486    private EventListener<ConfigurationEvent> createChangeListener() {
487        return this::subnodeConfigurationChanged;
488    }
489
490    /**
491     * Returns a configuration with the same content as this configuration, but with all variables replaced by their actual
492     * values. This implementation is specific for hierarchical configurations. It clones the current configuration and runs
493     * a specialized visitor on the clone, which performs interpolation on the single configuration nodes.
494     *
495     * @return a configuration with all variables interpolated
496     * @since 1.5
497     */
498    @Override
499    public Configuration interpolatedConfiguration() {
500        final InterpolatedVisitor visitor = new InterpolatedVisitor();
501        final NodeHandler<ImmutableNode> handler = getModel().getNodeHandler();
502        NodeTreeWalker.INSTANCE.walkDFS(handler.getRootNode(), visitor, handler);
503
504        final BaseHierarchicalConfiguration c = (BaseHierarchicalConfiguration) clone();
505        c.getNodeModel().setRootNode(visitor.getInterpolatedRoot());
506        return c;
507    }
508
509    /**
510     * {@inheritDoc} This implementation creates a new instance of {@link InMemoryNodeModel}, initialized with this
511     * configuration's root node. This has the effect that although the same nodes are used, the original and copied
512     * configurations are independent on each other.
513     */
514    @Override
515    protected NodeModel<ImmutableNode> cloneNodeModel() {
516        return new InMemoryNodeModel(getModel().getNodeHandler().getRootNode());
517    }
518
519    /**
520     * Creates a list with immutable configurations from the given input list.
521     *
522     * @param subs a list with mutable configurations
523     * @return a list with corresponding immutable configurations
524     */
525    private static List<ImmutableHierarchicalConfiguration> toImmutable(final List<? extends HierarchicalConfiguration<?>> subs) {
526        final List<ImmutableHierarchicalConfiguration> res = new ArrayList<>(subs.size());
527        for (final HierarchicalConfiguration<?> sub : subs) {
528            res.add(ConfigurationUtils.unmodifiableConfiguration(sub));
529        }
530        return res;
531    }
532
533    /**
534     * Creates the {@code NodeModel} for this configuration based on a passed in source configuration. This implementation
535     * creates an {@link InMemoryNodeModel}. If the passed in source configuration is defined, its root node also becomes
536     * the root node of this configuration. Otherwise, a new, empty root node is used.
537     *
538     * @param c the configuration that is to be copied
539     * @return the {@code NodeModel} for the new configuration
540     */
541    private static NodeModel<ImmutableNode> createNodeModel(final HierarchicalConfiguration<ImmutableNode> c) {
542        final ImmutableNode root = c != null ? obtainRootNode(c) : null;
543        return new InMemoryNodeModel(root);
544    }
545
546    /**
547     * Obtains the root node from a configuration whose data is to be copied. It has to be ensured that the synchronizer is
548     * called correctly.
549     *
550     * @param c the configuration that is to be copied
551     * @return the root node of this configuration
552     */
553    private static ImmutableNode obtainRootNode(final HierarchicalConfiguration<ImmutableNode> c) {
554        return c.getNodeModel().getNodeHandler().getRootNode();
555    }
556
557    /**
558     * A specialized visitor base class that can be used for storing the tree of configuration nodes. The basic idea is that
559     * each node can be associated with a reference object. This reference object has a concrete meaning in a derived class,
560     * e.g. an entry in a JNDI context or an XML element. When the configuration tree is set up, the {@code load()} method
561     * is responsible for setting the reference objects. When the configuration tree is later modified, new nodes do not
562     * have a defined reference object. This visitor class processes all nodes and finds the ones without a defined
563     * reference object. For those nodes the {@code insert()} method is called, which must be defined in concrete sub
564     * classes. This method can perform all steps to integrate the new node into the original structure.
565     */
566    protected abstract static class BuilderVisitor extends ConfigurationNodeVisitorAdapter<ImmutableNode> {
567        @Override
568        public void visitBeforeChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
569            final ReferenceNodeHandler refHandler = (ReferenceNodeHandler) handler;
570            updateNode(node, refHandler);
571            insertNewChildNodes(node, refHandler);
572        }
573
574        /**
575         * Inserts a new node into the structure constructed by this builder. This method is called for each node that has been
576         * added to the configuration tree after the configuration has been loaded from its source. These new nodes have to be
577         * inserted into the original structure. The passed in nodes define the position of the node to be inserted: its parent
578         * and the siblings between to insert.
579         *
580         * @param newNode the node to be inserted
581         * @param parent the parent node
582         * @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
583         *        the first child node
584         * @param sibling2 the sibling before which the node is to be inserted; can be <b>null</b> if the new node is going to
585         *        be the last child node
586         * @param refHandler the {@code ReferenceNodeHandler}
587         */
588        protected abstract void insert(ImmutableNode newNode, ImmutableNode parent, ImmutableNode sibling1, ImmutableNode sibling2,
589            ReferenceNodeHandler refHandler);
590
591        /**
592         * Updates a node that already existed in the original hierarchy. This method is called for each node that has an
593         * assigned reference object. A concrete implementation should update the reference according to the node's current
594         * value.
595         *
596         * @param node the current node to be processed
597         * @param reference the reference object for this node
598         * @param refHandler the {@code ReferenceNodeHandler}
599         */
600        protected abstract void update(ImmutableNode node, Object reference, ReferenceNodeHandler refHandler);
601
602        /**
603         * Updates the value of a node. If this node is associated with a reference object, the {@code update()} method is
604         * called.
605         *
606         * @param node the current node to be processed
607         * @param refHandler the {@code ReferenceNodeHandler}
608         */
609        private void updateNode(final ImmutableNode node, final ReferenceNodeHandler refHandler) {
610            final Object reference = refHandler.getReference(node);
611            if (reference != null) {
612                update(node, reference, refHandler);
613            }
614        }
615
616        /**
617         * Inserts new children that have been added to the specified node.
618         *
619         * @param node the current node to be processed
620         * @param refHandler the {@code ReferenceNodeHandler}
621         */
622        private void insertNewChildNodes(final ImmutableNode node, final ReferenceNodeHandler refHandler) {
623            final Collection<ImmutableNode> subNodes = new LinkedList<>(refHandler.getChildren(node));
624            final Iterator<ImmutableNode> children = subNodes.iterator();
625            ImmutableNode sibling1;
626            ImmutableNode nd = null;
627
628            while (children.hasNext()) {
629                // find the next new node
630                do {
631                    sibling1 = nd;
632                    nd = children.next();
633                } while (refHandler.getReference(nd) != null && children.hasNext());
634
635                if (refHandler.getReference(nd) == null) {
636                    // find all following new nodes
637                    final List<ImmutableNode> newNodes = new LinkedList<>();
638                    newNodes.add(nd);
639                    while (children.hasNext()) {
640                        nd = children.next();
641                        if (refHandler.getReference(nd) != null) {
642                            break;
643                        }
644                        newNodes.add(nd);
645                    }
646
647                    // Insert all new nodes
648                    final ImmutableNode sibling2 = refHandler.getReference(nd) == null ? null : nd;
649                    for (final ImmutableNode insertNode : newNodes) {
650                        if (refHandler.getReference(insertNode) == null) {
651                            insert(insertNode, node, sibling1, sibling2, refHandler);
652                            sibling1 = insertNode;
653                        }
654                    }
655                }
656            }
657        }
658    }
659
660    /**
661     * A specialized visitor implementation which constructs the root node of a configuration with all variables replaced by
662     * their interpolated values.
663     */
664    private class InterpolatedVisitor extends ConfigurationNodeVisitorAdapter<ImmutableNode> {
665        /** A stack for managing node builder instances. */
666        private final List<ImmutableNode.Builder> builderStack;
667
668        /** The resulting root node. */
669        private ImmutableNode interpolatedRoot;
670
671        /**
672         * Creates a new instance of {@code InterpolatedVisitor}.
673         */
674        public InterpolatedVisitor() {
675            builderStack = new LinkedList<>();
676        }
677
678        /**
679         * Returns the result of this builder: the root node of the interpolated nodes hierarchy.
680         *
681         * @return the resulting root node
682         */
683        public ImmutableNode getInterpolatedRoot() {
684            return interpolatedRoot;
685        }
686
687        @Override
688        public void visitBeforeChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
689            if (isLeafNode(node, handler)) {
690                handleLeafNode(node, handler);
691            } else {
692                final ImmutableNode.Builder builder = new ImmutableNode.Builder(handler.getChildrenCount(node, null)).name(handler.nodeName(node))
693                    .value(interpolate(handler.getValue(node))).addAttributes(interpolateAttributes(node, handler));
694                push(builder);
695            }
696        }
697
698        @Override
699        public void visitAfterChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
700            if (!isLeafNode(node, handler)) {
701                final ImmutableNode newNode = pop().create();
702                storeInterpolatedNode(newNode);
703            }
704        }
705
706        /**
707         * Pushes a new builder on the stack.
708         *
709         * @param builder the builder
710         */
711        private void push(final ImmutableNode.Builder builder) {
712            builderStack.add(0, builder);
713        }
714
715        /**
716         * Pops the top-level element from the stack.
717         *
718         * @return the element popped from the stack
719         */
720        private ImmutableNode.Builder pop() {
721            return builderStack.remove(0);
722        }
723
724        /**
725         * Returns the top-level element from the stack without removing it.
726         *
727         * @return the top-level element from the stack
728         */
729        private ImmutableNode.Builder peek() {
730            return builderStack.get(0);
731        }
732
733        /**
734         * Returns a flag whether the given node is a leaf. This is the case if it does not have children.
735         *
736         * @param node the node in question
737         * @param handler the {@code NodeHandler}
738         * @return a flag whether this is a leaf node
739         */
740        private boolean isLeafNode(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
741            return handler.getChildren(node).isEmpty();
742        }
743
744        /**
745         * Handles interpolation for a node with no children. If interpolation does not change this node, it is copied as is to
746         * the resulting structure. Otherwise, a new node is created with the interpolated values.
747         *
748         * @param node the current node to be processed
749         * @param handler the {@code NodeHandler}
750         */
751        private void handleLeafNode(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
752            final Object value = interpolate(node.getValue());
753            final Map<String, Object> interpolatedAttributes = new HashMap<>();
754            final boolean attributeChanged = interpolateAttributes(node, handler, interpolatedAttributes);
755            final ImmutableNode newNode = valueChanged(value, handler.getValue(node)) || attributeChanged
756                ? new ImmutableNode.Builder().name(handler.nodeName(node)).value(value).addAttributes(interpolatedAttributes).create()
757                : node;
758            storeInterpolatedNode(newNode);
759        }
760
761        /**
762         * Stores a processed node. Per default, the node is added to the current builder on the stack. If no such builder
763         * exists, this is the result node.
764         *
765         * @param node the node to be stored
766         */
767        private void storeInterpolatedNode(final ImmutableNode node) {
768            if (builderStack.isEmpty()) {
769                interpolatedRoot = node;
770            } else {
771                peek().addChild(node);
772            }
773        }
774
775        /**
776         * Populates 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         * @param interpolatedAttributes a map for storing the results
781         * @return a flag whether an attribute value was changed by interpolation
782         */
783        private boolean interpolateAttributes(final ImmutableNode node, final NodeHandler<ImmutableNode> handler,
784            final Map<String, Object> interpolatedAttributes) {
785            boolean attributeChanged = false;
786            for (final String attr : handler.getAttributes(node)) {
787                final Object attrValue = interpolate(handler.getAttributeValue(node, attr));
788                if (valueChanged(attrValue, handler.getAttributeValue(node, attr))) {
789                    attributeChanged = true;
790                }
791                interpolatedAttributes.put(attr, attrValue);
792            }
793            return attributeChanged;
794        }
795
796        /**
797         * Returns a map with interpolated attributes of the passed in node.
798         *
799         * @param node the current node to be processed
800         * @param handler the {@code NodeHandler}
801         * @return the map with interpolated attributes
802         */
803        private Map<String, Object> interpolateAttributes(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
804            final Map<String, Object> attributes = new HashMap<>();
805            interpolateAttributes(node, handler, attributes);
806            return attributes;
807        }
808
809        /**
810         * Tests whether a value is changed because of interpolation.
811         *
812         * @param interpolatedValue the interpolated value
813         * @param value the original value
814         * @return a flag whether the value was changed
815         */
816        private boolean valueChanged(final Object interpolatedValue, final Object value) {
817            return ObjectUtils.notEqual(interpolatedValue, value);
818        }
819    }
820}