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.configuration;
018
019import java.math.BigDecimal;
020import java.math.BigInteger;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Properties;
028import java.util.Set;
029import java.util.concurrent.ConcurrentHashMap;
030import java.util.concurrent.ConcurrentMap;
031
032import org.apache.commons.configuration.event.ConfigurationErrorListener;
033import org.apache.commons.configuration.event.ConfigurationListener;
034import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
035import org.apache.commons.configuration.tree.ConfigurationNode;
036import org.apache.commons.configuration.tree.ExpressionEngine;
037import org.apache.commons.configuration.tree.NodeCombiner;
038import org.apache.commons.lang.text.StrSubstitutor;
039import org.apache.commons.logging.Log;
040import 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 1534064 2013-10-21 08:44:33Z henning $
051 */
052public 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<?> 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}