View Javadoc

1   /*
2    * Copyright (c) 2010, Marco Brade
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions are met:
7    *
8    * * Redistributions of source code must retain the above copyright notice, this
9    *   list of conditions and the following disclaimer.
10   *
11   * * Redistributions in binary form must reproduce the above copyright notice,
12   *   this list of conditions and the following disclaimer in the documentation
13   *   and/or other materials provided with the distribution.
14   *
15   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25   */
26  package net.sf.prefixedproperties;
27  
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.ObjectInputStream;
31  import java.io.ObjectOutputStream;
32  import java.io.OutputStream;
33  import java.io.OutputStreamWriter;
34  import java.io.PrintStream;
35  import java.io.PrintWriter;
36  import java.io.Reader;
37  import java.io.Serializable;
38  import java.io.Writer;
39  import java.nio.charset.Charset;
40  import java.util.ArrayList;
41  import java.util.Arrays;
42  import java.util.Collection;
43  import java.util.Collections;
44  import java.util.Enumeration;
45  import java.util.HashMap;
46  import java.util.HashSet;
47  import java.util.Iterator;
48  import java.util.LinkedList;
49  import java.util.List;
50  import java.util.Map;
51  import java.util.Map.Entry;
52  import java.util.Properties;
53  import java.util.Set;
54  import java.util.TreeMap;
55  import java.util.concurrent.locks.ReentrantReadWriteLock;
56  
57  import org.apache.commons.lang.StringUtils;
58  
59  import com.fasterxml.jackson.core.JsonEncoding;
60  import com.fasterxml.jackson.core.JsonFactory;
61  import com.fasterxml.jackson.core.JsonGenerator;
62  import com.fasterxml.jackson.core.JsonParser;
63  import com.fasterxml.jackson.core.JsonToken;
64  import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
65  import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
66  import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
67  
68  import net.sf.prefixedproperties.config.DefaultPrefixConfig;
69  import net.sf.prefixedproperties.config.DynamicPrefixConfig;
70  import net.sf.prefixedproperties.config.PrefixConfig;
71  import net.sf.triemap.TrieMap;
72  import net.sf.triemap.TrieMap.TrieMapBackedProperties;
73  
74  /**
75   * PrefixedProperties can be used to filter properties by an environment or
76   * key-prefix. The environment itself can be configured within this Properties
77   * by using {@link #setDefaultPrefix(String)} which is for a global level and
78   * also {@link #setLocalPrefix(String)} which is used on a {@link ThreadLocal}
79   * basis.<br>
80   * So it's possible to have a global prefix for everything. And a more specific
81   * or totally different prefix on a thread-dependend basis.<br>
82   * 
83   */
84  public class PrefixedProperties extends Properties implements Serializable {
85  
86  	private class DoubleEntry<T, P> {
87  		private final T one;
88  		private final P two;
89  
90  		private DoubleEntry(final T one, final P two) {
91  			this.one = one;
92  			this.two = two;
93  		}
94  
95  		public T getOne() {
96  			return one;
97  		}
98  
99  		public P getTwo() {
100 			return two;
101 		}
102 
103 		@Override
104 		public String toString() {
105 			final StringBuilder sb = new StringBuilder("One:").append(one).append(" Two: ").append(two);
106 			return sb.toString();
107 		}
108 
109 	}
110 
111 	/*
112 	 * The Class EmptyPrefix.
113 	 */
114 	private static class EmptyPrefix extends DefaultPrefixConfig {
115 
116 		/** The Constant serialVersionUID. */
117 		private final static long serialVersionUID = 1L;
118 
119 		/** The Constant INSTANCE. */
120 		private final static EmptyPrefix INSTANCE = new EmptyPrefix();
121 
122 		/*
123 		 * (non-Javadoc)
124 		 * 
125 		 * @see net.sf.prefixedproperties.config.AbstractPrefixConfig#getClone()
126 		 */
127 		@Override
128 		public final PrefixConfig clone() {
129 			return EmptyPrefix.INSTANCE;
130 		}
131 	}
132 
133 	/*
134 	 * The Class PPEntry.
135 	 */
136 	private final class PPEntry implements Map.Entry<Object, Object> {
137 
138 		/* The key. */
139 		private final Object key;
140 
141 		/* The value. */
142 		private final Object value;
143 
144 		/*
145 		 * Instantiates a new pP entry.
146 		 *
147 		 * @param key the key
148 		 * 
149 		 * @param value the value
150 		 */
151 		private PPEntry(final Object aKey, final Object aValue) {
152 			key = aKey;
153 			value = aValue;
154 		}
155 
156 		/*
157 		 * (non-Javadoc)
158 		 * 
159 		 * @see java.util.Map.Entry#getKey()
160 		 */
161 		@Override
162 		public Object getKey() {
163 			try {
164 				lock.readLock().lock();
165 				return getUnprefixedKey(key);
166 			} finally {
167 				lock.readLock().unlock();
168 			}
169 		}
170 
171 		/*
172 		 * (non-Javadoc)
173 		 * 
174 		 * @see java.util.Map.Entry#getValue()
175 		 */
176 		@Override
177 		public Object getValue() {
178 			return value;
179 		}
180 
181 		/*
182 		 * (non-Javadoc)
183 		 * 
184 		 * @see java.util.Map.Entry#setValue(java.lang.Object)
185 		 */
186 		@Override
187 		public Object setValue(final Object aValue) {
188 			return put(key, aValue);
189 		}
190 
191 		@Override
192 		public String toString() {
193 			final StringBuilder builder = new StringBuilder();
194 			builder.append("[key=").append(key).append(", value=").append(value).append("]");
195 			return builder.toString();
196 		}
197 
198 	}
199 
200 	/*
201 	 * The Class PrefixedPropertiesEnumerationImpl.
202 	 *
203 	 * @param <E> the element type
204 	 */
205 	private final class PrefixedPropertiesEnumerationImpl<E> implements PrefixedPropertiesEnumeration<E> {
206 
207 		/** The it. */
208 		private Iterator<E> it;
209 
210 		/** The last. */
211 		private E last;
212 
213 		/** The is key. */
214 		private boolean isKey;
215 
216 		/*
217 		 * Instantiates a new prefixed properties enumeration impl.
218 		 *
219 		 * @param it the it
220 		 */
221 		private PrefixedPropertiesEnumerationImpl(final Iterator<E> iterator) {
222 			this(iterator, false);
223 		}
224 
225 		/*
226 		 * Instantiates a new prefixed properties enumeration impl.
227 		 *
228 		 * @param it the it
229 		 * 
230 		 * @param isKey the is key
231 		 */
232 		private PrefixedPropertiesEnumerationImpl(final Iterator<E> iterator, final boolean isKeyParam) {
233 			it = iterator;
234 			isKey = isKeyParam;
235 		}
236 
237 		/*
238 		 * (non-Javadoc)
239 		 * 
240 		 * @see java.util.Enumeration#hasMoreElements()
241 		 */
242 		@Override
243 		public boolean hasMoreElements() {
244 			return hasNext();
245 		}
246 
247 		/*
248 		 * (non-Javadoc)
249 		 * 
250 		 * @see java.util.Iterator#hasNext()
251 		 */
252 		@Override
253 		public boolean hasNext() {
254 			return it.hasNext();
255 		}
256 
257 		/*
258 		 * (non-Javadoc)
259 		 * 
260 		 * @see java.lang.Iterable#iterator()
261 		 */
262 		@Override
263 		public Iterator<E> iterator() {
264 			return this;
265 		}
266 
267 		/*
268 		 * (non-Javadoc)
269 		 * 
270 		 * @see java.util.Iterator#next()
271 		 */
272 		@Override
273 		public E next() {
274 			last = it.next();
275 			return last;
276 		}
277 
278 		/*
279 		 * (non-Javadoc)
280 		 * 
281 		 * @see java.util.Enumeration#nextElement()
282 		 */
283 		@Override
284 		public E nextElement() {
285 			return next();
286 		}
287 
288 		/*
289 		 * (non-Javadoc)
290 		 * 
291 		 * @see java.util.Iterator#remove()
292 		 */
293 		@Override
294 		public void remove() {
295 			if (isKey) {
296 				PrefixedProperties.this.remove(last);
297 			}
298 		}
299 
300 	}
301 
302 	/**
303 	 * Creates the cascading prefix properties.
304 	 * 
305 	 * @param configs
306 	 *            the configs
307 	 * @return the prefixed properties
308 	 */
309 	public static PrefixedProperties createCascadingPrefixProperties(final List<PrefixConfig> configs) {
310 		PrefixedProperties properties = null;
311 		for (final PrefixConfig config : configs) {
312 			if (properties == null) {
313 				properties = new PrefixedProperties((config == null) ? new DynamicPrefixConfig() : config);
314 			} else {
315 				properties = new PrefixedProperties(properties, (config == null) ? new DynamicPrefixConfig() : config);
316 			}
317 		}
318 		return properties;
319 	}
320 
321 	/**
322 	 * Creates the cascading prefix properties.
323 	 * 
324 	 * @param configs
325 	 *            the configs
326 	 * @return the prefixed properties
327 	 */
328 	public static PrefixedProperties createCascadingPrefixProperties(final PrefixConfig... configs) {
329 		PrefixedProperties properties = null;
330 		for (final PrefixConfig config : configs) {
331 			if (properties == null) {
332 				properties = new PrefixedProperties((config == null) ? new DynamicPrefixConfig() : config);
333 			} else {
334 				properties = new PrefixedProperties(properties, (config == null) ? new DynamicPrefixConfig() : config);
335 			}
336 		}
337 		return properties;
338 	}
339 
340 	/**
341 	 * Creates the cascading prefix properties. This method parses the given
342 	 * prefixString and creates for each delimiter part a PrefixedProperties.
343 	 * 
344 	 * @param prefixString
345 	 *            the prefixes
346 	 * @return the prefixed properties
347 	 */
348 	public static PrefixedProperties createCascadingPrefixProperties(final String prefixString) {
349 		return prefixString.indexOf(PrefixConfig.PREFIXDELIMITER) != -1
350 				? createCascadingPrefixProperties(prefixString.split("\\" + PrefixConfig.PREFIXDELIMITER))
351 				: createCascadingPrefixProperties(new String[] { prefixString });
352 
353 	}
354 
355 	/**
356 	 * Creates the cascading prefix properties by using the given Prefixes.
357 	 * 
358 	 * @param prefixes
359 	 *            the prefixes
360 	 * @return the prefixed properties
361 	 */
362 	public static PrefixedProperties createCascadingPrefixProperties(final String[] prefixes) {
363 		PrefixedProperties properties = null;
364 		for (final String aPrefix : prefixes) {
365 			if (properties == null) {
366 				properties = new PrefixedProperties(aPrefix);
367 			} else {
368 				properties = new PrefixedProperties(properties, aPrefix);
369 			}
370 		}
371 		return properties;
372 	}
373 
374 	private static final long serialVersionUID = 1L;
375 
376 	private transient ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
377 
378 	private PrefixConfig prefixes = new DynamicPrefixConfig();
379 
380 	private Properties properties = new TrieMap<Object>().asProperties();
381 
382 	private boolean mixDefaultAndLocalPrefixes = true;
383 
384 	/**
385 	 * Instantiates a new prefixed properties.
386 	 */
387 	public PrefixedProperties() {
388 	}
389 
390 	/**
391 	 * Instantiates a new prefixed properties.
392 	 * 
393 	 * @param config
394 	 *            the config
395 	 */
396 	public PrefixedProperties(final PrefixConfig config) {
397 		setPrefixConfig(config);
398 	}
399 
400 	/**
401 	 * Instantiates a new prefixed properties.
402 	 * 
403 	 * @param config
404 	 *            the PrefixConfig
405 	 * @param defaults
406 	 *            the defaults
407 	 */
408 	public PrefixedProperties(final PrefixConfig config, final Properties defaults) {
409 		this(defaults);
410 		setPrefixConfig(config);
411 	}
412 
413 	/**
414 	 * Instantiates a new prefixed properties.
415 	 * 
416 	 * @param props
417 	 *            the properties to be inserted.
418 	 * @param config
419 	 *            the config to be used for this PrefixedProperties
420 	 */
421 	public PrefixedProperties(final PrefixedProperties props, final PrefixConfig config) {
422 		properties = props;
423 		setPrefixConfig(config);
424 	}
425 
426 	/**
427 	 * Instantiates a new prefixed properties.
428 	 * 
429 	 * @param props the props
430 	 * @param defaultPrefix the default prefix
431 	 */
432 	public PrefixedProperties(final PrefixedProperties props, final String defaultPrefix) {
433 		properties = props;
434 		setDefaultPrefix(defaultPrefix);
435 	}
436 
437 	/**
438 	 * Instantiates a new prefixed properties.
439 	 * 
440 	 * @param defaults
441 	 *            the defaults
442 	 */
443 	public PrefixedProperties(final Properties defaults) {
444 		properties = defaults instanceof PrefixedProperties ? defaults : new TrieMap<Object>().asProperties(defaults);
445 	}
446 
447 	/**
448 	 * Instantiates a new prefixed properties.
449 	 * 
450 	 * @param defaultPrefix
451 	 *            the default prefix
452 	 */
453 	public PrefixedProperties(final String defaultPrefix) {
454 		setDefaultPrefix(defaultPrefix);
455 	}
456 
457 	private String checkAndConvertPrefix(final String prefix) {
458 		if (prefix == null) {
459 			throw new IllegalArgumentException("The prefix has to be set and is not allowed to be null.");
460 		}
461 		String myPrefix = prefix;
462 		if ("*".equals(prefix)) {
463 			myPrefix = StringUtils.repeat(PrefixConfig.PREFIXDELIMITER_STRING, getPrefixConfigs().size());
464 		}
465 		return myPrefix;
466 	}
467 
468 	/*
469 	 * (non-Javadoc)
470 	 * 
471 	 * @see java.util.Hashtable#clear()
472 	 */
473 	@Override
474 	public void clear() {
475 		lock.writeLock().lock();
476 		try {
477 			properties.clear();
478 		} finally {
479 			lock.writeLock().unlock();
480 		}
481 	}
482 
483 	/**
484 	 * Removes all default prefixes from the {@link PrefixConfig}s
485 	 */
486 	public void clearDefaultPrefixes() {
487 		final Map<Integer, PrefixConfig> prefixConfigs = getPrefixConfigs();
488 		for (final PrefixConfig config : prefixConfigs.values()) {
489 			config.setDefaultPrefix(null);
490 		}
491 	}
492 
493 	/**
494 	 * Removes all local prefixes from the {@link PrefixConfig}s
495 	 */
496 	public void clearLocalPrefixes() {
497 		final Map<Integer, PrefixConfig> prefixConfigs = getPrefixConfigs();
498 		for (final PrefixConfig config : prefixConfigs.values()) {
499 			config.setPrefix(null);
500 		}
501 	}
502 
503 	/*
504 	 * (non-Javadoc)
505 	 * 
506 	 * @see java.util.Hashtable#clone()
507 	 */
508 	@SuppressWarnings("rawtypes")
509 	@Override
510 	public PrefixedProperties clone() {
511 		lock.readLock().lock();
512 		try {
513 			final PrefixedProperties clone = (PrefixedProperties) super.clone();
514 			if (prefixes != null) {
515 				clone.prefixes = prefixes.clone();
516 			}
517 			if (properties instanceof PrefixedProperties) {
518 				clone.properties = (Properties) properties.clone();
519 			} else if (properties instanceof TrieMapBackedProperties) {
520 				clone.properties = ((TrieMapBackedProperties) properties).clone();
521 			} else {
522 				clone.properties = new TrieMap<Object>().asProperties();
523 				clone.properties.putAll(properties);
524 			}
525 			return clone;
526 		} finally {
527 			lock.readLock().unlock();
528 		}
529 	}
530 
531 	private void configureJsonParser(final JsonParser jp) {
532 		jp.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
533 		jp.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
534 		jp.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
535 		jp.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
536 	}
537 
538 	/*
539 	 * (non-Javadoc)
540 	 * 
541 	 * @see java.util.Hashtable#contains(java.lang.Object)
542 	 */
543 	@Override
544 	public boolean contains(final Object value) {
545 		lock.readLock().lock();
546 		try {
547 			if (value == null) {
548 				return false;
549 			}
550 			for (@SuppressWarnings("rawtypes")
551 			final Map.Entry entry : entrySet()) {
552 				final Object otherValue = entry.getValue();
553 				if (otherValue != null && otherValue.equals(value)) {
554 					return true;
555 				}
556 			}
557 			return false;
558 		} finally {
559 			lock.readLock().unlock();
560 		}
561 	}
562 
563 	/*
564 	 * (non-Javadoc)
565 	 * 
566 	 * @see java.util.Hashtable#containsKey(java.lang.Object)
567 	 */
568 	@Override
569 	public boolean containsKey(final Object key) {
570 		lock.readLock().lock();
571 		try {
572 			if (isKeyValid(key)) {
573 				final boolean useLocalPrefixes = !mixDefaultAndLocalPrefixes && hasLocalPrefixConfigurations();
574 				if (!properties.containsKey(getPrefixedKey(key, useLocalPrefixes))) {
575 					return properties.containsKey(key);
576 				} else {
577 					return true;
578 				}
579 			}
580 			return false;
581 		} finally {
582 			lock.readLock().unlock();
583 		}
584 	}
585 
586 	private boolean containsValidPrefix(final Object key) {
587 		if (key != null && String.class == key.getClass()) {
588 			return prefixes.containsValidPrefix((String) key);
589 		}
590 		return false;
591 	}
592 
593 	/*
594 	 * (non-Javadoc)
595 	 * 
596 	 * @see java.util.Hashtable#containsValue(java.lang.Object)
597 	 */
598 	@Override
599 	public boolean containsValue(final Object value) {
600 		lock.readLock().lock();
601 		try {
602 			return contains(value);
603 		} finally {
604 			lock.readLock().unlock();
605 		}
606 	}
607 
608 	/*
609 	 * (non-Javadoc)
610 	 * 
611 	 * @see java.util.Hashtable#elements()
612 	 */
613 	@SuppressWarnings({ "rawtypes", "unchecked" })
614 	@Override
615 	public PrefixedPropertiesEnumeration<Object> elements() {
616 		lock.readLock().lock();
617 		try {
618 			final Collection values = values();
619 			final Iterator it = values.iterator();
620 			return new PrefixedPropertiesEnumerationImpl(it);
621 		} finally {
622 			lock.readLock().unlock();
623 		}
624 	}
625 
626 	/*
627 	 * (non-Javadoc)
628 	 * 
629 	 * @see java.util.Hashtable#entrySet()
630 	 */
631 	@Override
632 	public Set<Entry<Object, Object>> entrySet() {
633 		final Set<Entry<Object, Object>> entrySet = new HashSet<Entry<Object, Object>>();
634 		lock.readLock().lock();
635 		try {
636 			final boolean useLocalPrefixes = !mixDefaultAndLocalPrefixes && hasLocalPrefixConfigurations();
637 			for (final Map.Entry<Object, Object> keyEntry : getKeyMap(false).entrySet()) {
638 				final Object value = get(keyEntry.getValue(), useLocalPrefixes);
639 				if (String.class == keyEntry.getKey().getClass()) {
640 					entrySet.add(new PPEntry(keyEntry.getKey(), value));
641 				} else {
642 					entrySet.add(new PPEntry(keyEntry.getKey(), value));
643 				}
644 			}
645 			return entrySet;
646 		} finally {
647 			lock.readLock().unlock();
648 		}
649 	}
650 
651 	/*
652 	 * (non-Javadoc)
653 	 * 
654 	 * @see java.util.Hashtable#equals(java.lang.Object)
655 	 */
656 	@Override
657 	public boolean equals(final Object obj) {
658 		if (this == obj) {
659 			return true;
660 		}
661 		if (!super.equals(obj)) {
662 			return false;
663 		}
664 		if (getClass() != obj.getClass()) {
665 			return false;
666 		}
667 		final PrefixedProperties other = (PrefixedProperties) obj;
668 		if (prefixes == null) {
669 			if (other.prefixes != null) {
670 				return false;
671 			}
672 		} else if (!prefixes.equals(other.prefixes)) {
673 			return false;
674 		}
675 		if (properties == null) {
676 			if (other.properties != null) {
677 				return false;
678 			}
679 		} else if (!properties.equals(other.properties)) {
680 			return false;
681 		}
682 		return true;
683 	}
684 
685 	/*
686 	 * (non-Javadoc)
687 	 * 
688 	 * @see java.util.Hashtable#get(java.lang.Object)
689 	 */
690 	@Override
691 	public Object get(final Object key) {
692 		final boolean useLocalPrefixes = !mixDefaultAndLocalPrefixes && hasLocalPrefixConfigurations();
693 		return get(key, useLocalPrefixes);
694 	}
695 
696 	/*
697 	 * (non-Javadoc)
698 	 * 
699 	 * @see java.util.Hashtable#get(java.lang.Object)
700 	 */
701 	protected Object get(final Object key, final boolean useLocalPrefixes) {
702 		Object result = null;
703 		lock.readLock().lock();
704 		try {
705 			final Object prefixedKey = getPrefixedKey(key, useLocalPrefixes);
706 			result = (properties instanceof PrefixedProperties)
707 					? ((PrefixedProperties) properties).get(prefixedKey, useLocalPrefixes)
708 					: properties.get(prefixedKey);
709 			// fall back by getting the property without any prefixed keys
710 			if (result == null) {
711 				result = (properties instanceof PrefixedProperties)
712 						? ((PrefixedProperties) properties).get(key, useLocalPrefixes) : properties.get(key);
713 			}
714 			return result;
715 		} finally {
716 			lock.readLock().unlock();
717 		}
718 	}
719 
720 	/**
721 	 * Gets the prefixed key and parse it to an String[]<br>
722 	 * Each comma-separated list can be used.
723 	 * 
724 	 * @param key
725 	 *            the key
726 	 * 
727 	 * @return String[] or null if the key couldn't get found.
728 	 */
729 	public String[] getArray(final String key) {
730 		final String value = getProperty(key);
731 		if (value != null) {
732 			final String[] strings = value.split(",[\\s]*|[\\s]*$");
733 			return strings;
734 		}
735 		return null;
736 	}
737 
738 	/**
739 	 * Gets the prefixed key and parse it to an String[]<br>
740 	 * Each comma-separated list can be used. If the key couldn't get found, the
741 	 * default will be used.
742 	 * 
743 	 * @param key
744 	 *            the key
745 	 * @param def
746 	 *            default value
747 	 * 
748 	 * @return String[]
749 	 */
750 	public String[] getArray(final String key, final String[] def) {
751 		final String[] value = getArray(key);
752 		if (value != null) {
753 			return value;
754 		}
755 		return def;
756 	}
757 
758 	/**
759 	 * Gets the prefixed key and parse it to an boolean-value. If the key was
760 	 * not found it will always return false.
761 	 * 
762 	 * @param key
763 	 *            key value
764 	 * 
765 	 * @return boolean-representation of value
766 	 */
767 	public boolean getBoolean(final String key) {
768 		return Boolean.valueOf(getProperty(key)).booleanValue();
769 	}
770 
771 	/**
772 	 * Gets the prefixed key and parse it to an boolean-value.
773 	 * 
774 	 * @param key
775 	 *            key value
776 	 * @param def
777 	 *            default value
778 	 * 
779 	 * @return boolean-representation of value
780 	 */
781 	public boolean getBoolean(final String key, final boolean def) {
782 		final String value = getProperty(key);
783 		return value != null ? Boolean.valueOf(value).booleanValue() : def;
784 	}
785 
786 	/**
787 	 * Gets the prefixed key and parse it to an boolean[]<br>
788 	 * Each comma-separated list can be used.
789 	 * 
790 	 * @param key
791 	 *            the key
792 	 * @return boolean[] or null if the key couldn't get found.
793 	 */
794 	public boolean[] getBooleanArray(final String key) {
795 		final String[] value = getArray(key);
796 		if (value != null) {
797 			final boolean[] result = new boolean[value.length];
798 			for (int i = 0; i < value.length; i++) {
799 				result[i] = Boolean.valueOf(value[i]).booleanValue();
800 			}
801 			return result;
802 		}
803 		return null;
804 	}
805 
806 	/**
807 	 * Gets the prefixed key and parse it to an boolean[]<br>
808 	 * Each comma-separated list can be used. If the key couldn't get found, the
809 	 * default will be used.
810 	 * 
811 	 * @param key
812 	 *            the key
813 	 * @param def
814 	 *            default value
815 	 * 
816 	 * @return boolean[]
817 	 */
818 	public boolean[] getBooleanArray(final String key, final boolean[] def) {
819 		final boolean[] result = getBooleanArray(key);
820 		return result == null ? def : result;
821 	}
822 
823 	/**
824 	 * Gets the prefixed key and parse it to an byte-value.
825 	 * 
826 	 * @param key
827 	 *            key value
828 	 * @return byte-representation of value
829 	 */
830 	public byte getByte(final String key) {
831 		final String value = getProperty(key);
832 		if (value == null) {
833 			throw new NumberFormatException("Couldn't parse property to byte.");
834 		}
835 		return Byte.parseByte(value);
836 	}
837 
838 	/**
839 	 * Gets the prefixed key and parse it to an byte-value.
840 	 * 
841 	 * @param key
842 	 *            key value
843 	 * @param def
844 	 *            default value
845 	 * 
846 	 * @return byte-representation of value
847 	 */
848 	public byte getByte(final String key, final byte def) {
849 		try {
850 			return getByte(key);
851 		} catch (final NumberFormatException nfe) {
852 			return def;
853 		}
854 	}
855 
856 	/**
857 	 * Gets the byte array.
858 	 * 
859 	 * @param key
860 	 *            the key
861 	 * @return the byte array
862 	 */
863 	public byte[] getByteArray(final String key) {
864 		final String[] value = getArray(key);
865 		if (value != null) {
866 			final byte[] result = new byte[value.length];
867 			for (int i = 0; i < value.length; i++) {
868 				result[i] = Byte.parseByte(value[i]);
869 			}
870 			return result;
871 		}
872 		return null;
873 	}
874 
875 	/**
876 	 * Gets the byte array.
877 	 * 
878 	 * @param key
879 	 *            the key
880 	 * @param def
881 	 *            the def
882 	 * @return the byte array
883 	 */
884 	public byte[] getByteArray(final String key, final byte[] def) {
885 		try {
886 			final byte[] result = getByteArray(key);
887 			return result != null ? result : def;
888 		} catch (final NumberFormatException nfe) {
889 			return def;
890 		}
891 	}
892 
893 	/**
894 	 * Gets the complete properties.
895 	 * 
896 	 * @return the complete properties
897 	 */
898 	protected Properties getCompleteProperties() {
899 		if (properties instanceof PrefixedProperties) {
900 			return ((PrefixedProperties) properties).getCompleteProperties();
901 		} else {
902 			return properties;
903 		}
904 	}
905 
906 	/**
907 	 * Gets the prefixed key and parse it to an double-value.
908 	 * 
909 	 * @param key
910 	 *            key value
911 	 * @return double-representation of value
912 	 */
913 	public double getDouble(final String key) {
914 		final String value = getProperty(key);
915 		if (value == null) {
916 			throw new NumberFormatException("Couldn't parse property to double.");
917 		}
918 		return Double.parseDouble(value);
919 	}
920 
921 	/**
922 	 * Gets the prefixed key and parse it to an double-value.
923 	 * 
924 	 * @param key
925 	 *            key value
926 	 * @param def
927 	 *            default value
928 	 * 
929 	 * @return double-representation of value
930 	 */
931 	public double getDouble(final String key, final double def) {
932 		try {
933 			return getDouble(key);
934 		} catch (final NumberFormatException nfe) {
935 			return def;
936 		}
937 	}
938 
939 	/**
940 	 * Gets the prefixed key and parse it to an double[]<br>
941 	 * Each comma-separated list can be used.
942 	 * 
943 	 * @param key
944 	 *            the key
945 	 * @return double[] or null if the key couldn't get found.
946 	 */
947 	public double[] getDoubleArray(final String key) {
948 		final String[] value = getArray(key);
949 		if (value != null) {
950 			final double[] result = new double[value.length];
951 			for (int i = 0; i < value.length; i++) {
952 				result[i] = Double.parseDouble(value[i]);
953 			}
954 			return result;
955 		}
956 		return null;
957 	}
958 
959 	/**
960 	 * Gets the prefixed key and parse it to an double[]<br>
961 	 * Each comma-separated list can be used. If the key couldn't get found, the
962 	 * default will be used.
963 	 * 
964 	 * @param key
965 	 *            the key
966 	 * @param def
967 	 *            default value
968 	 * 
969 	 * @return double[]
970 	 */
971 	public double[] getDoubleArray(final String key, final double[] def) {
972 		try {
973 			final double[] result = getDoubleArray(key);
974 			return result != null ? result : def;
975 		} catch (final NumberFormatException nfe) {
976 			return def;
977 		}
978 	}
979 
980 	/**
981 	 * Gets the effective prefix.
982 	 * 
983 	 * @return the prefix
984 	 */
985 	public String getEffectivePrefix() {
986 		lock.readLock().lock();
987 		try {
988 			final boolean useLocalPrefixes = !mixDefaultAndLocalPrefixes && hasLocalPrefixConfigurations();
989 			return getPrefix(new StringBuilder(), useLocalPrefixes).toString();
990 		} finally {
991 			lock.readLock().unlock();
992 		}
993 	}
994 
995 	/**
996 	 * Gets the prefixed key and parse it to an float-value.
997 	 * 
998 	 * @param key
999 	 *            key value
1000 	 * @return float-representation of value
1001 	 */
1002 	public float getFloat(final String key) {
1003 		final String value = getProperty(key);
1004 		if (value == null) {
1005 			throw new NumberFormatException("Couldn't parse property to float.");
1006 		}
1007 		return Float.parseFloat(value);
1008 	}
1009 
1010 	/**
1011 	 * Gets the prefixed key and parse it to an float-value.
1012 	 * 
1013 	 * @param key
1014 	 *            key value
1015 	 * @param def
1016 	 *            default value
1017 	 * 
1018 	 * @return float-representation of value
1019 	 */
1020 	public float getFloat(final String key, final float def) {
1021 		try {
1022 			return getFloat(key);
1023 		} catch (final NumberFormatException nfe) {
1024 			return def;
1025 		}
1026 	}
1027 
1028 	/**
1029 	 * Gets the prefixed key and parse it to an float[]<br>
1030 	 * Each comma-separated list can be used.
1031 	 * 
1032 	 * @param key
1033 	 *            the key
1034 	 * @return float[] or null if the key couldn't get found.
1035 	 */
1036 	public float[] getFloatArray(final String key) {
1037 		final String[] value = getArray(key);
1038 		if (value != null) {
1039 			final float[] result = new float[value.length];
1040 			for (int i = 0; i < value.length; i++) {
1041 				result[i] = Float.parseFloat(value[i]);
1042 			}
1043 			return result;
1044 		}
1045 		return null;
1046 	}
1047 
1048 	/**
1049 	 * Gets the prefixed key and parse it to an float[]<br>
1050 	 * Each comma-separated list can be used. If the key couldn't get found, the
1051 	 * default will be used.
1052 	 * 
1053 	 * @param key
1054 	 *            the key
1055 	 * @param def
1056 	 *            default value
1057 	 * 
1058 	 * @return float[]
1059 	 */
1060 	public float[] getFloatArray(final String key, final float[] def) {
1061 		try {
1062 			final float[] result = getFloatArray(key);
1063 			return result != null ? result : def;
1064 		} catch (final NumberFormatException nfe) {
1065 			return def;
1066 		}
1067 	}
1068 
1069 	/**
1070 	 * Gets the prefixed key and parse it to an int-value.
1071 	 * 
1072 	 * @param key
1073 	 *            key value
1074 	 * @return int-representation of value
1075 	 */
1076 	public int getInt(final String key) {
1077 		final String value = getProperty(key);
1078 		if (value == null) {
1079 			throw new NumberFormatException("Couldn't parse property to int.");
1080 		}
1081 		return Integer.parseInt(value);
1082 	}
1083 
1084 	/**
1085 	 * Gets the prefixed key and parse it to an int-value if the key doesn't
1086 	 * exist the default value will be used.
1087 	 * 
1088 	 * @param key
1089 	 *            key value
1090 	 * @param def
1091 	 *            default value
1092 	 * 
1093 	 * @return int-representation of value
1094 	 */
1095 	public int getInt(final String key, final int def) {
1096 		try {
1097 			return getInt(key);
1098 		} catch (final NumberFormatException nfe) {
1099 			return def;
1100 		}
1101 	}
1102 
1103 	/**
1104 	 * Gets the prefixed key and parse it to an int[]<br>
1105 	 * Each comma-separated list can be used.
1106 	 * 
1107 	 * @param key
1108 	 *            the key
1109 	 * @return String[] or null if the key couldn't get found.
1110 	 */
1111 	public int[] getIntArray(final String key) {
1112 		final String[] value = getArray(key);
1113 		if (value != null) {
1114 			final int[] result = new int[value.length];
1115 			for (int i = 0; i < value.length; i++) {
1116 				result[i] = Integer.parseInt(value[i]);
1117 			}
1118 			return result;
1119 		}
1120 		return null;
1121 	}
1122 
1123 	/**
1124 	 * Gets the prefixed key and parse it to an int[]<br>
1125 	 * Each comma-separated list can be used. If the key couldn't get found, the
1126 	 * default will be used.
1127 	 * 
1128 	 * @param key
1129 	 *            the key
1130 	 * @param def
1131 	 *            default value
1132 	 * 
1133 	 * @return int[]
1134 	 */
1135 	public int[] getIntArray(final String key, final int[] def) {
1136 		try {
1137 			final int[] result = getIntArray(key);
1138 			return result != null ? result : def;
1139 		} catch (final NumberFormatException nfe) {
1140 			return def;
1141 		}
1142 	}
1143 
1144 	private Map<Object, Object> getKeyMap(final boolean onlyStrings) {
1145 		final Map<Object, Object> result = new HashMap<Object, Object>();
1146 		for (@SuppressWarnings("rawtypes")
1147 		final Map.Entry entry : properties.entrySet()) {
1148 			if (String.class == entry.getKey().getClass()) {
1149 				if (isKeyValid(entry.getKey())) {
1150 					final Object unprefixedKey = getUnprefixedKey(entry.getKey());
1151 					if (result.containsKey(unprefixedKey)) {
1152 						if (!unprefixedKey.equals(entry.getKey())) {
1153 							result.put(unprefixedKey, entry.getKey());
1154 						}
1155 					} else {
1156 						result.put(unprefixedKey, entry.getKey());
1157 					}
1158 				}
1159 			} else if (!onlyStrings) {
1160 				result.put(entry.getKey(), entry.getKey());
1161 			}
1162 		}
1163 		return result;
1164 	}
1165 
1166 	/**
1167 	 * Gets the prefixed key and parse it to an long-value.
1168 	 * 
1169 	 * @param key
1170 	 *            key value
1171 	 * @return long-representation of value
1172 	 */
1173 	public long getLong(final String key) {
1174 		final String value = getProperty(key);
1175 		if (value == null) {
1176 			throw new NumberFormatException("Couldn't parse property to long.");
1177 		}
1178 		return Long.parseLong(value);
1179 	}
1180 
1181 	/**
1182 	 * Gets the prefixed key and parse it to an long-value.
1183 	 * 
1184 	 * @param key
1185 	 *            key value
1186 	 * @param def
1187 	 *            default value
1188 	 * 
1189 	 * @return long-representation of value
1190 	 */
1191 	public long getLong(final String key, final long def) {
1192 		try {
1193 			return getLong(key);
1194 		} catch (final NumberFormatException nfe) {
1195 			return def;
1196 		}
1197 	}
1198 
1199 	/**
1200 	 * Gets the prefixed key and parse it to an long[]<br>
1201 	 * Each comma-separated list can be used.
1202 	 * 
1203 	 * @param key
1204 	 *            the key
1205 	 * @return long[] or null if the key couldn't get found.
1206 	 */
1207 	public long[] getLongArray(final String key) {
1208 		final String[] value = getArray(key);
1209 		if (value != null) {
1210 			final long[] result = new long[value.length];
1211 			for (int i = 0; i < value.length; i++) {
1212 				result[i] = Long.parseLong(value[i]);
1213 			}
1214 			return result;
1215 		}
1216 		return null;
1217 	}
1218 
1219 	/**
1220 	 * Gets the prefixed key and parse it to an long[]<br>
1221 	 * Each comma-separated list can be used. If the key couldn't get found, the
1222 	 * default will be used.
1223 	 * 
1224 	 * @param key
1225 	 *            the key
1226 	 * @param def
1227 	 *            default value
1228 	 * 
1229 	 * @return long[]
1230 	 */
1231 	public long[] getLongArray(final String key, final long[] def) {
1232 		try {
1233 			final long[] result = getLongArray(key);
1234 			return result != null ? result : def;
1235 		} catch (final NumberFormatException nfe) {
1236 			return def;
1237 		}
1238 	}
1239 
1240 	private StringBuilder getPrefix(final StringBuilder sb, final boolean useLocalPrefixConfigurations) {
1241 		if (properties instanceof PrefixedProperties) {
1242 			((PrefixedProperties) properties).getPrefix(sb, useLocalPrefixConfigurations);
1243 		}
1244 
1245 		if (prefixes != null) {
1246 			if (!useLocalPrefixConfigurations) {
1247 				if (prefixes.getPrefix() != null) {
1248 					if (sb.length() > 0) {
1249 						sb.append(PrefixConfig.PREFIXDELIMITER);
1250 					}
1251 					sb.append(prefixes.getPrefix());
1252 				}
1253 			} else {
1254 				if (prefixes.getLocalPrefix() != null) {
1255 					if (sb.length() > 0) {
1256 						sb.append(PrefixConfig.PREFIXDELIMITER);
1257 					}
1258 					sb.append(prefixes.getLocalPrefix());
1259 				}
1260 			}
1261 		}
1262 		return sb;
1263 	}
1264 
1265 	/**
1266 	 * Gets the prefix config.
1267 	 * 
1268 	 * @return the prefix config
1269 	 */
1270 	public PrefixConfig getPrefixConfig() {
1271 		lock.readLock().lock();
1272 		try {
1273 			return prefixes;
1274 		} finally {
1275 			lock.readLock().unlock();
1276 		}
1277 	}
1278 
1279 	private Map<Integer, PrefixConfig> getPrefixConfigs() {
1280 		return getPrefixConfigs(new TreeMap<Integer, PrefixConfig>());
1281 	}
1282 
1283 	private Map<Integer, PrefixConfig> getPrefixConfigs(final Map<Integer, PrefixConfig> result) {
1284 		if (properties instanceof PrefixedProperties) {
1285 			((PrefixedProperties) properties).getPrefixConfigs(result);
1286 		}
1287 		result.put(result.size(), getPrefixConfig());
1288 		return result;
1289 	}
1290 
1291 	@SuppressWarnings("unchecked")
1292 	private <T> T getPrefixedKey(final T key, final boolean useLocalPrefixConfigurations) {
1293 		if (String.class == key.getClass()) {
1294 			if (containsValidPrefix(key)) {
1295 				return key;
1296 			} else {
1297 				if (prefixes != null) {
1298 					return (T) prefixes.getPrefixedKey((String) key, useLocalPrefixConfigurations);
1299 				} else {
1300 					return key;
1301 				}
1302 			}
1303 		}
1304 		return key;
1305 	}
1306 
1307 	private Set<String> getPrefixes() {
1308 		return prefixes != null ? prefixes.getPrefixes() : Collections.<String> emptySet();
1309 	}
1310 
1311 	/**
1312 	 * Gets the property.
1313 	 * 
1314 	 * @param key
1315 	 *            the key
1316 	 * 
1317 	 * @return the property
1318 	 * 
1319 
1320 	 */
1321 	@Override
1322 	public String getProperty(final String key) {
1323 		lock.readLock().lock();
1324 		try {
1325 			final boolean useLocalPrefixes = !mixDefaultAndLocalPrefixes && hasLocalPrefixConfigurations();
1326 			final Object object = get(key, useLocalPrefixes);
1327 			if (object instanceof String) {
1328 				return (String) object;
1329 			} else {
1330 				if (object == null) {
1331 					return null;
1332 				}
1333 				throw new IllegalStateException("The value of " + key + " is of type: " + object.getClass().getName());
1334 			}
1335 		} finally {
1336 			lock.readLock().unlock();
1337 		}
1338 	}
1339 
1340 	/**
1341 	 * Gets the property.
1342 	 * 
1343 	 * @param value
1344 	 *            the value
1345 	 * @param def
1346 	 *            the def
1347 	 * 
1348 	 * @return the property
1349 	 * 
1350 	 */
1351 	@Override
1352 	public String getProperty(final String value, final String def) {
1353 		final String result = getProperty(value);
1354 		return result == null ? def : result;
1355 	}
1356 
1357 	/**
1358 	 * Gets the prefixed key and parse it to an byte-value.
1359 	 * 
1360 	 * @param key
1361 	 *            key value
1362 	 * @return byte-representation of value
1363 	 */
1364 	public short getShort(final String key) {
1365 		final String value = getProperty(key);
1366 		if (value == null) {
1367 			throw new NumberFormatException("Couldn't parse property to short.");
1368 		}
1369 		return Short.parseShort(value);
1370 	}
1371 
1372 	/**
1373 	 * Gets the prefixed key and parse it to an short-value.
1374 	 * 
1375 	 * @param key
1376 	 *            key value
1377 	 * @param def
1378 	 *            default value
1379 	 * 
1380 	 * @return short-representation of value
1381 	 */
1382 	public short getShort(final String key, final short def) {
1383 		try {
1384 			return getShort(key);
1385 		} catch (final NumberFormatException nfe) {
1386 			return def;
1387 		}
1388 	}
1389 
1390 	/**
1391 	 * Gets the prefixed key and parse it to an short[]<br>
1392 	 * Each comma-separated list can be used.
1393 	 * 
1394 	 * @param key
1395 	 *            the key
1396 	 * @return short[] or null if the key couldn't get found.
1397 	 */
1398 	public short[] getShortArray(final String key) {
1399 		final String[] value = getArray(key);
1400 		if (value != null) {
1401 			final short[] result = new short[value.length];
1402 			for (int i = 0; i < value.length; i++) {
1403 				result[i] = Short.parseShort(value[i]);
1404 			}
1405 			return result;
1406 		}
1407 		return null;
1408 	}
1409 
1410 	/**
1411 	 * Gets the prefixed key and parse it to an short[]<br>
1412 	 * Each comma-separated list can be used. If the key couldn't get found, the
1413 	 * default will be used.
1414 	 * 
1415 	 * @param key
1416 	 *            the key
1417 	 * @param def
1418 	 *            default value
1419 	 * 
1420 	 * @return short[]
1421 	 */
1422 	public short[] getShortArray(final String key, final short[] def) {
1423 		try {
1424 			final short[] result = getShortArray(key);
1425 			return result != null ? result : def;
1426 		} catch (final NumberFormatException nfe) {
1427 			return def;
1428 		}
1429 	}
1430 
1431 	private List<DoubleEntry<PrefixConfig, String>> getToSetPrefixMap(final List<String> prefixesList,
1432 			final Map<Integer, PrefixConfig> configs) throws IllegalArgumentException {
1433 		int i = 0;
1434 		PrefixConfig config = null;
1435 		final Map<Integer, PrefixConfig> subConfigs = new HashMap<Integer, PrefixConfig>(configs);
1436 		final List<DoubleEntry<PrefixConfig, String>> result = new LinkedList<DoubleEntry<PrefixConfig, String>>();
1437 		for (final Map.Entry<Integer, PrefixConfig> entry : configs.entrySet()) {
1438 			if (i == prefixesList.size()) {
1439 				break;
1440 			}
1441 			config = entry.getValue();
1442 			subConfigs.remove(entry.getKey());
1443 			if (config.isDynamic()) {
1444 				try {
1445 					result.addAll(getToSetPrefixMap(prefixesList.subList(i, prefixesList.size()), subConfigs));
1446 					i = prefixesList.size();
1447 					break;
1448 				} catch (final IllegalArgumentException iae) {
1449 					result.add(new DoubleEntry<PrefixConfig, String>(config, prefixesList.get(i)));
1450 					i++;
1451 				}
1452 			} else if (config.containsValidPrefix(prefixesList.get(i))) {
1453 				result.add(new DoubleEntry<PrefixConfig, String>(config, prefixesList.get(i)));
1454 				i++;
1455 			}
1456 		}
1457 		if (i < prefixesList.size()) {
1458 			throw new IllegalArgumentException("Prefix does not match the given PrefixConfig.");
1459 		}
1460 		return result;
1461 	}
1462 
1463 	@SuppressWarnings("unchecked")
1464 	private Map<String, Object> getTreeMap() {
1465 		final Map<Integer, PrefixConfig> configs = getPrefixConfigs();
1466 		final Map<String, Object> treeMap = new TreeMap<String, Object>();
1467 		final Properties props = getCompleteProperties();
1468 		String keyName = "";
1469 		String plainProperty = "";
1470 		for (final Enumeration<?> completition = props.propertyNames(); completition.hasMoreElements();) {
1471 			keyName = (String) completition.nextElement();
1472 			plainProperty = keyName;
1473 			Map<String, Object> propertyMap = treeMap;
1474 			for (int i = 0; i < configs.size(); i++) {
1475 				final PrefixConfig config = configs.get(i);
1476 				boolean found = false;
1477 				for (final Iterator<String> prefixIterator = config.getPrefixes().iterator(); prefixIterator.hasNext()
1478 						&& !found;) {
1479 					final String prefix = prefixIterator.next();
1480 					if (plainProperty.startsWith(prefix + PrefixConfig.PREFIXDELIMITER)) {
1481 						Object subMap = propertyMap.get(prefix);
1482 						if (subMap == null) {
1483 							subMap = new TreeMap<String, Object>();
1484 							propertyMap.put(prefix, subMap);
1485 						}
1486 						plainProperty = plainProperty.replaceFirst(prefix + PrefixConfig.PREFIXDELIMITER, "");
1487 						found = true;
1488 						if (subMap instanceof Map<?, ?>) {
1489 							propertyMap = (Map<String, Object>) subMap;
1490 						} else {
1491 							throw new IllegalStateException("Failed to render JSON-File.");
1492 						}
1493 					}
1494 				}
1495 			}
1496 			propertyMap.put((char) 1 + plainProperty, props.getProperty(keyName));
1497 		}
1498 		return treeMap;
1499 	}
1500 
1501 	@SuppressWarnings("unchecked")
1502 	private <T> T getUnprefixedKey(final T key) {
1503 		if (key == null) {
1504 			throw new IllegalArgumentException("A null key is not allowed.");
1505 		}
1506 		if (String.class == key.getClass()) {
1507 			String newKey = (String) key;
1508 			if (properties instanceof PrefixedProperties) {
1509 				newKey = (String) ((PrefixedProperties) properties).getUnprefixedKey(key);
1510 			}
1511 			newKey = prefixes.getUnprefixedKey(newKey);
1512 			return (T) newKey;
1513 		}
1514 		return key;
1515 	}
1516 
1517 	/*
1518 	 * (non-Javadoc)
1519 	 * 
1520 	 * @see java.util.Hashtable#hashCode()
1521 	 */
1522 	@Override
1523 	public int hashCode() {
1524 		final int prime = 31;
1525 		int result = super.hashCode();
1526 		result = prime * result + (prefixes == null ? 0 : prefixes.hashCode());
1527 		result = prime * result + (properties == null ? 0 : properties.hashCode());
1528 		return result;
1529 	}
1530 
1531 	/**
1532 	 * Checks if there are local prefix configurations existing.
1533 	 * 
1534 	 * @return true/false
1535 	 */
1536 	public boolean hasLocalPrefixConfigurations() {
1537 		return prefixes.containsLocalPrefix() || ((properties instanceof PrefixedProperties)
1538 				&& ((PrefixedProperties) properties).hasLocalPrefixConfigurations());
1539 	}
1540 
1541 	/*
1542 	 * (non-Javadoc)
1543 	 * 
1544 	 * @see java.util.Hashtable#isEmpty()
1545 	 */
1546 	@Override
1547 	public boolean isEmpty() {
1548 		lock.readLock().lock();
1549 		try {
1550 			return entrySet().isEmpty();
1551 		} finally {
1552 			lock.readLock().unlock();
1553 		}
1554 	}
1555 
1556 	private boolean isKeyValid(final Object key) {
1557 		if (key == null) {
1558 			throw new IllegalArgumentException("A null key is not allowed.");
1559 		}
1560 		if (String.class == key.getClass()) {
1561 			return !containsValidPrefix(key) || startsWithCurrentPrefix(key);
1562 		}
1563 		return true;
1564 	}
1565 
1566 	/*
1567 	 * (non-Javadoc)
1568 	 * 
1569 	 * @see java.util.Hashtable#keys()
1570 	 */
1571 	@SuppressWarnings("unchecked")
1572 	@Override
1573 	public PrefixedPropertiesEnumeration<Object> keys() {
1574 		lock.readLock().lock();
1575 		try {
1576 			@SuppressWarnings("rawtypes")
1577 			final Set keys = keySet();
1578 			@SuppressWarnings("rawtypes")
1579 			final Iterator it = keys.iterator();
1580 			return new PrefixedPropertiesEnumerationImpl<Object>(it, true);
1581 		} finally {
1582 			lock.readLock().unlock();
1583 		}
1584 	}
1585 
1586 	/*
1587 	 * (non-Javadoc)
1588 	 * 
1589 	 * @see java.util.Hashtable#keySet()
1590 	 */
1591 	@SuppressWarnings({ "rawtypes", "unchecked" })
1592 	@Override
1593 	public Set<Object> keySet() {
1594 		return new HashSet(getKeyMap(false).keySet());
1595 	}
1596 
1597 	/*
1598 	 * (non-Javadoc)
1599 	 * 
1600 	 * @see java.util.Properties#list(java.io.PrintStream)
1601 	 */
1602 	@Override
1603 	public void list(final PrintStream out) {
1604 		lock.readLock().lock();
1605 		try {
1606 			properties.list(out);
1607 		} finally {
1608 			lock.readLock().unlock();
1609 		}
1610 	}
1611 
1612 	/*
1613 	 * (non-Javadoc)
1614 	 * 
1615 	 * @see java.util.Properties#list(java.io.PrintWriter)
1616 	 */
1617 	@Override
1618 	public void list(final PrintWriter out) {
1619 		lock.readLock().lock();
1620 		try {
1621 			properties.list(out);
1622 		} finally {
1623 			lock.readLock().unlock();
1624 		}
1625 	}
1626 
1627 	/*
1628 	 * (non-Javadoc)
1629 	 * 
1630 	 * @see java.util.Properties#load(java.io.InputStream)
1631 	 */
1632 	@Override
1633 	public void load(final InputStream inStream) throws IOException {
1634 		lock.writeLock().lock();
1635 		try {
1636 			properties.load(inStream);
1637 		} finally {
1638 			lock.writeLock().unlock();
1639 		}
1640 	}
1641 
1642 	/*
1643 	 * (non-Javadoc)
1644 	 * 
1645 	 * @see java.util.Properties#load(java.io.Reader)
1646 	 */
1647 	@Override
1648 	public void load(final Reader reader) throws IOException {
1649 		lock.writeLock().lock();
1650 		try {
1651 			properties.load(reader);
1652 		} finally {
1653 			lock.writeLock().unlock();
1654 		}
1655 	}
1656 
1657 	/**
1658 	 * Loads a json file. Reading from the given InputStream. The InputStream
1659 	 * itself will not be closed after usage.
1660 	 * 
1661 	 * @param is
1662 	 *            the is
1663 	 * @throws IOException
1664 	 *             Signals that an I/O exception has occurred.
1665 	 */
1666 	public void loadFromJSON(final InputStream is) throws IOException {
1667 		lock.writeLock().lock();
1668 		try {
1669 			final JsonFactory f = new JsonFactory();
1670 			final JsonParser jp = f.createParser(is);
1671 			configureJsonParser(jp);
1672 			if (jp.nextToken() == JsonToken.START_OBJECT) {
1673 				traverseJSON(jp, null);
1674 			}
1675 		} finally {
1676 			lock.writeLock().unlock();
1677 		}
1678 	}
1679 
1680 	/**
1681 	 * Load a json file by using the given Reader. The reader will not be closed
1682 	 * by the method.
1683 	 * 
1684 	 * @param reader
1685 	 *            the reader
1686 	 * @throws IOException
1687 	 *             Signals that an I/O exception has occurred.
1688 	 */
1689 	public void loadFromJSON(final Reader reader) throws IOException {
1690 		lock.writeLock().lock();
1691 		try {
1692 			final JsonFactory f = new JsonFactory();
1693 			final JsonParser jp = f.createParser(reader);
1694 			configureJsonParser(jp);
1695 			if (jp.nextToken() == JsonToken.START_OBJECT) {
1696 				traverseJSON(jp, null);
1697 			}
1698 		} finally {
1699 			lock.writeLock().unlock();
1700 		}
1701 	}
1702 	
1703 	/**
1704 	 * Loads a json file. Reading from the given InputStream. The InputStream
1705 	 * itself will not be closed after usage.
1706 	 * 
1707 	 * @param is
1708 	 *            the is
1709 	 * @throws IOException
1710 	 *             Signals that an I/O exception has occurred.
1711 	 */
1712 	public void loadFromYAML(final InputStream is) throws IOException {
1713 		lock.writeLock().lock();
1714 		try {
1715 			final YAMLFactory f = new YAMLFactory();
1716 			final YAMLParser jp = f.createParser(is);
1717 			configureJsonParser(jp);
1718 			if (jp.nextToken() == JsonToken.START_OBJECT) {
1719 				traverseJSON(jp, null);
1720 			}
1721 		} finally {
1722 			lock.writeLock().unlock();
1723 		}
1724 	}
1725 	
1726 	/**
1727 	 * Load a json file by using the given Reader. The reader will not be closed
1728 	 * by the method.
1729 	 * 
1730 	 * @param reader
1731 	 *            the reader
1732 	 * @throws IOException
1733 	 *             Signals that an I/O exception has occurred.
1734 	 */
1735 	public void loadFromYAML(final Reader reader) throws IOException {
1736 		lock.writeLock().lock();
1737 		try {
1738 			final YAMLFactory f = new YAMLFactory();
1739 			final JsonParser jp = f.createParser(reader);
1740 			configureJsonParser(jp);
1741 			if (jp.nextToken() == JsonToken.START_OBJECT) {
1742 				traverseJSON(jp, null);
1743 			}
1744 		} finally {
1745 			lock.writeLock().unlock();
1746 		}
1747 	}
1748 
1749 	/*
1750 	 * (non-Javadoc)
1751 	 * 
1752 	 * @see java.util.Properties#loadFromXML(java.io.InputStream)
1753 	 */
1754 	@Override
1755 	public void loadFromXML(final InputStream in) throws IOException {
1756 		lock.writeLock().lock();
1757 		try {
1758 			properties.loadFromXML(in);
1759 		} finally {
1760 			lock.writeLock().unlock();
1761 		}
1762 	}
1763 
1764 	/*
1765 	 * (non-Javadoc)
1766 	 * 
1767 	 * @see java.util.Properties#propertyNames()
1768 	 */
1769 	@SuppressWarnings({ "rawtypes", "unchecked" })
1770 	@Override
1771 	public PrefixedPropertiesEnumeration<?> propertyNames() {
1772 		lock.readLock().lock();
1773 		try {
1774 			final Set result = new HashSet();
1775 			for (final Object key : getKeyMap(false).keySet()) {
1776 				result.add(getUnprefixedKey(key));
1777 			}
1778 			return new PrefixedPropertiesEnumerationImpl(result.iterator(), true);
1779 		} finally {
1780 			lock.readLock().unlock();
1781 		}
1782 	}
1783 
1784 	/*
1785 	 * (non-Javadoc)
1786 	 * 
1787 	 * @see java.util.Hashtable#put(java.lang.Object, java.lang.Object)
1788 	 */
1789 	@Override
1790 	public Object put(final Object key, final Object value) {
1791 		lock.writeLock().lock();
1792 		try {
1793 			return properties.put(key, value);
1794 		} finally {
1795 			lock.writeLock().unlock();
1796 		}
1797 	}
1798 
1799 	/*
1800 	 * (non-Javadoc)
1801 	 * 
1802 	 * @see java.util.Hashtable#putAll(java.util.Map)
1803 	 */
1804 	@Override
1805 	public void putAll(final Map<? extends Object, ? extends Object> t) {
1806 		lock.writeLock().lock();
1807 		try {
1808 			properties.putAll(t);
1809 		} finally {
1810 			lock.writeLock().unlock();
1811 		}
1812 	}
1813 
1814 	private void readObject(final ObjectInputStream ois) throws ClassNotFoundException, IOException {
1815 		prefixes = (PrefixConfig) ois.readObject();
1816 		properties = (Properties) ois.readObject();
1817 		mixDefaultAndLocalPrefixes = ois.readBoolean();
1818 		lock = new ReentrantReadWriteLock();
1819 	}
1820 
1821 	/*
1822 	 * (non-Javadoc)
1823 	 * 
1824 	 * @see java.util.Hashtable#remove(java.lang.Object)
1825 	 */
1826 	@Override
1827 	public Object remove(final Object key) {
1828 		lock.writeLock().lock();
1829 		try {
1830 			final boolean useLocalPrefixes = !mixDefaultAndLocalPrefixes && hasLocalPrefixConfigurations();
1831 			final Object someKey = getPrefixedKey(key, useLocalPrefixes);
1832 			Object result = properties.remove(someKey);
1833 			if (result == null) {
1834 				result = properties.remove(key);
1835 			}
1836 			return result;
1837 		} finally {
1838 			lock.writeLock().unlock();
1839 		}
1840 	}
1841 
1842 	/**
1843 	 * Removes the all.
1844 	 * 
1845 	 * @param key
1846 	 *            the key
1847 	 * @return the map
1848 	 */
1849 	public Map<Object, Object> removeAll(final Object key) {
1850 		lock.writeLock().lock();
1851 		try {
1852 			final Map<Object, Object> result = new HashMap<Object, Object>();
1853 			Object resultObj;
1854 			if (containsValidPrefix(key)) {
1855 				resultObj = properties.remove(key);
1856 				if (resultObj != null) {
1857 					result.put(key, resultObj);
1858 				}
1859 			} else {
1860 				for (final String prefix : getPrefixes()) {
1861 					final String pkey = prefix + PrefixConfig.PREFIXDELIMITER + key;
1862 					resultObj = properties.remove(pkey);
1863 					if (resultObj != null) {
1864 						result.put(pkey, resultObj);
1865 					}
1866 				}
1867 				resultObj = properties.remove(key);
1868 				if (resultObj != null) {
1869 					result.put(key, resultObj);
1870 				}
1871 			}
1872 			return result;
1873 		} finally {
1874 			lock.writeLock().unlock();
1875 		}
1876 	}
1877 
1878 	/**
1879 	 * Removes the property. That matches the given key. (It has the same
1880 	 * function like {@link java.util.Properties#remove(Object)}
1881 	 * 
1882 	 * @param key
1883 	 *            the key
1884 	 */
1885 	public void removeProperty(final String key) {
1886 		remove(key);
1887 	}
1888 
1889 	/*
1890 	 * (non-Javadoc)
1891 	 * 
1892 	 * @see java.util.Properties#save(java.io.OutputStream, java.lang.String)
1893 	 */
1894 	@SuppressWarnings("deprecation")
1895 	@Override
1896 	public void save(final OutputStream out, final String comments) {
1897 		lock.readLock().lock();
1898 		try {
1899 			properties.save(out, comments);
1900 		} finally {
1901 			lock.readLock().unlock();
1902 		}
1903 	}
1904 
1905 	/**
1906 	 * Sets the default prefix.
1907 	 * 
1908 	 * @param prefix
1909 	 *            the new default prefix
1910 	 */
1911 	public void setDefaultPrefix(final String prefix) {
1912 		lock.writeLock().lock();
1913 		try {
1914 			final String myPrefix = checkAndConvertPrefix(prefix);
1915 			final List<String> prefixList = split(myPrefix);
1916 			setDefaultPrefixes(prefixList);
1917 		} finally {
1918 			lock.writeLock().unlock();
1919 		}
1920 	}
1921 
1922 	private void setDefaultPrefixes(final List<String> prefixesList) {
1923 		final Map<Integer, PrefixConfig> configs = getPrefixConfigs();
1924 		try {
1925 			final List<DoubleEntry<PrefixConfig, String>> prefixesToSet = getToSetPrefixMap(prefixesList, configs);
1926 			final List<PrefixConfig> prefixesToSetList = new ArrayList<PrefixConfig>();
1927 			for (final DoubleEntry<PrefixConfig, String> entry : prefixesToSet) {
1928 				entry.getOne().setDefaultPrefix(entry.getTwo());
1929 				prefixesToSetList.add(entry.getOne());
1930 			}
1931 			final Collection<PrefixConfig> allConfigs = getPrefixConfigs().values();
1932 			for (final PrefixConfig config : allConfigs) {
1933 				if (!prefixesToSetList.contains(config)) {
1934 					config.setDefaultPrefix(null);// this will remove the
1935 													// defaultPrefix.
1936 				}
1937 			}
1938 		} catch (final IllegalArgumentException iae) {
1939 			throw new IllegalArgumentException("The given prefixes are not part of the PrefixConfig: " + prefixesList);
1940 		}
1941 	}
1942 
1943 	/**
1944 	 * Sets the local Prefix. The local Prefix is Thread depended and will only
1945 	 * affect the current thread. You can have a combination of default and
1946 	 * local prefix.
1947 	 * 
1948 	 * @param configuredPrefix
1949 	 *            the new configuredPrefix
1950 	 */
1951 	public void setLocalPrefix(final String configuredPrefix) {
1952 		lock.writeLock().lock();
1953 		try {
1954 			final String myPrefix = checkAndConvertPrefix(configuredPrefix);
1955 			final List<String> prefixList = split(myPrefix);
1956 			setPrefixes(prefixList);
1957 		} finally {
1958 			lock.writeLock().unlock();
1959 		}
1960 	}
1961 
1962 	/**
1963 	 * Setting to define if default prefixes should be mixed with local prefixes if local prefixes are not present.
1964 	 * 
1965 	 * @param value decide
1966 	 */
1967 	public void setMixDefaultAndLocalPrefixSettings(final boolean value) {
1968 		this.mixDefaultAndLocalPrefixes = value;
1969 		if (properties instanceof PrefixedProperties) {
1970 			((PrefixedProperties) properties).setMixDefaultAndLocalPrefixSettings(value);
1971 		}
1972 	}
1973 
1974 	/**
1975 	 * Sets the prefix config.
1976 	 * 
1977 	 * @param config
1978 	 *            the new prefix config
1979 	 */
1980 	public void setPrefixConfig(final PrefixConfig config) {
1981 		lock.writeLock().lock();
1982 		try {
1983 			if (config == null) {
1984 				prefixes = EmptyPrefix.INSTANCE;
1985 			} else {
1986 				prefixes = config;
1987 			}
1988 		} finally {
1989 			lock.writeLock().unlock();
1990 		}
1991 	}
1992 
1993 	private void setPrefixes(final List<String> prefixesList) {
1994 		final Map<Integer, PrefixConfig> configs = getPrefixConfigs();
1995 		try {
1996 			final List<DoubleEntry<PrefixConfig, String>> prefixesToSet = getToSetPrefixMap(prefixesList, configs);
1997 			final List<PrefixConfig> prefixesToSetList = new ArrayList<PrefixConfig>();
1998 			for (final DoubleEntry<PrefixConfig, String> entry : prefixesToSet) {
1999 				entry.getOne().setPrefix(entry.getTwo());
2000 				prefixesToSetList.add(entry.getOne());
2001 			}
2002 			final Collection<PrefixConfig> allConfigs = getPrefixConfigs().values();
2003 			for (final PrefixConfig config : allConfigs) {
2004 				if (!prefixesToSetList.contains(config)) {
2005 					config.setPrefix(null);// this will remove the
2006 											// configuredPrefix.
2007 				}
2008 			}
2009 		} catch (final IllegalArgumentException iae) {
2010 			throw new IllegalArgumentException("The given prefixes are not part of the PrefixConfig: " + prefixesList);
2011 		}
2012 	}
2013 
2014 	/*
2015 	 * (non-Javadoc)
2016 	 * 
2017 	 * @see java.util.Properties#setProperty(java.lang.String, java.lang.String)
2018 	 */
2019 	@Override
2020 	public Object setProperty(final String key, final String value) {
2021 		lock.writeLock().lock();
2022 		try {
2023 			return properties.setProperty(key, value);
2024 		} finally {
2025 			lock.writeLock().unlock();
2026 		}
2027 	}
2028 
2029 	/*
2030 	 * (non-Javadoc)
2031 	 * 
2032 	 * @see java.util.Hashtable#size()
2033 	 */
2034 	@Override
2035 	public int size() {
2036 		lock.readLock().lock();
2037 		try {
2038 			return getKeyMap(false).size();
2039 		} finally {
2040 			lock.readLock().unlock();
2041 		}
2042 	}
2043 
2044 	private List<String> split(final String myPrefix) {
2045 		List<String> prefixList;
2046 		if (myPrefix.indexOf(PrefixConfig.PREFIXDELIMITER) != -1) {
2047 			prefixList = new ArrayList<String>(Arrays.asList(myPrefix.split("\\" + PrefixConfig.PREFIXDELIMITER)));
2048 		} else {
2049 			prefixList = new ArrayList<String>(1);
2050 			prefixList.add(myPrefix);
2051 		}
2052 		return prefixList;
2053 	}
2054 
2055 	private boolean startsWithCurrentPrefix(final Object key) {
2056 		if (key != null && String.class == key.getClass()) {
2057 			return prefixes.startsWithCurrentPrefix((String) key);
2058 		}
2059 		return false;
2060 	}
2061 
2062 	/*
2063 	 * (non-Javadoc)
2064 	 * 
2065 	 * @see java.util.Properties#store(java.io.OutputStream, java.lang.String)
2066 	 */
2067 	@Override
2068 	public void store(final OutputStream out, final String comments) throws IOException {
2069 		lock.readLock().lock();
2070 		try {
2071 			properties.store(out, comments);
2072 		} finally {
2073 			lock.readLock().unlock();
2074 		}
2075 	}
2076 
2077 	/**
2078 	 * Stores a properties-file by using the given comments and encoding.
2079 	 * 
2080 	 * @param out
2081 	 *            the out
2082 	 * @param comments
2083 	 *            the comments
2084 	 * @param encoding
2085 	 *            the encoding
2086 	 * @throws IOException
2087 	 *             Signals that an I/O exception has occurred.
2088 	 */
2089 	public void store(final OutputStream out, final String comments, final String encoding) throws IOException {
2090 		lock.readLock().lock();
2091 		try {
2092 			properties.store(new OutputStreamWriter(out, Charset.forName(encoding)), comments);
2093 		} finally {
2094 			lock.readLock().unlock();
2095 		}
2096 	}
2097 
2098 	/*
2099 	 * (non-Javadoc)
2100 	 * 
2101 	 * @see java.util.Properties#store(java.io.Writer, java.lang.String)
2102 	 */
2103 	@Override
2104 	public void store(final Writer writer, final String comments) throws IOException {
2105 		lock.readLock().lock();
2106 		try {
2107 			properties.store(writer, comments);
2108 		} finally {
2109 			lock.readLock().unlock();
2110 		}
2111 	}
2112 
2113 	/**
2114 	 * Store to json.
2115 	 * 
2116 	 * @param os
2117 	 *            the os
2118 	 * @throws IOException
2119 	 *             Signals that an I/O exception has occurred.
2120 	 */
2121 	public void storeToJSON(final OutputStream os) throws IOException {
2122 		lock.readLock().lock();
2123 		try {
2124 			final JsonFactory f = new JsonFactory();
2125 			final JsonGenerator generator = f.createGenerator(os, JsonEncoding.UTF8);
2126 //			generator.configure(Feature.QUOTE_FIELD_NAMES, false);
2127 			generator.useDefaultPrettyPrinter();
2128 
2129 			generator.writeStartObject();
2130 			writeJsonOrYaml(generator, getTreeMap());
2131 			generator.writeEndObject();
2132 			generator.flush();
2133 		} finally {
2134 			lock.readLock().unlock();
2135 		}
2136 	}
2137 
2138 	/**
2139 	 * Store to json to the given OutputStream writing an additional header as
2140 	 * comment.
2141 	 * 
2142 	 * @param os
2143 	 *            the os
2144 	 * @param header
2145 	 *            the header
2146 	 * @throws IOException
2147 	 *             Signals that an I/O exception has occurred.
2148 	 */
2149 	public void storeToJSON(final OutputStream os, final String header) throws IOException {
2150 		storeToJSON(os, header, "UTF-8");
2151 	}
2152 
2153 	public void storeToJSON(final OutputStream os, final String header, final String encoding) throws IOException {
2154 		lock.readLock().lock();
2155 		try {
2156 			final JsonFactory f = new JsonFactory();
2157 			final JsonGenerator generator = f.createGenerator(new OutputStreamWriter(os, Charset.forName(encoding)));
2158 			generator.useDefaultPrettyPrinter();
2159 
2160 			generator.writeStartObject();
2161 			if (header != null) {
2162 				generator.writeRaw("/*");
2163 				generator.writeRaw(header);
2164 				generator.writeRaw("*/");
2165 			}
2166 
2167 			writeJsonOrYaml(generator, getTreeMap());
2168 			generator.writeEndObject();
2169 			generator.flush();
2170 		} finally {
2171 			lock.readLock().unlock();
2172 		}
2173 	}
2174 	
2175 	/**
2176 	 * Store to yaml.
2177 	 * 
2178 	 * @param os
2179 	 *            the os
2180 	 * @throws IOException
2181 	 *             Signals that an I/O exception has occurred.
2182 	 */
2183 	public void storeToYAML(final OutputStream os) throws IOException {
2184 		lock.readLock().lock();
2185 		try {
2186 			final YAMLFactory f = new YAMLFactory();
2187 			final YAMLGenerator generator = f.createGenerator(os, JsonEncoding.UTF8);
2188 			generator.useDefaultPrettyPrinter();
2189 
2190 			generator.writeStartObject();
2191 			writeJsonOrYaml(generator, getTreeMap());
2192 			generator.writeEndObject();
2193 			generator.flush();
2194 		} finally {
2195 			lock.readLock().unlock();
2196 		}
2197 	}
2198 
2199 	/**
2200 	 * Store to yaml to the given OutputStream writing an additional header as
2201 	 * comment.
2202 	 * 
2203 	 * @param os
2204 	 *            the os
2205 	 * @param header
2206 	 *            the header
2207 	 * @throws IOException
2208 	 *             Signals that an I/O exception has occurred.
2209 	 */
2210 	public void storeToYAML(final OutputStream os, final String header) throws IOException {
2211 		storeToYAML(os, header, "UTF-8");
2212 	}
2213 
2214 	public void storeToYAML(final OutputStream os, final String header, final String encoding) throws IOException {
2215 		lock.readLock().lock();
2216 		try {
2217 			final YAMLFactory f = new YAMLFactory();
2218 			final JsonGenerator generator = f.createGenerator(new OutputStreamWriter(os, Charset.forName(encoding)));
2219 			generator.useDefaultPrettyPrinter();
2220 
2221 			generator.writeStartObject();
2222 			if (header != null) {
2223 				generator.writeRaw("/*");
2224 				generator.writeRaw(header);
2225 				generator.writeRaw("*/");
2226 			}
2227 
2228 			writeJsonOrYaml(generator, getTreeMap());
2229 			generator.writeEndObject();
2230 			generator.flush();
2231 		} finally {
2232 			lock.readLock().unlock();
2233 		}
2234 	}
2235 
2236 	/*
2237 	 * (non-Javadoc)
2238 	 * 
2239 	 * @see java.util.Properties#storeToXML(java.io.OutputStream,
2240 	 * java.lang.String)
2241 	 */
2242 	@Override
2243 	public void storeToXML(final OutputStream os, final String comment) throws IOException {
2244 		lock.readLock().lock();
2245 		try {
2246 			properties.storeToXML(os, comment);
2247 		} finally {
2248 			lock.readLock().unlock();
2249 		}
2250 	}
2251 
2252 	/*
2253 	 * (non-Javadoc)
2254 	 * 
2255 	 * @see java.util.Properties#storeToXML(java.io.OutputStream,
2256 	 * java.lang.String, java.lang.String)
2257 	 */
2258 	@Override
2259 	public void storeToXML(final OutputStream os, final String comment, final String encoding) throws IOException {
2260 		lock.readLock().lock();
2261 		try {
2262 			properties.storeToXML(os, comment, encoding);
2263 		} finally {
2264 			lock.readLock().unlock();
2265 		}
2266 	}
2267 
2268 	/*
2269 	 * (non-Javadoc)
2270 	 * 
2271 	 * @see java.util.Properties#stringPropertyNames()
2272 	 */
2273 	@Override
2274 	public Set<String> stringPropertyNames() {
2275 		lock.readLock().lock();
2276 		try {
2277 			final Set<String> result = new HashSet<String>();
2278 			for (final Object key : getKeyMap(true).keySet()) {
2279 				result.add(getUnprefixedKey((String) key));
2280 			}
2281 			return result;
2282 		} finally {
2283 			lock.readLock().unlock();
2284 		}
2285 	}
2286 
2287 	/*
2288 	 * (non-Javadoc)
2289 	 * 
2290 	 * @see java.util.Hashtable#toString()
2291 	 */
2292 	@Override
2293 	public String toString() {
2294 		lock.readLock().lock();
2295 		try {
2296 			return properties.toString();
2297 		} finally {
2298 			lock.readLock().unlock();
2299 		}
2300 	}
2301 
2302 	protected void traverseJSON(final JsonParser jp, final String prefix) throws IOException {
2303 		while (jp.nextToken() != JsonToken.END_OBJECT) {
2304 			final String fieldname = jp.getText();
2305 			if (jp.nextToken() == JsonToken.START_OBJECT) {
2306 				traverseJSON(jp, prefix != null ? prefix + PrefixConfig.PREFIXDELIMITER + fieldname : fieldname);
2307 			} else {
2308 				final String text = jp.getText();
2309 				put(prefix != null ? prefix + PrefixConfig.PREFIXDELIMITER + fieldname : fieldname, text);
2310 			}
2311 		}
2312 	}
2313 
2314 	/*
2315 	 * (non-Javadoc)
2316 	 * 
2317 	 * @see java.util.Hashtable#values()
2318 	 */
2319 	@Override
2320 	public Collection<Object> values() {
2321 		lock.readLock().lock();
2322 		try {
2323 			final List<Object> result = new LinkedList<Object>();
2324 			final boolean useLocalPrefixes = !mixDefaultAndLocalPrefixes && hasLocalPrefixConfigurations();
2325 			for (final Object key : getKeyMap(false).values()) {
2326 				result.add(get(key, useLocalPrefixes));
2327 			}
2328 			return result;
2329 		} finally {
2330 			lock.readLock().unlock();
2331 		}
2332 	}
2333 
2334 	@SuppressWarnings("unchecked")
2335 	protected void writeJsonOrYaml(final JsonGenerator generator, final Map<String, Object> treeMap) throws IOException {
2336 		if (treeMap != null) {
2337 			for (final Map.Entry<String, Object> entry : treeMap.entrySet()) {
2338 				if (entry.getValue() instanceof String) {
2339 					generator.writeStringField(entry.getKey().substring(1), (String) entry.getValue());
2340 				} else if (entry.getValue() instanceof Map<?, ?>) {
2341 					generator.writeObjectFieldStart(entry.getKey());
2342 					writeJsonOrYaml(generator, (Map<String, Object>) entry.getValue());
2343 					generator.writeEndObject();
2344 				}
2345 			}
2346 		}
2347 	}
2348 
2349 	private void writeObject(final ObjectOutputStream oos) throws IOException {
2350 		oos.writeObject(prefixes);
2351 		oos.writeObject(properties);
2352 		oos.writeBoolean(mixDefaultAndLocalPrefixes);
2353 	}
2354 
2355 }