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    package org.apache.commons.configuration;
018    
019    import java.math.BigDecimal;
020    import java.math.BigInteger;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Properties;
028    import java.util.Set;
029    import java.util.concurrent.ConcurrentHashMap;
030    import java.util.concurrent.ConcurrentMap;
031    
032    import org.apache.commons.configuration.event.ConfigurationErrorListener;
033    import org.apache.commons.configuration.event.ConfigurationListener;
034    import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
035    import org.apache.commons.configuration.tree.ConfigurationNode;
036    import org.apache.commons.configuration.tree.ExpressionEngine;
037    import org.apache.commons.configuration.tree.NodeCombiner;
038    import org.apache.commons.lang.text.StrSubstitutor;
039    import org.apache.commons.logging.Log;
040    import org.apache.commons.logging.LogFactory;
041    
042    /**
043     * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used. Each CombinedConfiguration
044     * is referenced by a key that is dynamically constructed from a key pattern on each call. The key pattern
045     * will be resolved using the configured ConfigurationInterpolator.
046     * @since 1.6
047     * @author <a
048     * href="http://commons.apache.org/configuration/team-list.html">Commons
049     * Configuration team</a>
050     * @version $Id: DynamicCombinedConfiguration.java 1367234 2012-07-30 19:32:00Z oheger $
051     */
052    public class DynamicCombinedConfiguration extends CombinedConfiguration
053    {
054        /**
055         * Prevent recursion while resolving unprefixed properties.
056         */
057        private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
058        {
059            @Override
060            protected synchronized Boolean initialValue()
061            {
062                return Boolean.FALSE;
063            }
064        };
065    
066        /** The CombinedConfigurations */
067        private final ConcurrentMap<String, CombinedConfiguration> configs =
068                new ConcurrentHashMap<String, CombinedConfiguration>();
069    
070        /** Stores a list with the contained configurations. */
071        private List<ConfigData> configurations = new ArrayList<ConfigData>();
072    
073        /** Stores a map with the named configurations. */
074        private Map<String, AbstractConfiguration> namedConfigurations =
075                new HashMap<String, AbstractConfiguration>();
076    
077        /** The key pattern for the CombinedConfiguration map */
078        private String keyPattern;
079    
080        /** Stores the combiner. */
081        private NodeCombiner nodeCombiner;
082    
083        /** The name of the logger to use for each CombinedConfiguration */
084        private String loggerName = DynamicCombinedConfiguration.class.getName();
085    
086        /** The object for handling variable substitution in key patterns. */
087        private StrSubstitutor localSubst = new StrSubstitutor(new ConfigurationInterpolator());
088    
089        /**
090         * Creates a new instance of {@code DynamicCombinedConfiguration} and
091         * initializes the combiner to be used.
092         *
093         * @param comb the node combiner (can be <b>null</b>, then a union combiner
094         * is used as default)
095         */
096        public DynamicCombinedConfiguration(NodeCombiner comb)
097        {
098            super();
099            setNodeCombiner(comb);
100            setIgnoreReloadExceptions(false);
101            setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
102        }
103    
104        /**
105         * Creates a new instance of {@code DynamicCombinedConfiguration} that uses
106         * a union combiner.
107         *
108         * @see org.apache.commons.configuration.tree.UnionCombiner
109         */
110        public DynamicCombinedConfiguration()
111        {
112            super();
113            setIgnoreReloadExceptions(false);
114            setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
115        }
116    
117        public void setKeyPattern(String pattern)
118        {
119            this.keyPattern = pattern;
120        }
121    
122        public String getKeyPattern()
123        {
124            return this.keyPattern;
125        }
126    
127        /**
128         * Set the name of the Logger to use on each CombinedConfiguration.
129         * @param name The Logger name.
130         */
131        public void setLoggerName(String name)
132        {
133            this.loggerName = name;
134        }
135    
136        /**
137         * Returns the node combiner that is used for creating the combined node
138         * structure.
139         *
140         * @return the node combiner
141         */
142        @Override
143        public NodeCombiner getNodeCombiner()
144        {
145            return nodeCombiner;
146        }
147    
148        /**
149         * Sets the node combiner. This object will be used when the combined node
150         * structure is to be constructed. It must not be <b>null</b>, otherwise an
151         * {@code IllegalArgumentException} exception is thrown. Changing the
152         * node combiner causes an invalidation of this combined configuration, so
153         * that the new combiner immediately takes effect.
154         *
155         * @param nodeCombiner the node combiner
156         */
157        @Override
158        public void setNodeCombiner(NodeCombiner nodeCombiner)
159        {
160            if (nodeCombiner == null)
161            {
162                throw new IllegalArgumentException(
163                        "Node combiner must not be null!");
164            }
165            this.nodeCombiner = nodeCombiner;
166            invalidateAll();
167        }
168        /**
169         * Adds a new configuration to this combined configuration. It is possible
170         * (but not mandatory) to give the new configuration a name. This name must
171         * be unique, otherwise a {@code ConfigurationRuntimeException} will
172         * be thrown. With the optional {@code at} argument you can specify
173         * where in the resulting node structure the content of the added
174         * configuration should appear. This is a string that uses dots as property
175         * delimiters (independent on the current expression engine). For instance
176         * if you pass in the string {@code "database.tables"},
177         * all properties of the added configuration will occur in this branch.
178         *
179         * @param config the configuration to add (must not be <b>null</b>)
180         * @param name the name of this configuration (can be <b>null</b>)
181         * @param at the position of this configuration in the combined tree (can be
182         * <b>null</b>)
183         */
184        @Override
185        public void addConfiguration(AbstractConfiguration config, String name,
186                String at)
187        {
188            ConfigData cd = new ConfigData(config, name, at);
189            configurations.add(cd);
190            if (name != null)
191            {
192                namedConfigurations.put(name, config);
193            }
194        }
195           /**
196         * Returns the number of configurations that are contained in this combined
197         * configuration.
198         *
199         * @return the number of contained configurations
200         */
201        @Override
202        public int getNumberOfConfigurations()
203        {
204            return configurations.size();
205        }
206    
207        /**
208         * Returns the configuration at the specified index. The contained
209         * configurations are numbered in the order they were added to this combined
210         * configuration. The index of the first configuration is 0.
211         *
212         * @param index the index
213         * @return the configuration at this index
214         */
215        @Override
216        public Configuration getConfiguration(int index)
217        {
218            ConfigData cd = configurations.get(index);
219            return cd.getConfiguration();
220        }
221    
222        /**
223         * Returns the configuration with the given name. This can be <b>null</b>
224         * if no such configuration exists.
225         *
226         * @param name the name of the configuration
227         * @return the configuration with this name
228         */
229        @Override
230        public Configuration getConfiguration(String name)
231        {
232            return namedConfigurations.get(name);
233        }
234    
235        /**
236         * Returns a set with the names of all configurations contained in this
237         * combined configuration. Of course here are only these configurations
238         * listed, for which a name was specified when they were added.
239         *
240         * @return a set with the names of the contained configurations (never
241         * <b>null</b>)
242         */
243        @Override
244        public Set<String> getConfigurationNames()
245        {
246            return namedConfigurations.keySet();
247        }
248    
249        /**
250         * Removes the configuration with the specified name.
251         *
252         * @param name the name of the configuration to be removed
253         * @return the removed configuration (<b>null</b> if this configuration
254         * was not found)
255         */
256        @Override
257        public Configuration removeConfiguration(String name)
258        {
259            Configuration conf = getConfiguration(name);
260            if (conf != null)
261            {
262                removeConfiguration(conf);
263            }
264            return conf;
265        }
266    
267        /**
268         * Removes the specified configuration from this combined configuration.
269         *
270         * @param config the configuration to be removed
271         * @return a flag whether this configuration was found and could be removed
272         */
273        @Override
274        public boolean removeConfiguration(Configuration config)
275        {
276            for (int index = 0; index < getNumberOfConfigurations(); index++)
277            {
278                if (configurations.get(index).getConfiguration() == config)
279                {
280                    removeConfigurationAt(index);
281    
282                }
283            }
284    
285            return super.removeConfiguration(config);
286        }
287    
288        /**
289         * Removes the configuration at the specified index.
290         *
291         * @param index the index
292         * @return the removed configuration
293         */
294        @Override
295        public Configuration removeConfigurationAt(int index)
296        {
297            ConfigData cd = configurations.remove(index);
298            if (cd.getName() != null)
299            {
300                namedConfigurations.remove(cd.getName());
301            }
302            return super.removeConfigurationAt(index);
303        }
304        /**
305         * Returns the configuration root node of this combined configuration. This
306         * method will construct a combined node structure using the current node
307         * combiner if necessary.
308         *
309         * @return the combined root node
310         */
311        @Override
312        public ConfigurationNode getRootNode()
313        {
314            return getCurrentConfig().getRootNode();
315        }
316    
317        @Override
318        public void setRootNode(ConfigurationNode rootNode)
319        {
320            if (configs != null)
321            {
322                this.getCurrentConfig().setRootNode(rootNode);
323            }
324            else
325            {
326                super.setRootNode(rootNode);
327            }
328        }
329    
330        @Override
331        public void addProperty(String key, Object value)
332        {
333            this.getCurrentConfig().addProperty(key, value);
334        }
335    
336        @Override
337        public void clear()
338        {
339            if (configs != null)
340            {
341                this.getCurrentConfig().clear();
342            }
343        }
344    
345        @Override
346        public void clearProperty(String key)
347        {
348            this.getCurrentConfig().clearProperty(key);
349        }
350    
351        @Override
352        public boolean containsKey(String key)
353        {
354            return this.getCurrentConfig().containsKey(key);
355        }
356    
357        @Override
358        public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
359        {
360            return this.getCurrentConfig().getBigDecimal(key, defaultValue);
361        }
362    
363        @Override
364        public BigDecimal getBigDecimal(String key)
365        {
366            return this.getCurrentConfig().getBigDecimal(key);
367        }
368    
369        @Override
370        public BigInteger getBigInteger(String key, BigInteger defaultValue)
371        {
372            return this.getCurrentConfig().getBigInteger(key, defaultValue);
373        }
374    
375        @Override
376        public BigInteger getBigInteger(String key)
377        {
378            return this.getCurrentConfig().getBigInteger(key);
379        }
380    
381        @Override
382        public boolean getBoolean(String key, boolean defaultValue)
383        {
384            return this.getCurrentConfig().getBoolean(key, defaultValue);
385        }
386    
387        @Override
388        public Boolean getBoolean(String key, Boolean defaultValue)
389        {
390            return this.getCurrentConfig().getBoolean(key, defaultValue);
391        }
392    
393        @Override
394        public boolean getBoolean(String key)
395        {
396            return this.getCurrentConfig().getBoolean(key);
397        }
398    
399        @Override
400        public byte getByte(String key, byte defaultValue)
401        {
402            return this.getCurrentConfig().getByte(key, defaultValue);
403        }
404    
405        @Override
406        public Byte getByte(String key, Byte defaultValue)
407        {
408            return this.getCurrentConfig().getByte(key, defaultValue);
409        }
410    
411        @Override
412        public byte getByte(String key)
413        {
414            return this.getCurrentConfig().getByte(key);
415        }
416    
417        @Override
418        public double getDouble(String key, double defaultValue)
419        {
420            return this.getCurrentConfig().getDouble(key, defaultValue);
421        }
422    
423        @Override
424        public Double getDouble(String key, Double defaultValue)
425        {
426            return this.getCurrentConfig().getDouble(key, defaultValue);
427        }
428    
429        @Override
430        public double getDouble(String key)
431        {
432            return this.getCurrentConfig().getDouble(key);
433        }
434    
435        @Override
436        public float getFloat(String key, float defaultValue)
437        {
438            return this.getCurrentConfig().getFloat(key, defaultValue);
439        }
440    
441        @Override
442        public Float getFloat(String key, Float defaultValue)
443        {
444            return this.getCurrentConfig().getFloat(key, defaultValue);
445        }
446    
447        @Override
448        public float getFloat(String key)
449        {
450            return this.getCurrentConfig().getFloat(key);
451        }
452    
453        @Override
454        public int getInt(String key, int defaultValue)
455        {
456            return this.getCurrentConfig().getInt(key, defaultValue);
457        }
458    
459        @Override
460        public int getInt(String key)
461        {
462            return this.getCurrentConfig().getInt(key);
463        }
464    
465        @Override
466        public Integer getInteger(String key, Integer defaultValue)
467        {
468            return this.getCurrentConfig().getInteger(key, defaultValue);
469        }
470    
471        @Override
472        public Iterator<String> getKeys()
473        {
474            return this.getCurrentConfig().getKeys();
475        }
476    
477        @Override
478        public Iterator<String> getKeys(String prefix)
479        {
480            return this.getCurrentConfig().getKeys(prefix);
481        }
482    
483        @Override
484        public List<Object> getList(String key, List<Object> defaultValue)
485        {
486            return this.getCurrentConfig().getList(key, defaultValue);
487        }
488    
489        @Override
490        public List<Object> getList(String key)
491        {
492            return this.getCurrentConfig().getList(key);
493        }
494    
495        @Override
496        public long getLong(String key, long defaultValue)
497        {
498            return this.getCurrentConfig().getLong(key, defaultValue);
499        }
500    
501        @Override
502        public Long getLong(String key, Long defaultValue)
503        {
504            return this.getCurrentConfig().getLong(key, defaultValue);
505        }
506    
507        @Override
508        public long getLong(String key)
509        {
510            return this.getCurrentConfig().getLong(key);
511        }
512    
513        @Override
514        public Properties getProperties(String key)
515        {
516            return this.getCurrentConfig().getProperties(key);
517        }
518    
519        @Override
520        public Object getProperty(String key)
521        {
522            return this.getCurrentConfig().getProperty(key);
523        }
524    
525        @Override
526        public short getShort(String key, short defaultValue)
527        {
528            return this.getCurrentConfig().getShort(key, defaultValue);
529        }
530    
531        @Override
532        public Short getShort(String key, Short defaultValue)
533        {
534            return this.getCurrentConfig().getShort(key, defaultValue);
535        }
536    
537        @Override
538        public short getShort(String key)
539        {
540            return this.getCurrentConfig().getShort(key);
541        }
542    
543        @Override
544        public String getString(String key, String defaultValue)
545        {
546            return this.getCurrentConfig().getString(key, defaultValue);
547        }
548    
549        @Override
550        public String getString(String key)
551        {
552            return this.getCurrentConfig().getString(key);
553        }
554    
555        @Override
556        public String[] getStringArray(String key)
557        {
558            return this.getCurrentConfig().getStringArray(key);
559        }
560    
561        @Override
562        public boolean isEmpty()
563        {
564            return this.getCurrentConfig().isEmpty();
565        }
566    
567        @Override
568        public void setProperty(String key, Object value)
569        {
570            if (configs != null)
571            {
572                this.getCurrentConfig().setProperty(key, value);
573            }
574        }
575    
576        @Override
577        public Configuration subset(String prefix)
578        {
579            return this.getCurrentConfig().subset(prefix);
580        }
581    
582        @Override
583        public Node getRoot()
584        {
585            return this.getCurrentConfig().getRoot();
586        }
587    
588        @Override
589        public void setRoot(Node node)
590        {
591            if (configs != null)
592            {
593                this.getCurrentConfig().setRoot(node);
594            }
595            else
596            {
597                super.setRoot(node);
598            }
599        }
600    
601        @Override
602        public ExpressionEngine getExpressionEngine()
603        {
604            return super.getExpressionEngine();
605        }
606    
607        @Override
608        public void setExpressionEngine(ExpressionEngine expressionEngine)
609        {
610            super.setExpressionEngine(expressionEngine);
611        }
612    
613        @Override
614        public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
615        {
616            this.getCurrentConfig().addNodes(key, nodes);
617        }
618    
619        @Override
620        public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
621        {
622            return this.getCurrentConfig().configurationAt(key, supportUpdates);
623        }
624    
625        @Override
626        public SubnodeConfiguration configurationAt(String key)
627        {
628            return this.getCurrentConfig().configurationAt(key);
629        }
630    
631        @Override
632        public List<HierarchicalConfiguration> configurationsAt(String key)
633        {
634            return this.getCurrentConfig().configurationsAt(key);
635        }
636    
637        @Override
638        public void clearTree(String key)
639        {
640            this.getCurrentConfig().clearTree(key);
641        }
642    
643        @Override
644        public int getMaxIndex(String key)
645        {
646            return this.getCurrentConfig().getMaxIndex(key);
647        }
648    
649        @Override
650        public Configuration interpolatedConfiguration()
651        {
652            return this.getCurrentConfig().interpolatedConfiguration();
653        }
654    
655    
656        /**
657         * Returns the configuration source, in which the specified key is defined.
658         * This method will determine the configuration node that is identified by
659         * the given key. The following constellations are possible:
660         * <ul>
661         * <li>If no node object is found for this key, <b>null</b> is returned.</li>
662         * <li>If the key maps to multiple nodes belonging to different
663         * configuration sources, a {@code IllegalArgumentException} is
664         * thrown (in this case no unique source can be determined).</li>
665         * <li>If exactly one node is found for the key, the (child) configuration
666         * object, to which the node belongs is determined and returned.</li>
667         * <li>For keys that have been added directly to this combined
668         * configuration and that do not belong to the namespaces defined by
669         * existing child configurations this configuration will be returned.</li>
670         * </ul>
671         *
672         * @param key the key of a configuration property
673         * @return the configuration, to which this property belongs or <b>null</b>
674         * if the key cannot be resolved
675         * @throws IllegalArgumentException if the key maps to multiple properties
676         * and the source cannot be determined, or if the key is <b>null</b>
677         */
678        @Override
679        public Configuration getSource(String key)
680        {
681            if (key == null)
682            {
683                throw new IllegalArgumentException("Key must not be null!");
684            }
685            return getCurrentConfig().getSource(key);
686        }
687    
688        @Override
689        public void addConfigurationListener(ConfigurationListener l)
690        {
691            super.addConfigurationListener(l);
692    
693            for (CombinedConfiguration cc : configs.values())
694            {
695                cc.addConfigurationListener(l);
696            }
697        }
698    
699        @Override
700        public boolean removeConfigurationListener(ConfigurationListener l)
701        {
702            for (CombinedConfiguration cc : configs.values())
703            {
704                cc.removeConfigurationListener(l);
705            }
706            return super.removeConfigurationListener(l);
707        }
708    
709        @Override
710        public Collection<ConfigurationListener> getConfigurationListeners()
711        {
712            return super.getConfigurationListeners();
713        }
714    
715        @Override
716        public void clearConfigurationListeners()
717        {
718            for (CombinedConfiguration cc : configs.values())
719            {
720                cc.clearConfigurationListeners();
721            }
722            super.clearConfigurationListeners();
723        }
724    
725        @Override
726        public void addErrorListener(ConfigurationErrorListener l)
727        {
728            for (CombinedConfiguration cc : configs.values())
729            {
730                cc.addErrorListener(l);
731            }
732            super.addErrorListener(l);
733        }
734    
735        @Override
736        public boolean removeErrorListener(ConfigurationErrorListener l)
737        {
738            for (CombinedConfiguration cc : configs.values())
739            {
740                cc.removeErrorListener(l);
741            }
742            return super.removeErrorListener(l);
743        }
744    
745        @Override
746        public void clearErrorListeners()
747        {
748            for (CombinedConfiguration cc : configs.values())
749            {
750                cc.clearErrorListeners();
751            }
752            super.clearErrorListeners();
753        }
754    
755        @Override
756        public Collection<ConfigurationErrorListener> getErrorListeners()
757        {
758            return super.getErrorListeners();
759        }
760    
761        /**
762         * Returns a copy of this object. This implementation performs a deep clone,
763         * i.e. all contained configurations will be cloned, too. For this to work,
764         * all contained configurations must be cloneable. Registered event
765         * listeners won't be cloned. The clone will use the same node combiner than
766         * the original.
767         *
768         * @return the copied object
769         */
770        @Override
771        public Object clone()
772        {
773            return super.clone();
774        }
775    
776        /**
777         * Invalidates the current combined configuration. This means that the next time a
778         * property is accessed the combined node structure must be re-constructed.
779         * Invalidation of a combined configuration also means that an event of type
780         * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
781         * events most times appear twice (once before and once after an update),
782         * this event is only fired once (after update).
783         */
784        @Override
785        public void invalidate()
786        {
787            getCurrentConfig().invalidate();
788        }
789    
790        public void invalidateAll()
791        {
792            if (configs == null)
793            {
794                return;
795            }
796            for (CombinedConfiguration cc : configs.values())
797            {
798                cc.invalidate();
799            }
800        }
801    
802        /*
803         * Don't allow resolveContainerStore to be called recursively.
804         * @param key The key to resolve.
805         * @return The value of the key.
806         */
807        @Override
808        protected Object resolveContainerStore(String key)
809        {
810            if (recursive.get().booleanValue())
811            {
812                return null;
813            }
814            recursive.set(Boolean.TRUE);
815            try
816            {
817                return super.resolveContainerStore(key);
818            }
819            finally
820            {
821                recursive.set(Boolean.FALSE);
822            }
823        }
824    
825        private CombinedConfiguration getCurrentConfig()
826        {
827            String key = localSubst.replace(keyPattern);
828            CombinedConfiguration config = configs.get(key);
829            // The double-checked works here due to the Thread guarantees of ConcurrentMap.
830            if (config == null)
831            {
832                synchronized (configs)
833                {
834                    config = configs.get(key);
835                    if (config == null)
836                    {
837                        config = new CombinedConfiguration(getNodeCombiner());
838                        if (loggerName != null)
839                        {
840                            Log log = LogFactory.getLog(loggerName);
841                            if (log != null)
842                            {
843                                config.setLogger(log);
844                            }
845                        }
846                        config.setIgnoreReloadExceptions(isIgnoreReloadExceptions());
847                        config.setExpressionEngine(this.getExpressionEngine());
848                        config.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
849                        config.setConversionExpressionEngine(getConversionExpressionEngine());
850                        config.setListDelimiter(getListDelimiter());
851                        for (ConfigurationErrorListener listener : getErrorListeners())
852                        {
853                            config.addErrorListener(listener);
854                        }
855                        for (ConfigurationListener listener : getConfigurationListeners())
856                        {
857                            config.addConfigurationListener(listener);
858                        }
859                        config.setForceReloadCheck(isForceReloadCheck());
860                        for (ConfigData data : configurations)
861                        {
862                            config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt());
863                        }
864                        configs.put(key, config);
865                    }
866                }
867            }
868            if (getLogger().isDebugEnabled())
869            {
870                getLogger().debug("Returning config for " + key + ": " + config);
871            }
872            return config;
873        }
874    
875        /**
876         * Internal class that identifies each Configuration.
877         */
878        static class ConfigData
879        {
880            /** Stores a reference to the configuration. */
881            private AbstractConfiguration configuration;
882    
883            /** Stores the name under which the configuration is stored. */
884            private String name;
885    
886            /** Stores the at string.*/
887            private String at;
888    
889            /**
890             * Creates a new instance of {@code ConfigData} and initializes
891             * it.
892             *
893             * @param config the configuration
894             * @param n the name
895             * @param at the at position
896             */
897            public ConfigData(AbstractConfiguration config, String n, String at)
898            {
899                configuration = config;
900                name = n;
901                this.at = at;
902            }
903    
904            /**
905             * Returns the stored configuration.
906             *
907             * @return the configuration
908             */
909            public AbstractConfiguration getConfiguration()
910            {
911                return configuration;
912            }
913    
914            /**
915             * Returns the configuration's name.
916             *
917             * @return the name
918             */
919            public String getName()
920            {
921                return name;
922            }
923    
924            /**
925             * Returns the at position of this configuration.
926             *
927             * @return the at position
928             */
929            public String getAt()
930            {
931                return at;
932            }
933    
934        }
935    }