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