1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package de.fu_berlin.ties;
23
24 import java.io.File;
25 import java.io.InputStream;
26 import java.util.Collections;
27 import java.util.Iterator;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.SortedSet;
32 import java.util.TreeSet;
33
34 import org.apache.commons.configuration.CompositeConfiguration;
35 import org.apache.commons.configuration.Configuration;
36 import org.apache.commons.configuration.ConfigurationException;
37 import org.apache.commons.configuration.ConfigurationKey;
38 import org.apache.commons.configuration.PropertiesConfiguration;
39 import org.apache.commons.configuration.XMLConfiguration;
40 import org.apache.commons.lang.StringUtils;
41 import org.apache.commons.lang.builder.ToStringBuilder;
42
43 import de.fu_berlin.ties.io.IOUtils;
44 import de.fu_berlin.ties.util.Util;
45
46 /***
47 * A composite configuration that allows to localize of keys and to querying
48 * types and descriptions of entries.
49 *
50 * @author Christian Siefkes
51 * @version $Revision: 1.32 $, $Date: 2006/10/21 16:03:52 $, $Author: siefkes $
52 */
53 public class TiesConfiguration extends CompositeConfiguration {
54
55 /***
56 * The main configuration object for TIES, loaded via
57 * {@link #TiesConfiguration(String)} using "ties" as base name.
58 * <strong>You should NOT modify the configuration properties stored in this
59 * object, especially since it is not fully thread-safe (unless you know
60 * what you're doing and are sure there are no other objects around that
61 * might try to access the configuration, e.g. at the begin of a
62 * program's <code>main</code> method).</strong>
63 */
64 public static final TiesConfiguration CONF = new TiesConfiguration("ties");
65
66 /***
67 * The subdirectory in the class path containing config files.
68 */
69 public static final String CONF_DIR = "conf";
70
71 /***
72 * The extension of config files in {@link PropertiesConfiguration}
73 * format.
74 */
75 public static final String CONF_EXTENSION = ".cfg";
76
77 /***
78 * The extension of config files in {@linkplain XMLConfiguration XML
79 * format}.
80 */
81 public static final String XML_EXTENSION = ".xml";
82
83 /***
84 * The extension of
85 * {@linkplain #addDescriptorConfig(Configuration) descriptor configuration}
86 * files (in {@link PropertiesConfiguration} format).
87 */
88 public static final String DESC_EXTENSION = ".desc";
89
90 /***
91 * Configuration key prefix mapping goals to fully qualified class names.
92 */
93 public static final String CONFIG_GOAL_PREFIX = "goal";
94
95 /***
96 * Special configuration key: the language of documents, using the <a
97 * href="http://www.loc.gov/standards/iso639-2/englangn.html">ISO 639</a>
98 * language codes (2-letter codes where available, e.g. "en" (English) or
99 * "de" (German).
100 */
101 public static final String CONFIG_LANG = "lang";
102
103 /***
104 * The character separating tokens in keys.
105 */
106 private static final char PROPERTY_DELIM = '.';
107
108
109 /***
110 * Creates a adapted (caller-specific) key by joining a base name
111 * with a suffix. If the adapted form of the key doesn't exist in this
112 * configuration, the base form is returned instead.
113 *
114 * @param baseKey the basic key
115 * @param suffix the suffix of the key; if <code>null</code> the
116 * <code>baseKey</code> is returned
117 * @return the full key, adapted by joining with the {@link #CONFIG_LANG}
118 * key; resp. the base form if the full key doesn't exist or
119 * <code>suffix</code> is <code>null</code>
120 * @see #joinKey(String, String)
121 */
122 public String adaptKey(final String baseKey, final String suffix) {
123 if (suffix != null) {
124 final String adaptedKey = joinKey(baseKey, suffix);
125
126
127 if (containsKey(adaptedKey)) {
128 return adaptedKey;
129 } else {
130 return baseKey;
131 }
132 } else {
133 return baseKey;
134 }
135 }
136
137 /***
138 * Utility method that thorougly checks whether a string array is empty.
139 * It returns <code>true</code> in three cases:
140 *
141 * <ol>
142 * <li>the specified array is <code>null</code>;
143 * <li>the specified array is empty (0 elements);
144 * <li>the specified array contains a single element which is
145 * <code>null</code> or an empty or blank (only whitespace) string.
146 * </ol>
147 *
148 * This method is useful because some reconfigurations can cause a
149 * <code>TiesConfiguration</code> to return a single-element list containing
150 * only an empty string. Usually such a list should be treated as empty.
151 *
152 * @param array the array to check
153 * @return <code>true</code> iff the list is empty as described above
154 */
155 public static boolean arrayIsEmpty(final String[] array) {
156 final boolean result;
157
158 if ((array == null) || array.length == 0) {
159
160 result = true;
161 } else if (array.length == 1) {
162
163 result = StringUtils.isBlank(array[0]);
164 } else {
165 result = false;
166 }
167
168 return result;
169 }
170
171 /***
172 * Utility method that thorougly checks whether a list is empty. It returns
173 * <code>true</code> in three cases:
174 *
175 * <ol>
176 * <li>the specified list is <code>null</code>;
177 * <li>the specified list is empty (0 elements);
178 * <li>the specified list contains a single element which is
179 * <code>null</code> or an empty or blank (only whitespace) string.
180 * </ol>
181 *
182 * This method is useful because some reconfigurations can cause a
183 * <code>TiesConfiguration</code> to return a single-element list containing
184 * only an empty string. Usually such a list should be treated as empty.
185 *
186 * @param list the list to check
187 * @return <code>true</code> iff the list is empty as described above
188 */
189 public static boolean listIsEmpty(final List list) {
190 final boolean result;
191
192 if ((list == null) || list.isEmpty()) {
193
194 result = true;
195 } else if (list.size() == 1) {
196
197 final Object first = list.get(0);
198
199 if (first == null) {
200
201 result = true;
202 } else if (first instanceof String) {
203
204 result = StringUtils.isBlank((String) first);
205 } else {
206 result = false;
207 }
208 } else {
209 result = false;
210 }
211
212 return result;
213 }
214
215 /***
216 * Utility method that thorougly checks whether a property (as returned
217 * by {@link Configuration#getProperty(String)} is empty. It returns
218 * <code>true</code> in two cases:
219 *
220 * <ol>
221 * <li>the specified object is <code>null</code>;
222 * <li>the specified object is an empty or blank (only whitespace) string.
223 * </ol>
224 *
225 * This method is useful because some reconfigurations can cause a
226 * <code>TiesConfiguration</code> to return an empty string.
227 *
228 * @param object the object to check
229 * @return <code>true</code> iff the object is empty as described above
230 */
231 public static boolean propertyIsEmpty(final Object object) {
232 final boolean result;
233
234 if (object == null) {
235
236 result = true;
237 } else if (object instanceof String) {
238
239 result = StringUtils.isBlank((String) object);
240 } else {
241 result = false;
242 }
243
244 return result;
245 }
246
247 /***
248 * Creates a full key by joining a prefix and a suffix string, separated by
249 * the property delimiter.
250 *
251 * @param prefix the prefix of the key
252 * @param suffix the suffix of the key
253 * @return the full key (prefix + delimiter + suffix)
254 */
255 public static String joinKey(final String prefix, final String suffix) {
256 return new ConfigurationKey(prefix).append(suffix).toString();
257 }
258
259
260 /***
261 * An inner class wrapping descriptor information on an entry: type
262 * of the entry, whether it is optional or a list, a description of the
263 * entry.
264 */
265 public final class EntryDescriptor {
266
267 /***
268 * The key (name) of this entry.
269 */
270 private final String key;
271
272 /***
273 * The base type of the entry (String, Integer etc.).
274 */
275 private final String type;
276
277 /***
278 * Whether the entry is optional.
279 */
280 private final boolean optional;
281
282 /***
283 * Whether the entry is a list (can contain multiple values).
284 */
285 private final boolean list;
286
287 /***
288 * A textual description of the entry.
289 */
290 private final String description;
291
292 /***
293 * Returns a textual description of the entry.
294 * @return the value of the attribute
295 */
296 public String getDescription() {
297 return description;
298 }
299
300 /***
301 * Creates a new instance.
302 *
303 * @param keyName the key (name) of this entry
304 * @param typ the base type of the entry
305 * @param isOpt whether the entry is optional
306 * @param isList whether the entry is a list
307 * @param desc a textual description of the entry
308 */
309 private EntryDescriptor(final String keyName, final String typ,
310 final boolean isOpt, final boolean isList, final String desc) {
311 key = keyName;
312 type = typ;
313 optional = isOpt;
314 list = isList;
315 description = desc;
316 }
317
318 /***
319 * Reads the value of this property as a string list and returns the
320 * element at the specified position.
321 *
322 * @param index the index of element to return
323 * @return the element at the specified position in this list; or
324 * <code>null</code> if the position does not exist in the list or
325 * no value exists for this key
326 * @throws IndexOutOfBoundsException if <code>index</code> < 0
327 */
328 public String getElement(final int index)
329 throws IndexOutOfBoundsException {
330
331 if (containsKey(key)) {
332 final List value = getList(key);
333
334
335 if (index < value.size()) {
336 return (String) value.get(index);
337 } else {
338 return null;
339 }
340 } else {
341 return null;
342 }
343 }
344
345 /***
346 * Reads the value of this property as a string list and returns a
347 * sublist starting at the specified position.
348 *
349 * @param fromIndex the index of the first element to return (inclusive)
350 * @return a sublist starting at the specified position in this list; or
351 * <code>null</code> if the position does not exist in the list or
352 * no value exists for this key
353 * @throws IndexOutOfBoundsException if <code>fromIndex</code> < 0
354 */
355 public List getElementsFrom(final int fromIndex)
356 throws IndexOutOfBoundsException {
357
358 if (containsKey(key)) {
359 final List value = getList(key);
360
361
362 if (fromIndex < value.size()) {
363 return value.subList(fromIndex, value.size());
364 } else {
365 return null;
366 }
367 } else {
368 return null;
369 }
370 }
371
372 /***
373 * Returns the key (name) of this entry.
374 * @return the value of the attribute
375 */
376 public String getKey() {
377 return key;
378 }
379
380 /***
381 * Returns the base type of the entry (String, Integer etc.).
382 * @return the value of the attribute
383 */
384 public String getType() {
385 return type;
386 }
387
388 /***
389 * Returns the value of this property (as an object).
390 * @return the value of this property; or <code>null</code> if no
391 * value is set
392 */
393 public Object getValue() {
394
395 return containsKey(key) ? getProperty(key) : null;
396 }
397
398 /***
399 * Whether the entry is a list (can contain multiple values).
400 * @return the value of the attribute
401 */
402 public boolean isList() {
403 return list;
404 }
405
406 /***
407 * Whether the entry is optional.
408 * @return the value of the attribute
409 */
410 public boolean isOptional() {
411 return optional;
412 }
413
414 /***
415 * Returns a string representation of this object.
416 *
417 * @return a textual representation
418 */
419 public String toString() {
420 return new ToStringBuilder(this)
421 .append("key", key)
422 .append("type", type)
423 .append("optional", optional)
424 .append("list", list)
425 .append("description", description)
426 .toString();
427 }
428
429 }
430
431
432 /***
433 * Describes the entries in this config. Each value must be a two-array,
434 * specifying the type of the entry and a description of the entry.
435 */
436 private final CompositeConfiguration descriptorConfig =
437 new CompositeConfiguration();
438
439
440 /***
441 * Creates a new empty instance.
442 */
443 public TiesConfiguration() {
444 super();
445 }
446
447 /***
448 * Creates a new instance, delegating to
449 * {@link #addConfiguration(Configuration, Configuration)}.
450 *
451 * @param config the initial configuration to wrap
452 * @param desc the initial descriptor config to wrap (cf.
453 * {@link #addDescriptorConfig(Configuration)} for the required contents)
454 */
455 public TiesConfiguration(final Configuration config,
456 final Configuration desc) {
457 this();
458 addConfiguration(config, desc);
459 }
460
461 /***
462 * Creates a new instance, delegating to {@link #load(String)}.
463 *
464 * @param baseName the base name of the configuration
465 */
466 public TiesConfiguration(final String baseName) {
467 this();
468 load(baseName);
469 }
470
471 /***
472 * Adds a configuration and a corresponding descriptor config.
473 *
474 * @param config the configuration to add
475 * @param desc the descriptor config to add (cf.
476 * {@link #addDescriptorConfig(Configuration)} for the required contents)
477 */
478 public void addConfiguration(final Configuration config,
479 final Configuration desc) {
480 addConfiguration(config);
481 addDescriptorConfig(desc);
482 }
483
484 /***
485 * Adds a descriptor configuration that can be consulted to query the
486 * type and use of a entries. Each value must be a two-array specifying
487 * the type (first element) and a description (second element) of the key
488 * as used in the main configuration. Append '?' to the type if it is
489 * optional (zero or one values), '*' if it is an optional list (zero or
490 * more values), or '+' if it is a required list (one or more values).
491 *
492 * @param desc the descriptor configuration to add
493 */
494 public void addDescriptorConfig(final Configuration desc) {
495 descriptorConfig.addConfiguration(desc);
496 }
497
498 /***
499 * Modifies configuration properties from <code>[+|-]key[=value]</code>
500 * pairs in a string array. Delegates to
501 * {@link #modifyProperty(String, boolean)} for each array element
502 * starting with "-" (using overwrite mode) or "+" (using append mode).
503 * Other array elements are collected and returned.
504 *
505 * @param args the array of strings to parse
506 * @return a list of all arguments that do not start with "+" or "-"
507 * @throws IllegalArgumentException if one of the
508 * <code>[+|-]key[=value]</code> pairs doesn't contain a key ("=" is second
509 * character)
510 */
511 public List<String> configureFromArgs(final String[] args)
512 throws IllegalArgumentException {
513 final List<String> otherArgs = new LinkedList<String>();
514 for (int i = 0; i < args.length; i++) {
515 if (args[i].startsWith("-")) {
516 modifyProperty(args[i].substring(1), true);
517 } else if (args[i].startsWith("+")) {
518 modifyProperty(args[i].substring(1), false);
519 } else {
520
521 otherArgs.add(args[i]);
522 }
523 }
524 return otherArgs;
525 }
526
527 /***
528 * Returns the descriptor for a given key, if any is given in the
529 * {@linkplain #addDescriptorConfig(Configuration) descriptor
530 * configuration}.
531 *
532 * @param key the key to describe
533 * @return a descriptor object for this key, or <code>null</code> is none
534 * is given
535 */
536 public EntryDescriptor getDescriptor(final String key) {
537 final EntryDescriptor result;
538 final String[] descEntry;
539 final String suffixDesc;
540
541 if (descriptorConfig.containsKey(key)) {
542 descEntry = descriptorConfig.getStringArray(key);
543 suffixDesc = null;
544 } else {
545
546
547 final int lastDelim = key.lastIndexOf(PROPERTY_DELIM);
548
549 if (lastDelim > 0) {
550 final String mainKey = key.substring(0, lastDelim);
551 final String suffix = key.substring(lastDelim);
552 descEntry = descriptorConfig.getStringArray(mainKey);
553 suffixDesc = descriptorConfig.getString(suffix, null);
554 } else {
555 descEntry = null;
556 suffixDesc = null;
557 }
558 }
559
560 if (descEntry != null) {
561 if (descEntry.length >= 2) {
562
563 final String firstElem = descEntry[0].trim();
564 final String type;
565 final boolean optional;
566 final boolean list;
567
568
569 if (firstElem.endsWith("?")) {
570 optional = true;
571 list = false;
572 type = StringUtils.chop(firstElem);
573 } else if (firstElem.endsWith("*")) {
574 optional = true;
575 list = true;
576 type = StringUtils.chop(firstElem);
577 } else if (firstElem.endsWith("+")) {
578 optional = false;
579 list = true;
580 type = StringUtils.chop(firstElem);
581 } else {
582 optional = false;
583 list = false;
584 type = firstElem;
585 }
586
587
588 String description = descEntry[1];
589
590
591 if (!StringUtils.isEmpty(suffixDesc)) {
592 description += " (" + suffixDesc + ")";
593 }
594
595 result =
596 new EntryDescriptor(key, type, optional, list, description);
597 } else {
598 result = null;
599 }
600
601
602 if ((descEntry.length == 1) || (descEntry.length > 2)) {
603 Util.LOG.error("Descriptor entry for " + key
604 + " has " + descEntry.length + " elements instead of 2");
605 }
606 } else {
607 result = null;
608 }
609
610 return result;
611 }
612
613 /***
614 * Read a property from this configuration. If the given key does not exist
615 * in this configuration, it is looked up as a
616 * {@linkplain System#getProperty(java.lang.String) Java system property}.
617 *
618 * @param key key to use for mapping
619 * @return object associated with the given configuration key
620 */
621 public Object getProperty(final String key) {
622 Object result = super.getProperty(key);
623
624
625 if (result == null) {
626 try {
627 result = System.getProperty(key);
628 } catch (SecurityException se) {
629
630 result = null;
631 }
632 }
633
634 return result;
635 }
636
637 /***
638 * Get an array of strings associated with the given configuration key.
639 *
640 * @param key The configuration key
641 * @return The associated string array if key is found.
642 */
643 public String[] getStringArray(final String key) {
644 final String[] result = super.getStringArray(key);
645
646 if ((result.length == 1) && (StringUtils.isBlank(result[0]))) {
647
648
649 return new String[0];
650 } else {
651 return result;
652 }
653 }
654
655 /***
656 * Copies all properties contained in this instance to a given
657 * configuration. This method can be used for storing this configuration in
658 * any format desired, by creating an empty configuration of the
659 * requested type and passing it to this method. Keys are sorted in
660 * alphabetic order.
661 *
662 * @param appender the configuration to append all properties to
663 */
664 public void flatten(final Configuration appender) {
665 final Iterator keys = sortedKeys().iterator();
666 String key;
667 Object value;
668
669 while (keys.hasNext()) {
670 key = (String) keys.next();
671 value = getProperty(key);
672 appender.addProperty(key, value);
673 }
674 }
675
676 /***
677 * Loads configuration in {@link PropertiesConfiguration} or
678 * {@linkplain org.apache.commons.configuration.XMLConfiguration XML}
679 * format. Combines configuration properties from several resources if
680 * they exist:
681 *
682 * <ol>
683 * <li>The files <code><em>baseName</em>.xml</code> resp.
684 * <code><em>baseName</em>.cfg</code> in the current working
685 * directory, if they exist and are readable files</li>
686 * <li>The file <code><em>baseName</em>.xml</code> resp.
687 * <code><em>baseName</em>.cfg</code> in the user's home
688 * directory, if they exist and are readable files</li>
689 * <li>The file <code><em>baseName</em>.cfg</code> in the <code>conf</code>
690 * subdirectory in the classpath (e.g. from a jar file) -- <strong>this
691 * classpath resource should always exist (otherwise an warning will be
692 * logged and later errors are likely)</strong></li>
693 * </ol>
694 *
695 * <p>For shared keys, the first match will be returned. If corresponding
696 * <code><em>baseName</em>.desc</code> exists, they are assumed to
697 * contain {@linkplain #addDescriptorConfig(Configuration) descriptors}.
698 *
699 * <p>*.cfg and *.desc files must must use the
700 * {@link PropertiesConfiguration} format, *.xml files must use the
701 * {@linkplain org.apache.commons.configuration.XMLConfiguration XML}
702 * format.
703 *
704 * <p>The last resource loaded form the classpath must use UTF-8 character
705 * set (otherwise it could not be shared between systems). The other
706 * *.cfg and *.desc (local files) are assumed to use the platform's default
707 * character set. The character set of XML files is determined in the
708 * standard way.
709 *
710 * @param baseName the base name of the configuration
711 */
712 public void load(final String baseName) {
713
714 final String[] dirCandidates = {
715 IOUtils.userDir(),
716 IOUtils.userHome()
717 };
718 File file;
719
720
721 for (int i = 0; i < dirCandidates.length; i++) {
722
723 file = new File(dirCandidates[i] + File.separator + baseName
724 + XML_EXTENSION);
725 try {
726 if (file.canRead() && file.isFile()) {
727 addConfiguration(new XMLConfiguration(file));
728 }
729
730
731 file = new File(dirCandidates[i] + File.separator + baseName
732 + CONF_EXTENSION);
733 if (file.canRead() && file.isFile()) {
734 addConfiguration(new PropertiesConfiguration(
735 file.getAbsolutePath()));
736 }
737
738
739 file = new File(dirCandidates[i] + File.separator + baseName
740 + DESC_EXTENSION);
741 if (file.canRead() && file.isFile()) {
742 addDescriptorConfig(new PropertiesConfiguration(
743 file.getAbsolutePath()));
744 }
745 } catch (Exception e) {
746 Util.LOG.error("Couldn't load configuarion file "
747 + file.getAbsolutePath() + ": " + e);
748 }
749 }
750
751
752
753 String classPathLocation
754 = CONF_DIR + '/' + baseName + CONF_EXTENSION;
755 final ClassLoader classLoader = this.getClass().getClassLoader();
756 InputStream in = classLoader.getResourceAsStream(classPathLocation);
757
758 try {
759 if (in != null) {
760 final PropertiesConfiguration defaults =
761 new PropertiesConfiguration();
762 defaults.load(in, IOUtils.STANDARD_UNICODE_CHARSET);
763 addConfiguration(defaults);
764 IOUtils.tryToClose(in);
765 } else {
766 Util.LOG.warn("Didn't find default configuration "
767 + classPathLocation + " in classpath");
768 }
769
770 classPathLocation = CONF_DIR + File.separator + baseName
771 + DESC_EXTENSION;
772 in = classLoader.getResourceAsStream(classPathLocation);
773
774 if (in != null) {
775 final PropertiesConfiguration defaultDesc =
776 new PropertiesConfiguration();
777 defaultDesc.load(in, IOUtils.STANDARD_UNICODE_CHARSET);
778 addDescriptorConfig(defaultDesc);
779 }
780 } catch (ConfigurationException ce) {
781 Util.LOG.error("Couldn't load default configuration "
782 + classPathLocation + " from classpath: " + ce);
783 } finally {
784 IOUtils.tryToClose(in);
785 }
786
787 }
788
789 /***
790 * Creates a localized (language-specific) key by joining a base name
791 * with the configured language suffix (value mapped to the
792 * {@link #CONFIG_LANG}} key (if this key doesn't exist, the language of
793 * default locale used by the Java Virtual Machine is used).
794 * If the localized form of the key doesn't exist in the specified
795 * configuration, the base form is returned instead. Implemented by
796 * delegating to {@link #adaptKey(String, String)}.
797 *
798 * @param baseKey the basic key
799 * @return the full key, localized by joining with the {@link #CONFIG_LANG}
800 * key
801 */
802 public String localizeKey(final String baseKey) {
803 final String lang = getString(CONFIG_LANG,
804 Locale.getDefault().getLanguage());
805 return adaptKey(baseKey, lang);
806 }
807
808 /***
809 * Modifies a configuration property, parsing a <code>key[=value]</code>
810 * pair. Both key and value are trimmed. {@link Boolean#TRUE} is used as
811 * default value if the <code>=value</code> part is omitted.
812 * If "=" is the last character (empty value) and <code>overwrite</code> is
813 * <code>true</code>, the property is removed from the configuration
814 * ({@link Configuration#clearProperty(java.lang.String)}).
815 *
816 * @param keyValue the <code>key=value</code> pair to parse
817 * @param overwrite whether to overwrite (replacing old value: {@link
818 * Configuration#setProperty(java.lang.String, java.lang.Object)}) or
819 * add (append to list, {@link
820 * Configuration#addProperty(java.lang.String, java.lang.Object)}) the
821 * value
822 * @throws IllegalArgumentException if <code>keyValue</code> doesn't contain
823 * a key ("=" is first character)
824 */
825 public void modifyProperty(final String keyValue,
826 final boolean overwrite) throws IllegalArgumentException {
827
828 final String key;
829 final Object value;
830 final int equalIndex = keyValue.indexOf('=');
831
832 if (equalIndex > 0) {
833 key = keyValue.substring(0, equalIndex).trim();
834 value = keyValue.substring(equalIndex + 1).trim();
835 } else if (equalIndex < 0) {
836
837 key = keyValue.trim();
838
839 value = "true";
840 } else {
841
842 throw new IllegalArgumentException("Missing key: " + keyValue);
843 }
844
845 if (overwrite) {
846
847 if ("".equals(value)) {
848 clearProperty(key);
849 Util.LOG.debug("Cleared property " + key);
850 } else {
851 setProperty(key, value);
852 Util.LOG.debug("Set property " + key + " = " + value);
853 }
854 } else {
855 addProperty(key, value);
856 Util.LOG.debug("Added to property " + key + ": " + value);
857 }
858 }
859
860 /***
861 * Saves the contents of this configuration in a file, storing them in
862 * {@link PropertiesConfiguration} format. The platform's default
863 * character set is used.
864 *
865 * @param file the file to use
866 * @throws ConfigurationException if an I/O error occurs during saving
867 */
868 public void save(final File file) throws ConfigurationException {
869
870 save(file.getAbsolutePath());
871 }
872
873 /***
874 * Saves the contents of this configuration in a file, storing them in
875 * {@link PropertiesConfiguration} format. The platform's default
876 * character set is used.
877 *
878 * @param filename the name of the file
879 * @throws ConfigurationException if an I/O error occurs during saving
880 */
881 public void save(final String filename) throws ConfigurationException {
882 final PropertiesConfiguration outConf = new PropertiesConfiguration();
883 flatten(outConf);
884 outConf.save(filename);
885 }
886
887 /***
888 * Returns the list of keys contained in this configuration, sorted in
889 * alphabetic order. Delegates to {@link #sortedKeys(boolean)}, setting
890 * <code>inclNotSet</code> to <code>false</code>.
891 *
892 * @return an immutable set containing the sorted list of keys
893 */
894 public SortedSet sortedKeys() {
895 return sortedKeys(false);
896 }
897
898 /***
899 * Returns the list of keys contained in this configuration, sorted in
900 * alphabetic order.
901 *
902 * @param inclNotSet if <code>true</code>, keys that are not contained in
903 * this configuration but that described in the
904 * {@linkplain #addDescriptorConfig(Configuration) descriptor configuration}
905 * are also included
906 * @return an immutable set containing the sorted list of keys
907 */
908 public SortedSet<Object> sortedKeys(final boolean inclNotSet) {
909 final SortedSet<Object> result = new TreeSet<Object>();
910 final Iterator keys = getKeys();
911
912 while (keys.hasNext()) {
913 result.add(keys.next());
914 }
915
916 if (inclNotSet) {
917
918 final Iterator descKeys = descriptorConfig.getKeys();
919 String key;
920
921 while (descKeys.hasNext()) {
922 key = (String) descKeys.next();
923
924
925
926
927 if (!containsKey(key) && super.subset(key).isEmpty()
928 && (key.indexOf(PROPERTY_DELIM) != 0)) {
929 result.add(key);
930 }
931 }
932 }
933
934 return Collections.unmodifiableSortedSet(result);
935 }
936
937 /***
938 * Create an Configuration object that is a subset of this one. The new
939 * Configuration object contains every key from the current Configuration
940 * that starts with prefix. The prefix is removed from the keys in the
941 * subset.
942 *
943 * @param prefix The prefix used to select the properties
944 * @return a subset of this configuration; the returned object will be
945 * a {@link TiesConfiguration} instance
946 */
947 public Configuration subset(final String prefix) {
948 return new TiesConfiguration(super.subset(prefix),
949 descriptorConfig.subset(prefix));
950 }
951
952 }