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 */
017package org.apache.commons.configuration2;
018
019import java.io.ByteArrayOutputStream;
020import java.io.PrintStream;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030import org.apache.commons.configuration2.event.ConfigurationEvent;
031import org.apache.commons.configuration2.event.EventListener;
032import org.apache.commons.configuration2.event.EventSource;
033import org.apache.commons.configuration2.event.EventType;
034import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
035import org.apache.commons.configuration2.sync.LockMode;
036import org.apache.commons.configuration2.tree.DefaultConfigurationKey;
037import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
038import org.apache.commons.configuration2.tree.ExpressionEngine;
039import org.apache.commons.configuration2.tree.ImmutableNode;
040import org.apache.commons.configuration2.tree.NodeCombiner;
041import org.apache.commons.configuration2.tree.NodeTreeWalker;
042import org.apache.commons.configuration2.tree.QueryResult;
043import org.apache.commons.configuration2.tree.TreeUtils;
044import org.apache.commons.configuration2.tree.UnionCombiner;
045
046/**
047 * <p>
048 * A hierarchical composite configuration class.
049 * </p>
050 * <p>
051 * This class maintains a list of configuration objects, which can be added
052 * using the diverse {@code addConfiguration()} methods. After that the
053 * configurations can be accessed either by name (if one was provided when the
054 * configuration was added) or by index. For the whole set of managed
055 * configurations a logical node structure is constructed. For this purpose a
056 * {@link org.apache.commons.configuration2.tree.NodeCombiner NodeCombiner}
057 * object can be set. This makes it possible to specify different algorithms for
058 * the combination process.
059 * </p>
060 * <p>
061 * The big advantage of this class is that it creates a truly hierarchical
062 * structure of all the properties stored in the contained configurations - even
063 * if some of them are no hierarchical configurations per se. So all enhanced
064 * features provided by a hierarchical configuration (e.g. choosing an
065 * expression engine) are applicable.
066 * </p>
067 * <p>
068 * The class works by registering itself as an event listener at all added
069 * configurations. So it gets notified whenever one of these configurations is
070 * changed and can invalidate its internal node structure. The next time a
071 * property is accessed the node structure will be re-constructed using the
072 * current state of the managed configurations. Note that, depending on the used
073 * {@code NodeCombiner}, this may be a complex operation.
074 * </p>
075 * <p>
076 * Because of the way a {@code CombinedConfiguration} is working it has more or
077 * less view character: it provides a logic view on the configurations it
078 * contains. In this constellation not all methods defined for hierarchical
079 * configurations - especially methods that update the stored properties - can
080 * be implemented in a consistent manner. Using such methods (like
081 * {@code addProperty()}, or {@code clearProperty()} on a
082 * {@code CombinedConfiguration} is not strictly forbidden, however, depending
083 * on the current {@link NodeCombiner} and the involved properties, the results
084 * may be different than expected. Some examples may illustrate this:
085 * </p>
086 * <ul>
087 * <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing two child
088 * configurations with the following content:
089 * <dl>
090 * <dt>user.properties</dt>
091 * <dd>
092 *
093 * <pre>
094 * gui.background = blue
095 * gui.position = (10, 10, 400, 200)
096 * </pre>
097 *
098 * </dd>
099 * <dt>default.properties</dt>
100 * <dd>
101 *
102 * <pre>
103 * gui.background = black
104 * gui.foreground = white
105 * home.dir = /data
106 * </pre>
107 *
108 * </dd>
109 * </dl>
110 * As a {@code NodeCombiner} a
111 * {@link org.apache.commons.configuration2.tree.OverrideCombiner
112 * OverrideCombiner} is used. This combiner will ensure that defined user
113 * settings take precedence over the default values. If the resulting
114 * {@code CombinedConfiguration} is queried for the background color,
115 * {@code blue} will be returned because this value is defined in
116 * {@code user.properties}. Now consider what happens if the key
117 * {@code gui.background} is removed from the {@code CombinedConfiguration}:
118 *
119 * <pre>
120 * cc.clearProperty(&quot;gui.background&quot;);
121 * </pre>
122 *
123 * Will a {@code cc.containsKey("gui.background")} now return <b>false</b>? No,
124 * it won't! The {@code clearProperty()} operation is executed on the node set
125 * of the combined configuration, which was constructed from the nodes of the
126 * two child configurations. It causes the value of the <em>background</em> node
127 * to be cleared, which is also part of the first child configuration. This
128 * modification of one of its child configurations causes the
129 * {@code CombinedConfiguration} to be re-constructed. This time the
130 * {@code OverrideCombiner} cannot find a {@code gui.background} property in the
131 * first child configuration, but it finds one in the second, and adds it to the
132 * resulting combined configuration. So the property is still present (with a
133 * different value now).</li>
134 * <li>{@code addProperty()} can also be problematic: Most node combiners use
135 * special view nodes for linking parts of the original configurations' data
136 * together. If new properties are added to such a special node, they do not
137 * belong to any of the managed configurations and thus hang in the air. Using
138 * the same configurations as in the last example, the statement
139 *
140 * <pre>
141 * addProperty(&quot;database.user&quot;, &quot;scott&quot;);
142 * </pre>
143 *
144 * would cause such a hanging property. If now one of the child configurations
145 * is changed and the {@code CombinedConfiguration} is re-constructed, this
146 * property will disappear! (Add operations are not problematic if they result
147 * in a child configuration being updated. For instance an
148 * {@code addProperty("home.url", "localhost");} will alter the second child
149 * configuration - because the prefix <em>home</em> is here already present;
150 * when the {@code CombinedConfiguration} is re-constructed, this change is
151 * taken into account.)</li>
152 * </ul>
153 * <p>
154 * Because of such problems it is recommended to perform updates only on the
155 * managed child configurations.
156 * </p>
157 * <p>
158 * Whenever the node structure of a {@code CombinedConfiguration} becomes
159 * invalid (either because one of the contained configurations was modified or
160 * because the {@code invalidate()} method was directly called) an event is
161 * generated. So this can be detected by interested event listeners. This also
162 * makes it possible to add a combined configuration into another one.
163 * </p>
164 * <p>
165 * Notes about thread-safety: This configuration implementation uses a
166 * {@code Synchronizer} object to protect instances against concurrent access.
167 * The concrete {@code Synchronizer} implementation used determines whether an
168 * instance of this class is thread-safe or not. In contrast to other
169 * implementations derived from {@link BaseHierarchicalConfiguration},
170 * thread-safety is an issue here because the nodes structure used by this
171 * configuration has to be constructed dynamically when a child configuration is
172 * changed. Therefore, when multiple threads are involved which also manipulate
173 * one of the child configurations, a proper {@code Synchronizer} object should
174 * be set. Note that the {@code Synchronizer} objects used by the child
175 * configurations do not really matter. Because immutable in-memory nodes
176 * structures are used for them there is no danger that updates on child
177 * configurations could interfere with read operations on the combined
178 * configuration.
179 * </p>
180 *
181 * @since 1.3
182 */
183public class CombinedConfiguration extends BaseHierarchicalConfiguration implements
184        EventListener<ConfigurationEvent>
185{
186    /**
187     * Constant for the event type fired when the internal node structure of a
188     * combined configuration becomes invalid.
189     *
190     * @since 2.0
191     */
192    public static final EventType<ConfigurationEvent> COMBINED_INVALIDATE =
193            new EventType<>(ConfigurationEvent.ANY,
194                    "COMBINED_INVALIDATE");
195
196    /** Constant for the expression engine for parsing the at path. */
197    private static final DefaultExpressionEngine AT_ENGINE = DefaultExpressionEngine.INSTANCE;
198
199    /** Constant for the default node combiner. */
200    private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
201
202    /** Constant for a root node for an empty configuration. */
203    private static final ImmutableNode EMPTY_ROOT = new ImmutableNode.Builder()
204            .create();
205
206    /** Stores the combiner. */
207    private NodeCombiner nodeCombiner;
208
209    /** Stores a list with the contained configurations. */
210    private List<ConfigData> configurations;
211
212    /** Stores a map with the named configurations. */
213    private Map<String, Configuration> namedConfigurations;
214
215    /**
216     * An expression engine used for converting child configurations to
217     * hierarchical ones.
218     */
219    private ExpressionEngine conversionExpressionEngine;
220
221    /** A flag whether this configuration is up-to-date. */
222    private boolean upToDate;
223
224    /**
225     * Creates a new instance of {@code CombinedConfiguration} and
226     * initializes the combiner to be used.
227     *
228     * @param comb the node combiner (can be <b>null</b>, then a union combiner
229     * is used as default)
230     */
231    public CombinedConfiguration(final NodeCombiner comb)
232    {
233        nodeCombiner = comb != null ? comb : DEFAULT_COMBINER;
234        initChildCollections();
235    }
236
237    /**
238     * Creates a new instance of {@code CombinedConfiguration} that uses
239     * a union combiner.
240     *
241     * @see org.apache.commons.configuration2.tree.UnionCombiner
242     */
243    public CombinedConfiguration()
244    {
245        this(null);
246    }
247
248    /**
249     * Returns the node combiner that is used for creating the combined node
250     * structure.
251     *
252     * @return the node combiner
253     */
254    public NodeCombiner getNodeCombiner()
255    {
256        beginRead(true);
257        try
258        {
259            return nodeCombiner;
260        }
261        finally
262        {
263            endRead();
264        }
265    }
266
267    /**
268     * Sets the node combiner. This object will be used when the combined node
269     * structure is to be constructed. It must not be <b>null</b>, otherwise an
270     * {@code IllegalArgumentException} exception is thrown. Changing the
271     * node combiner causes an invalidation of this combined configuration, so
272     * that the new combiner immediately takes effect.
273     *
274     * @param nodeCombiner the node combiner
275     */
276    public void setNodeCombiner(final NodeCombiner nodeCombiner)
277    {
278        if (nodeCombiner == null)
279        {
280            throw new IllegalArgumentException(
281                    "Node combiner must not be null!");
282        }
283
284        beginWrite(true);
285        try
286        {
287            this.nodeCombiner = nodeCombiner;
288            invalidateInternal();
289        }
290        finally
291        {
292            endWrite();
293        }
294    }
295
296    /**
297     * Returns the {@code ExpressionEngine} for converting flat child
298     * configurations to hierarchical ones.
299     *
300     * @return the conversion expression engine
301     * @since 1.6
302     */
303    public ExpressionEngine getConversionExpressionEngine()
304    {
305        beginRead(true);
306        try
307        {
308            return conversionExpressionEngine;
309        }
310        finally
311        {
312            endRead();
313        }
314    }
315
316    /**
317     * Sets the {@code ExpressionEngine} for converting flat child
318     * configurations to hierarchical ones. When constructing the root node for
319     * this combined configuration the properties of all child configurations
320     * must be combined to a single hierarchical node structure. In this
321     * process, non hierarchical configurations are converted to hierarchical
322     * ones first. This can be problematic if a child configuration contains
323     * keys that are no compatible with the default expression engine used by
324     * hierarchical configurations. Therefore it is possible to specify a
325     * specific expression engine to be used for this purpose.
326     *
327     * @param conversionExpressionEngine the conversion expression engine
328     * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine)
329     * @since 1.6
330     */
331    public void setConversionExpressionEngine(
332            final ExpressionEngine conversionExpressionEngine)
333    {
334        beginWrite(true);
335        try
336        {
337            this.conversionExpressionEngine = conversionExpressionEngine;
338        }
339        finally
340        {
341            endWrite();
342        }
343    }
344
345    /**
346     * Adds a new configuration to this combined configuration. It is possible
347     * (but not mandatory) to give the new configuration a name. This name must
348     * be unique, otherwise a {@code ConfigurationRuntimeException} will
349     * be thrown. With the optional {@code at} argument you can specify
350     * where in the resulting node structure the content of the added
351     * configuration should appear. This is a string that uses dots as property
352     * delimiters (independent on the current expression engine). For instance
353     * if you pass in the string {@code "database.tables"},
354     * all properties of the added configuration will occur in this branch.
355     *
356     * @param config the configuration to add (must not be <b>null</b>)
357     * @param name the name of this configuration (can be <b>null</b>)
358     * @param at the position of this configuration in the combined tree (can be
359     * <b>null</b>)
360     */
361    public void addConfiguration(final Configuration config, final String name,
362            final String at)
363    {
364        if (config == null)
365        {
366            throw new IllegalArgumentException(
367                    "Added configuration must not be null!");
368        }
369
370        beginWrite(true);
371        try
372        {
373            if (name != null && namedConfigurations.containsKey(name))
374            {
375                throw new ConfigurationRuntimeException(
376                        "A configuration with the name '"
377                                + name
378                                + "' already exists in this combined configuration!");
379            }
380
381            final ConfigData cd = new ConfigData(config, name, at);
382            if (getLogger().isDebugEnabled())
383            {
384                getLogger()
385                        .debug("Adding configuration " + config + " with name "
386                                + name);
387            }
388            configurations.add(cd);
389            if (name != null)
390            {
391                namedConfigurations.put(name, config);
392            }
393
394            invalidateInternal();
395        }
396        finally
397        {
398            endWrite();
399        }
400        registerListenerAt(config);
401    }
402
403    /**
404     * Adds a new configuration to this combined configuration with an optional
405     * name. The new configuration's properties will be added under the root of
406     * the combined node structure.
407     *
408     * @param config the configuration to add (must not be <b>null</b>)
409     * @param name the name of this configuration (can be <b>null</b>)
410     */
411    public void addConfiguration(final Configuration config, final String name)
412    {
413        addConfiguration(config, name, null);
414    }
415
416    /**
417     * Adds a new configuration to this combined configuration. The new
418     * configuration is not given a name. Its properties will be added under the
419     * root of the combined node structure.
420     *
421     * @param config the configuration to add (must not be <b>null</b>)
422     */
423    public void addConfiguration(final Configuration config)
424    {
425        addConfiguration(config, null, null);
426    }
427
428    /**
429     * Returns the number of configurations that are contained in this combined
430     * configuration.
431     *
432     * @return the number of contained configurations
433     */
434    public int getNumberOfConfigurations()
435    {
436        beginRead(true);
437        try
438        {
439            return getNumberOfConfigurationsInternal();
440        }
441        finally
442        {
443            endRead();
444        }
445    }
446
447    /**
448     * Returns the configuration at the specified index. The contained
449     * configurations are numbered in the order they were added to this combined
450     * configuration. The index of the first configuration is 0.
451     *
452     * @param index the index
453     * @return the configuration at this index
454     */
455    public Configuration getConfiguration(final int index)
456    {
457        beginRead(true);
458        try
459        {
460            final ConfigData cd = configurations.get(index);
461            return cd.getConfiguration();
462        }
463        finally
464        {
465            endRead();
466        }
467    }
468
469    /**
470     * Returns the configuration with the given name. This can be <b>null</b>
471     * if no such configuration exists.
472     *
473     * @param name the name of the configuration
474     * @return the configuration with this name
475     */
476    public Configuration getConfiguration(final String name)
477    {
478        beginRead(true);
479        try
480        {
481            return namedConfigurations.get(name);
482        }
483        finally
484        {
485            endRead();
486        }
487    }
488
489    /**
490     * Returns a List of all the configurations that have been added.
491     * @return A List of all the configurations.
492     * @since 1.7
493     */
494    public List<Configuration> getConfigurations()
495    {
496        beginRead(true);
497        try
498        {
499            final List<Configuration> list =
500                    new ArrayList<>(getNumberOfConfigurationsInternal());
501            for (final ConfigData cd : configurations)
502            {
503                list.add(cd.getConfiguration());
504            }
505            return list;
506        }
507        finally
508        {
509            endRead();
510        }
511    }
512
513    /**
514     * Returns a List of the names of all the configurations that have been
515     * added in the order they were added. A NULL value will be present in
516     * the list for each configuration that was added without a name.
517     * @return A List of all the configuration names.
518     * @since 1.7
519     */
520    public List<String> getConfigurationNameList()
521    {
522        beginRead(true);
523        try
524        {
525            final List<String> list = new ArrayList<>(getNumberOfConfigurationsInternal());
526            for (final ConfigData cd : configurations)
527            {
528                list.add(cd.getName());
529            }
530            return list;
531        }
532        finally
533        {
534            endRead();
535        }
536    }
537
538    /**
539     * Removes the specified configuration from this combined configuration.
540     *
541     * @param config the configuration to be removed
542     * @return a flag whether this configuration was found and could be removed
543     */
544    public boolean removeConfiguration(final Configuration config)
545    {
546        for (int index = 0; index < getNumberOfConfigurations(); index++)
547        {
548            if (configurations.get(index).getConfiguration() == config)
549            {
550                removeConfigurationAt(index);
551                return true;
552            }
553        }
554
555        return false;
556    }
557
558    /**
559     * Removes the configuration at the specified index.
560     *
561     * @param index the index
562     * @return the removed configuration
563     */
564    public Configuration removeConfigurationAt(final int index)
565    {
566        final ConfigData cd = configurations.remove(index);
567        if (cd.getName() != null)
568        {
569            namedConfigurations.remove(cd.getName());
570        }
571        unregisterListenerAt(cd.getConfiguration());
572        invalidateInternal();
573        return cd.getConfiguration();
574    }
575
576    /**
577     * Removes the configuration with the specified name.
578     *
579     * @param name the name of the configuration to be removed
580     * @return the removed configuration (<b>null</b> if this configuration
581     * was not found)
582     */
583    public Configuration removeConfiguration(final String name)
584    {
585        final Configuration conf = getConfiguration(name);
586        if (conf != null)
587        {
588            removeConfiguration(conf);
589        }
590        return conf;
591    }
592
593    /**
594     * Returns a set with the names of all configurations contained in this
595     * combined configuration. Of course here are only these configurations
596     * listed, for which a name was specified when they were added.
597     *
598     * @return a set with the names of the contained configurations (never
599     * <b>null</b>)
600     */
601    public Set<String> getConfigurationNames()
602    {
603        beginRead(true);
604        try
605        {
606            return namedConfigurations.keySet();
607        }
608        finally
609        {
610            endRead();
611        }
612    }
613
614    /**
615     * Invalidates this combined configuration. This means that the next time a
616     * property is accessed the combined node structure must be re-constructed.
617     * Invalidation of a combined configuration also means that an event of type
618     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
619     * events most times appear twice (once before and once after an update),
620     * this event is only fired once (after update).
621     */
622    public void invalidate()
623    {
624        beginWrite(true);
625        try
626        {
627            invalidateInternal();
628        }
629        finally
630        {
631            endWrite();
632        }
633    }
634
635    /**
636     * Event listener call back for configuration update events. This method is
637     * called whenever one of the contained configurations was modified. It
638     * invalidates this combined configuration.
639     *
640     * @param event the update event
641     */
642    @Override
643    public void onEvent(final ConfigurationEvent event)
644    {
645        if (event.isBeforeUpdate())
646        {
647            invalidate();
648        }
649    }
650
651    /**
652     * Clears this configuration. All contained configurations will be removed.
653     */
654    @Override
655    protected void clearInternal()
656    {
657        unregisterListenerAtChildren();
658        initChildCollections();
659        invalidateInternal();
660    }
661
662    /**
663     * Returns a copy of this object. This implementation performs a deep clone,
664     * i.e. all contained configurations will be cloned, too. For this to work,
665     * all contained configurations must be cloneable. Registered event
666     * listeners won't be cloned. The clone will use the same node combiner than
667     * the original.
668     *
669     * @return the copied object
670     */
671    @Override
672    public Object clone()
673    {
674        beginRead(false);
675        try
676        {
677            final CombinedConfiguration copy = (CombinedConfiguration) super.clone();
678            copy.initChildCollections();
679            for (final ConfigData cd : configurations)
680            {
681                copy.addConfiguration(ConfigurationUtils.cloneConfiguration(cd
682                        .getConfiguration()), cd.getName(), cd.getAt());
683            }
684
685            return copy;
686        }
687        finally
688        {
689            endRead();
690        }
691    }
692
693    /**
694     * Returns the configuration source, in which the specified key is defined.
695     * This method will determine the configuration node that is identified by
696     * the given key. The following constellations are possible:
697     * <ul>
698     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
699     * <li>If the key maps to multiple nodes belonging to different
700     * configuration sources, a {@code IllegalArgumentException} is
701     * thrown (in this case no unique source can be determined).</li>
702     * <li>If exactly one node is found for the key, the (child) configuration
703     * object, to which the node belongs is determined and returned.</li>
704     * <li>For keys that have been added directly to this combined
705     * configuration and that do not belong to the namespaces defined by
706     * existing child configurations this configuration will be returned.</li>
707     * </ul>
708     *
709     * @param key the key of a configuration property
710     * @return the configuration, to which this property belongs or <b>null</b>
711     * if the key cannot be resolved
712     * @throws IllegalArgumentException if the key maps to multiple properties
713     * and the source cannot be determined, or if the key is <b>null</b>
714     * @since 1.5
715     */
716    public Configuration getSource(final String key)
717    {
718        if (key == null)
719        {
720            throw new IllegalArgumentException("Key must not be null!");
721        }
722
723        final Set<Configuration> sources = getSources(key);
724        if (sources.isEmpty())
725        {
726            return null;
727        }
728        final Iterator<Configuration> iterator = sources.iterator();
729        final Configuration source = iterator.next();
730        if (iterator.hasNext())
731        {
732            throw new IllegalArgumentException("The key " + key
733                    + " is defined by multiple sources!");
734        }
735        return source;
736    }
737
738    /**
739     * Returns a set with the configuration sources, in which the specified key
740     * is defined. This method determines the configuration nodes that are
741     * identified by the given key. It then determines the configuration sources
742     * to which these nodes belong and adds them to the result set. Note the
743     * following points:
744     * <ul>
745     * <li>If no node object is found for this key, an empty set is returned.</li>
746     * <li>For keys that have been added directly to this combined configuration
747     * and that do not belong to the namespaces defined by existing child
748     * configurations this combined configuration is contained in the result
749     * set.</li>
750     * </ul>
751     *
752     * @param key the key of a configuration property
753     * @return a set with the configuration sources, which contain this property
754     * @since 2.0
755     */
756    public Set<Configuration> getSources(final String key)
757    {
758        beginRead(false);
759        try
760        {
761            final List<QueryResult<ImmutableNode>> results = fetchNodeList(key);
762            final Set<Configuration> sources = new HashSet<>();
763
764            for (final QueryResult<ImmutableNode> result : results)
765            {
766                final Set<Configuration> resultSources =
767                        findSourceConfigurations(result.getNode());
768                if (resultSources.isEmpty())
769                {
770                    // key must be defined in combined configuration
771                    sources.add(this);
772                }
773                else
774                {
775                    sources.addAll(resultSources);
776                }
777            }
778
779            return sources;
780        }
781        finally
782        {
783            endRead();
784        }
785    }
786
787    /**
788     * {@inheritDoc} This implementation checks whether a combined root node
789     * is available. If not, it is constructed by requesting a write lock.
790     */
791    @Override
792    protected void beginRead(final boolean optimize)
793    {
794        if (optimize)
795        {
796            // just need a lock, don't construct configuration
797            super.beginRead(true);
798            return;
799        }
800
801        boolean lockObtained = false;
802        do
803        {
804            super.beginRead(false);
805            if (isUpToDate())
806            {
807                lockObtained = true;
808            }
809            else
810            {
811                // release read lock and try to obtain a write lock
812                endRead();
813                beginWrite(false); // this constructs the root node
814                endWrite();
815            }
816        } while (!lockObtained);
817    }
818
819    /**
820     * {@inheritDoc} This implementation checks whether a combined root node
821     * is available. If not, it is constructed now.
822     */
823    @Override
824    protected void beginWrite(final boolean optimize)
825    {
826        super.beginWrite(true);
827        if (optimize)
828        {
829            // just need a lock, don't construct configuration
830            return;
831        }
832
833        try
834        {
835            if (!isUpToDate())
836            {
837                getSubConfigurationParentModel().replaceRoot(
838                        constructCombinedNode(), this);
839                upToDate = true;
840            }
841        }
842        catch (final RuntimeException rex)
843        {
844            endWrite();
845            throw rex;
846        }
847    }
848
849    /**
850     * Returns a flag whether this configuration has been invalidated. This
851     * means that the combined nodes structure has to be rebuilt before the
852     * configuration can be accessed.
853     *
854     * @return a flag whether this configuration is invalid
855     */
856    private boolean isUpToDate()
857    {
858        return upToDate;
859    }
860
861    /**
862     * Marks this configuration as invalid. This means that the next access
863     * re-creates the root node. An invalidate event is also fired. Note:
864     * This implementation expects that an exclusive (write) lock is held on
865     * this instance.
866     */
867    private void invalidateInternal()
868    {
869        upToDate = false;
870        fireEvent(COMBINED_INVALIDATE, null, null, false);
871    }
872
873    /**
874     * Initializes internal data structures for storing information about
875     * child configurations.
876     */
877    private void initChildCollections()
878    {
879        configurations = new ArrayList<>();
880        namedConfigurations = new HashMap<>();
881    }
882
883    /**
884     * Creates the root node of this combined configuration.
885     *
886     * @return the combined root node
887     */
888    private ImmutableNode constructCombinedNode()
889    {
890        if (getNumberOfConfigurationsInternal() < 1)
891        {
892            if (getLogger().isDebugEnabled())
893            {
894                getLogger().debug("No configurations defined for " + this);
895            }
896            return EMPTY_ROOT;
897        }
898        final Iterator<ConfigData> it = configurations.iterator();
899        ImmutableNode node = it.next().getTransformedRoot();
900        while (it.hasNext())
901        {
902            node = nodeCombiner.combine(node,
903                    it.next().getTransformedRoot());
904        }
905        if (getLogger().isDebugEnabled())
906        {
907            final ByteArrayOutputStream os = new ByteArrayOutputStream();
908            final PrintStream stream = new PrintStream(os);
909            TreeUtils.printTree(stream, node);
910            getLogger().debug(os.toString());
911        }
912        return node;
913    }
914
915    /**
916     * Determines the configurations to which the specified node belongs. This
917     * is done by inspecting the nodes structures of all child configurations.
918     *
919     * @param node the node
920     * @return a set with the owning configurations
921     */
922    private Set<Configuration> findSourceConfigurations(final ImmutableNode node)
923    {
924        final Set<Configuration> result = new HashSet<>();
925        final FindNodeVisitor<ImmutableNode> visitor =
926                new FindNodeVisitor<>(node);
927
928        for (final ConfigData cd : configurations)
929        {
930            NodeTreeWalker.INSTANCE.walkBFS(cd.getRootNode(), visitor,
931                    getModel().getNodeHandler());
932            if (visitor.isFound())
933            {
934                result.add(cd.getConfiguration());
935                visitor.reset();
936            }
937        }
938
939        return result;
940    }
941
942    /**
943     * Registers this combined configuration as listener at the given child
944     * configuration.
945     *
946     * @param configuration the child configuration
947     */
948    private void registerListenerAt(final Configuration configuration)
949    {
950        if (configuration instanceof EventSource)
951        {
952            ((EventSource) configuration).addEventListener(
953                    ConfigurationEvent.ANY, this);
954        }
955    }
956
957    /**
958     * Removes this combined configuration as listener from the given child
959     * configuration.
960     *
961     * @param configuration the child configuration
962     */
963    private void unregisterListenerAt(final Configuration configuration)
964    {
965        if (configuration instanceof EventSource)
966        {
967            ((EventSource) configuration).removeEventListener(
968                    ConfigurationEvent.ANY, this);
969        }
970    }
971
972    /**
973     * Removes this combined configuration as listener from all child
974     * configurations. This method is called on a clear() operation.
975     */
976    private void unregisterListenerAtChildren()
977    {
978        if (configurations != null)
979        {
980            for (final ConfigData child : configurations)
981            {
982                unregisterListenerAt(child.getConfiguration());
983            }
984        }
985    }
986
987    /**
988     * Returns the number of child configurations in this combined
989     * configuration. The internal list of child configurations is accessed
990     * without synchronization.
991     *
992     * @return the number of child configurations
993     */
994    private int getNumberOfConfigurationsInternal()
995    {
996        return configurations.size();
997    }
998
999    /**
1000     * An internal helper class for storing information about contained
1001     * configurations.
1002     */
1003    private class ConfigData
1004    {
1005        /** Stores a reference to the configuration. */
1006        private final Configuration configuration;
1007
1008        /** Stores the name under which the configuration is stored. */
1009        private final String name;
1010
1011        /** Stores the at information as path of nodes. */
1012        private final Collection<String> atPath;
1013
1014        /** Stores the at string.*/
1015        private final String at;
1016
1017        /** Stores the root node for this child configuration.*/
1018        private ImmutableNode rootNode;
1019
1020        /**
1021         * Creates a new instance of {@code ConfigData} and initializes
1022         * it.
1023         *
1024         * @param config the configuration
1025         * @param n the name
1026         * @param at the at position
1027         */
1028        public ConfigData(final Configuration config, final String n, final String at)
1029        {
1030            configuration = config;
1031            name = n;
1032            atPath = parseAt(at);
1033            this.at = at;
1034        }
1035
1036        /**
1037         * Returns the stored configuration.
1038         *
1039         * @return the configuration
1040         */
1041        public Configuration getConfiguration()
1042        {
1043            return configuration;
1044        }
1045
1046        /**
1047         * Returns the configuration's name.
1048         *
1049         * @return the name
1050         */
1051        public String getName()
1052        {
1053            return name;
1054        }
1055
1056        /**
1057         * Returns the at position of this configuration.
1058         *
1059         * @return the at position
1060         */
1061        public String getAt()
1062        {
1063            return at;
1064        }
1065
1066        /**
1067         * Returns the root node for this child configuration.
1068         *
1069         * @return the root node of this child configuration
1070         * @since 1.5
1071         */
1072        public ImmutableNode getRootNode()
1073        {
1074            return rootNode;
1075        }
1076
1077        /**
1078         * Returns the transformed root node of the stored configuration. The
1079         * term &quot;transformed&quot; means that an eventually defined at path
1080         * has been applied.
1081         *
1082         * @return the transformed root node
1083         */
1084        public ImmutableNode getTransformedRoot()
1085        {
1086            final ImmutableNode configRoot = getRootNodeOfConfiguration();
1087            return atPath == null ? configRoot : prependAtPath(configRoot);
1088        }
1089
1090        /**
1091         * Prepends the at path to the given node.
1092         *
1093         * @param node the root node of the represented configuration
1094         * @return the new root node including the at path
1095         */
1096        private ImmutableNode prependAtPath(final ImmutableNode node)
1097        {
1098            final ImmutableNode.Builder pathBuilder = new ImmutableNode.Builder();
1099            final Iterator<String> pathIterator = atPath.iterator();
1100            prependAtPathComponent(pathBuilder, pathIterator.next(),
1101                    pathIterator, node);
1102            return new ImmutableNode.Builder(1).addChild(pathBuilder.create())
1103                    .create();
1104        }
1105
1106        /**
1107         * Handles a single component of the at path. A corresponding node is
1108         * created and added to the hierarchical path to the original root node
1109         * of the configuration.
1110         *
1111         * @param builder the current node builder object
1112         * @param currentComponent the name of the current path component
1113         * @param components an iterator with all components of the at path
1114         * @param orgRoot the original root node of the wrapped configuration
1115         */
1116        private void prependAtPathComponent(final ImmutableNode.Builder builder,
1117                final String currentComponent, final Iterator<String> components,
1118                final ImmutableNode orgRoot)
1119        {
1120            builder.name(currentComponent);
1121            if (components.hasNext())
1122            {
1123                final ImmutableNode.Builder childBuilder =
1124                        new ImmutableNode.Builder();
1125                prependAtPathComponent(childBuilder, components.next(),
1126                        components, orgRoot);
1127                builder.addChild(childBuilder.create());
1128            }
1129            else
1130            {
1131                builder.addChildren(orgRoot.getChildren());
1132                builder.addAttributes(orgRoot.getAttributes());
1133                builder.value(orgRoot.getValue());
1134            }
1135        }
1136
1137        /**
1138         * Obtains the root node of the wrapped configuration. If necessary, a
1139         * hierarchical representation of the configuration has to be created
1140         * first.
1141         *
1142         * @return the root node of the associated configuration
1143         */
1144        private ImmutableNode getRootNodeOfConfiguration()
1145        {
1146            getConfiguration().lock(LockMode.READ);
1147            try
1148            {
1149                final ImmutableNode root =
1150                        ConfigurationUtils
1151                                .convertToHierarchical(getConfiguration(),
1152                                        conversionExpressionEngine).getNodeModel()
1153                                .getInMemoryRepresentation();
1154                rootNode = root;
1155                return root;
1156            }
1157            finally
1158            {
1159                getConfiguration().unlock(LockMode.READ);
1160            }
1161        }
1162
1163        /**
1164         * Splits the at path into its components.
1165         *
1166         * @param at the at string
1167         * @return a collection with the names of the single components
1168         */
1169        private Collection<String> parseAt(final String at)
1170        {
1171            if (at == null)
1172            {
1173                return null;
1174            }
1175
1176            final Collection<String> result = new ArrayList<>();
1177            final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
1178                    AT_ENGINE, at).iterator();
1179            while (it.hasNext())
1180            {
1181                result.add(it.nextKey());
1182            }
1183            return result;
1184        }
1185    }
1186}