View Javadoc
1   /*
2    * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
3    * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
4    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
5    * Copyright (C) 2008-2010, Google Inc.
6    * Copyright (C) 2009, Google, Inc.
7    * Copyright (C) 2009, JetBrains s.r.o.
8    * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
9    * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
10   * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>
11   * and other copyright owners as documented in the project's IP log.
12   *
13   * This program and the accompanying materials are made available
14   * under the terms of the Eclipse Distribution License v1.0 which
15   * accompanies this distribution, is reproduced below, and is
16   * available at http://www.eclipse.org/org/documents/edl-v10.php
17   *
18   * All rights reserved.
19   *
20   * Redistribution and use in source and binary forms, with or
21   * without modification, are permitted provided that the following
22   * conditions are met:
23   *
24   * - Redistributions of source code must retain the above copyright
25   *   notice, this list of conditions and the following disclaimer.
26   *
27   * - Redistributions in binary form must reproduce the above
28   *   copyright notice, this list of conditions and the following
29   *   disclaimer in the documentation and/or other materials provided
30   *   with the distribution.
31   *
32   * - Neither the name of the Eclipse Foundation, Inc. nor the
33   *   names of its contributors may be used to endorse or promote
34   *   products derived from this software without specific prior
35   *   written permission.
36   *
37   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
38   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
39   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
40   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
41   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
42   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
43   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
44   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
45   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
46   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
47   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
48   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
49   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50   */
51  
52  package org.eclipse.jgit.lib;
53  
54  import static java.nio.charset.StandardCharsets.UTF_8;
55  
56  import java.text.MessageFormat;
57  import java.util.ArrayList;
58  import java.util.Collections;
59  import java.util.List;
60  import java.util.Locale;
61  import java.util.Set;
62  import java.util.concurrent.TimeUnit;
63  import java.util.concurrent.atomic.AtomicReference;
64  
65  import org.eclipse.jgit.errors.ConfigInvalidException;
66  import org.eclipse.jgit.events.ConfigChangedEvent;
67  import org.eclipse.jgit.events.ConfigChangedListener;
68  import org.eclipse.jgit.events.ListenerHandle;
69  import org.eclipse.jgit.events.ListenerList;
70  import org.eclipse.jgit.internal.JGitText;
71  import org.eclipse.jgit.transport.RefSpec;
72  import org.eclipse.jgit.util.RawParseUtils;
73  
74  /**
75   * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
76   */
77  public class Config {
78  
79  	private static final String[] EMPTY_STRING_ARRAY = {};
80  
81  	static final long KiB = 1024;
82  	static final long MiB = 1024 * KiB;
83  	static final long GiB = 1024 * MiB;
84  	private static final int MAX_DEPTH = 10;
85  
86  	private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter();
87  
88  	private static TypedConfigGetter typedGetter = DEFAULT_GETTER;
89  
90  	/** the change listeners */
91  	private final ListenerListhtml#ListenerList">ListenerList listeners = new ListenerList();
92  
93  	/**
94  	 * Immutable current state of the configuration data.
95  	 * <p>
96  	 * This state is copy-on-write. It should always contain an immutable list
97  	 * of the configuration keys/values.
98  	 */
99  	private final AtomicReference<ConfigSnapshot> state;
100 
101 	private final Config baseConfig;
102 
103 	/**
104 	 * Magic value indicating a missing entry.
105 	 * <p>
106 	 * This value is tested for reference equality in some contexts, so we
107 	 * must ensure it is a special copy of the empty string.  It also must
108 	 * be treated like the empty string.
109 	 */
110 	static final String MAGIC_EMPTY_VALUE = new String();
111 
112 	/**
113 	 * Create a configuration with no default fallback.
114 	 */
115 	public Config() {
116 		this(null);
117 	}
118 
119 	/**
120 	 * Create an empty configuration with a fallback for missing keys.
121 	 *
122 	 * @param defaultConfig
123 	 *            the base configuration to be consulted when a key is missing
124 	 *            from this configuration instance.
125 	 */
126 	public Configig" href="../../../../org/eclipse/jgit/lib/Config.html#Config">Config(Config defaultConfig) {
127 		baseConfig = defaultConfig;
128 		state = new AtomicReference<>(newState());
129 	}
130 
131 	/**
132 	 * Globally sets a {@link org.eclipse.jgit.lib.TypedConfigGetter} that is
133 	 * subsequently used to read typed values from all git configs.
134 	 *
135 	 * @param getter
136 	 *            to use; if {@code null} use the default getter.
137 	 * @since 4.9
138 	 */
139 	public static void setTypedConfigGetter(TypedConfigGetter getter) {
140 		typedGetter = getter == null ? DEFAULT_GETTER : getter;
141 	}
142 
143 	/**
144 	 * Escape the value before saving
145 	 *
146 	 * @param x
147 	 *            the value to escape
148 	 * @return the escaped value
149 	 */
150 	static String escapeValue(String x) {
151 		if (x.isEmpty()) {
152 			return ""; //$NON-NLS-1$
153 		}
154 
155 		boolean needQuote = x.charAt(0) == ' ' || x.charAt(x.length() - 1) == ' ';
156 		StringBuilder r = new StringBuilder(x.length());
157 		for (int k = 0; k < x.length(); k++) {
158 			char c = x.charAt(k);
159 			// git-config(1) lists the limited set of supported escape sequences, but
160 			// the documentation is otherwise not especially normative. In particular,
161 			// which ones of these produce and/or require escaping and/or quoting
162 			// around them is not documented and was discovered by trial and error.
163 			// In summary:
164 			//
165 			// * Quotes are only required if there is leading/trailing whitespace or a
166 			//   comment character.
167 			// * Bytes that have a supported escape sequence are escaped, except for
168 			//   \b for some reason which isn't.
169 			// * Needing an escape sequence is not sufficient reason to quote the
170 			//   value.
171 			switch (c) {
172 			case '\0':
173 				// Unix command line calling convention cannot pass a '\0' as an
174 				// argument, so there is no equivalent way in C git to store a null byte
175 				// in a config value.
176 				throw new IllegalArgumentException(
177 						JGitText.get().configValueContainsNullByte);
178 
179 			case '\n':
180 				r.append('\\').append('n');
181 				break;
182 
183 			case '\t':
184 				r.append('\\').append('t');
185 				break;
186 
187 			case '\b':
188 				// Doesn't match `git config foo.bar $'x\by'`, which doesn't escape the
189 				// \x08, but since both escaped and unescaped forms are readable, we'll
190 				// prefer internal consistency here.
191 				r.append('\\').append('b');
192 				break;
193 
194 			case '\\':
195 				r.append('\\').append('\\');
196 				break;
197 
198 			case '"':
199 				r.append('\\').append('"');
200 				break;
201 
202 			case '#':
203 			case ';':
204 				needQuote = true;
205 				r.append(c);
206 				break;
207 
208 			default:
209 				r.append(c);
210 				break;
211 			}
212 		}
213 
214 		return needQuote ? '"' + r.toString() + '"' : r.toString();
215 	}
216 
217 	static String escapeSubsection(String x) {
218 		if (x.isEmpty()) {
219 			return "\"\""; //$NON-NLS-1$
220 		}
221 
222 		StringBuilder r = new StringBuilder(x.length() + 2).append('"');
223 		for (int k = 0; k < x.length(); k++) {
224 			char c = x.charAt(k);
225 
226 			// git-config(1) lists the limited set of supported escape sequences
227 			// (which is even more limited for subsection names than for values).
228 			switch (c) {
229 			case '\0':
230 				throw new IllegalArgumentException(
231 						JGitText.get().configSubsectionContainsNullByte);
232 
233 			case '\n':
234 				throw new IllegalArgumentException(
235 						JGitText.get().configSubsectionContainsNewline);
236 
237 			case '\\':
238 			case '"':
239 				r.append('\\').append(c);
240 				break;
241 
242 			default:
243 				r.append(c);
244 				break;
245 			}
246 		}
247 
248 		return r.append('"').toString();
249 	}
250 
251 	/**
252 	 * Obtain an integer value from the configuration.
253 	 *
254 	 * @param section
255 	 *            section the key is grouped within.
256 	 * @param name
257 	 *            name of the key to get.
258 	 * @param defaultValue
259 	 *            default value to return if no value was present.
260 	 * @return an integer value from the configuration, or defaultValue.
261 	 */
262 	public int getInt(final String section, final String name,
263 			final int defaultValue) {
264 		return typedGetter.getInt(this, section, null, name, defaultValue);
265 	}
266 
267 	/**
268 	 * Obtain an integer value from the configuration.
269 	 *
270 	 * @param section
271 	 *            section the key is grouped within.
272 	 * @param subsection
273 	 *            subsection name, such a remote or branch name.
274 	 * @param name
275 	 *            name of the key to get.
276 	 * @param defaultValue
277 	 *            default value to return if no value was present.
278 	 * @return an integer value from the configuration, or defaultValue.
279 	 */
280 	public int getInt(final String section, String subsection,
281 			final String name, final int defaultValue) {
282 		return typedGetter.getInt(this, section, subsection, name,
283 				defaultValue);
284 	}
285 
286 	/**
287 	 * Obtain an integer value from the configuration.
288 	 *
289 	 * @param section
290 	 *            section the key is grouped within.
291 	 * @param name
292 	 *            name of the key to get.
293 	 * @param defaultValue
294 	 *            default value to return if no value was present.
295 	 * @return an integer value from the configuration, or defaultValue.
296 	 */
297 	public long getLong(String section, String name, long defaultValue) {
298 		return typedGetter.getLong(this, section, null, name, defaultValue);
299 	}
300 
301 	/**
302 	 * Obtain an integer value from the configuration.
303 	 *
304 	 * @param section
305 	 *            section the key is grouped within.
306 	 * @param subsection
307 	 *            subsection name, such a remote or branch name.
308 	 * @param name
309 	 *            name of the key to get.
310 	 * @param defaultValue
311 	 *            default value to return if no value was present.
312 	 * @return an integer value from the configuration, or defaultValue.
313 	 */
314 	public long getLong(final String section, String subsection,
315 			final String name, final long defaultValue) {
316 		return typedGetter.getLong(this, section, subsection, name,
317 				defaultValue);
318 	}
319 
320 	/**
321 	 * Get a boolean value from the git config
322 	 *
323 	 * @param section
324 	 *            section the key is grouped within.
325 	 * @param name
326 	 *            name of the key to get.
327 	 * @param defaultValue
328 	 *            default value to return if no value was present.
329 	 * @return true if any value or defaultValue is true, false for missing or
330 	 *         explicit false
331 	 */
332 	public boolean getBoolean(final String section, final String name,
333 			final boolean defaultValue) {
334 		return typedGetter.getBoolean(this, section, null, name, defaultValue);
335 	}
336 
337 	/**
338 	 * Get a boolean value from the git config
339 	 *
340 	 * @param section
341 	 *            section the key is grouped within.
342 	 * @param subsection
343 	 *            subsection name, such a remote or branch name.
344 	 * @param name
345 	 *            name of the key to get.
346 	 * @param defaultValue
347 	 *            default value to return if no value was present.
348 	 * @return true if any value or defaultValue is true, false for missing or
349 	 *         explicit false
350 	 */
351 	public boolean getBoolean(final String section, String subsection,
352 			final String name, final boolean defaultValue) {
353 		return typedGetter.getBoolean(this, section, subsection, name,
354 				defaultValue);
355 	}
356 
357 	/**
358 	 * Parse an enumeration from the configuration.
359 	 *
360 	 * @param section
361 	 *            section the key is grouped within.
362 	 * @param subsection
363 	 *            subsection name, such a remote or branch name.
364 	 * @param name
365 	 *            name of the key to get.
366 	 * @param defaultValue
367 	 *            default value to return if no value was present.
368 	 * @return the selected enumeration value, or {@code defaultValue}.
369 	 */
370 	public <T extends Enum<?>> T getEnum(final String section,
371 			final String subsection, final String name, final T defaultValue) {
372 		final T[] all = allValuesOf(defaultValue);
373 		return typedGetter.getEnum(this, all, section, subsection, name,
374 				defaultValue);
375 	}
376 
377 	@SuppressWarnings("unchecked")
378 	private static <T> T[] allValuesOf(T value) {
379 		try {
380 			return (T[]) value.getClass().getMethod("values").invoke(null); //$NON-NLS-1$
381 		} catch (Exception err) {
382 			String typeName = value.getClass().getName();
383 			String msg = MessageFormat.format(
384 					JGitText.get().enumValuesNotAvailable, typeName);
385 			throw new IllegalArgumentException(msg, err);
386 		}
387 	}
388 
389 	/**
390 	 * Parse an enumeration from the configuration.
391 	 *
392 	 * @param all
393 	 *            all possible values in the enumeration which should be
394 	 *            recognized. Typically {@code EnumType.values()}.
395 	 * @param section
396 	 *            section the key is grouped within.
397 	 * @param subsection
398 	 *            subsection name, such a remote or branch name.
399 	 * @param name
400 	 *            name of the key to get.
401 	 * @param defaultValue
402 	 *            default value to return if no value was present.
403 	 * @return the selected enumeration value, or {@code defaultValue}.
404 	 */
405 	public <T extends Enum<?>> T getEnum(final T[] all, final String section,
406 			final String subsection, final String name, final T defaultValue) {
407 		return typedGetter.getEnum(this, all, section, subsection, name,
408 				defaultValue);
409 	}
410 
411 	/**
412 	 * Get string value or null if not found.
413 	 *
414 	 * @param section
415 	 *            the section
416 	 * @param subsection
417 	 *            the subsection for the value
418 	 * @param name
419 	 *            the key name
420 	 * @return a String value from the config, <code>null</code> if not found
421 	 */
422 	public String getString(final String section, String subsection,
423 			final String name) {
424 		return getRawString(section, subsection, name);
425 	}
426 
427 	/**
428 	 * Get a list of string values
429 	 * <p>
430 	 * If this instance was created with a base, the base's values are returned
431 	 * first (if any).
432 	 *
433 	 * @param section
434 	 *            the section
435 	 * @param subsection
436 	 *            the subsection for the value
437 	 * @param name
438 	 *            the key name
439 	 * @return array of zero or more values from the configuration.
440 	 */
441 	public String[] getStringList(final String section, String subsection,
442 			final String name) {
443 		String[] base;
444 		if (baseConfig != null)
445 			base = baseConfig.getStringList(section, subsection, name);
446 		else
447 			base = EMPTY_STRING_ARRAY;
448 
449 		String[] self = getRawStringList(section, subsection, name);
450 		if (self == null)
451 			return base;
452 		if (base.length == 0)
453 			return self;
454 		String[] res = new String[base.length + self.length];
455 		int n = base.length;
456 		System.arraycopy(base, 0, res, 0, n);
457 		System.arraycopy(self, 0, res, n, self.length);
458 		return res;
459 	}
460 
461 	/**
462 	 * Parse a numerical time unit, such as "1 minute", from the configuration.
463 	 *
464 	 * @param section
465 	 *            section the key is in.
466 	 * @param subsection
467 	 *            subsection the key is in, or null if not in a subsection.
468 	 * @param name
469 	 *            the key name.
470 	 * @param defaultValue
471 	 *            default value to return if no value was present.
472 	 * @param wantUnit
473 	 *            the units of {@code defaultValue} and the return value, as
474 	 *            well as the units to assume if the value does not contain an
475 	 *            indication of the units.
476 	 * @return the value, or {@code defaultValue} if not set, expressed in
477 	 *         {@code units}.
478 	 * @since 4.5
479 	 */
480 	public long getTimeUnit(String section, String subsection, String name,
481 			long defaultValue, TimeUnit wantUnit) {
482 		return typedGetter.getTimeUnit(this, section, subsection, name,
483 				defaultValue, wantUnit);
484 	}
485 
486 	/**
487 	 * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from the
488 	 * configuration.
489 	 *
490 	 * @param section
491 	 *            section the key is in.
492 	 * @param subsection
493 	 *            subsection the key is in, or null if not in a subsection.
494 	 * @param name
495 	 *            the key name.
496 	 * @return a possibly empty list of
497 	 *         {@link org.eclipse.jgit.transport.RefSpec}s
498 	 * @since 4.9
499 	 */
500 	public List<RefSpec> getRefSpecs(String section, String subsection,
501 			String name) {
502 		return typedGetter.getRefSpecs(this, section, subsection, name);
503 	}
504 
505 	/**
506 	 * Get set of all subsections of specified section within this configuration
507 	 * and its base configuration
508 	 *
509 	 * @param section
510 	 *            section to search for.
511 	 * @return set of all subsections of specified section within this
512 	 *         configuration and its base configuration; may be empty if no
513 	 *         subsection exists. The set's iterator returns sections in the
514 	 *         order they are declared by the configuration starting from this
515 	 *         instance and progressing through the base.
516 	 */
517 	public Set<String> getSubsections(String section) {
518 		return getState().getSubsections(section);
519 	}
520 
521 	/**
522 	 * Get the sections defined in this {@link org.eclipse.jgit.lib.Config}.
523 	 *
524 	 * @return the sections defined in this {@link org.eclipse.jgit.lib.Config}.
525 	 *         The set's iterator returns sections in the order they are
526 	 *         declared by the configuration starting from this instance and
527 	 *         progressing through the base.
528 	 */
529 	public Set<String> getSections() {
530 		return getState().getSections();
531 	}
532 
533 	/**
534 	 * Get the list of names defined for this section
535 	 *
536 	 * @param section
537 	 *            the section
538 	 * @return the list of names defined for this section
539 	 */
540 	public Set<String> getNames(String section) {
541 		return getNames(section, null);
542 	}
543 
544 	/**
545 	 * Get the list of names defined for this subsection
546 	 *
547 	 * @param section
548 	 *            the section
549 	 * @param subsection
550 	 *            the subsection
551 	 * @return the list of names defined for this subsection
552 	 */
553 	public Set<String> getNames(String section, String subsection) {
554 		return getState().getNames(section, subsection);
555 	}
556 
557 	/**
558 	 * Get the list of names defined for this section
559 	 *
560 	 * @param section
561 	 *            the section
562 	 * @param recursive
563 	 *            if {@code true} recursively adds the names defined in all base
564 	 *            configurations
565 	 * @return the list of names defined for this section
566 	 * @since 3.2
567 	 */
568 	public Set<String> getNames(String section, boolean recursive) {
569 		return getState().getNames(section, null, recursive);
570 	}
571 
572 	/**
573 	 * Get the list of names defined for this section
574 	 *
575 	 * @param section
576 	 *            the section
577 	 * @param subsection
578 	 *            the subsection
579 	 * @param recursive
580 	 *            if {@code true} recursively adds the names defined in all base
581 	 *            configurations
582 	 * @return the list of names defined for this subsection
583 	 * @since 3.2
584 	 */
585 	public Set<String> getNames(String section, String subsection,
586 			boolean recursive) {
587 		return getState().getNames(section, subsection, recursive);
588 	}
589 
590 	/**
591 	 * Obtain a handle to a parsed set of configuration values.
592 	 *
593 	 * @param <T>
594 	 *            type of configuration model to return.
595 	 * @param parser
596 	 *            parser which can create the model if it is not already
597 	 *            available in this configuration file. The parser is also used
598 	 *            as the key into a cache and must obey the hashCode and equals
599 	 *            contract in order to reuse a parsed model.
600 	 * @return the parsed object instance, which is cached inside this config.
601 	 */
602 	@SuppressWarnings("unchecked")
603 	public <T> T get(SectionParser<T> parser) {
604 		final ConfigSnapshot myState = getState();
605 		T obj = (T) myState.cache.get(parser);
606 		if (obj == null) {
607 			obj = parser.parse(this);
608 			myState.cache.put(parser, obj);
609 		}
610 		return obj;
611 	}
612 
613 	/**
614 	 * Remove a cached configuration object.
615 	 * <p>
616 	 * If the associated configuration object has not yet been cached, this
617 	 * method has no effect.
618 	 *
619 	 * @param parser
620 	 *            parser used to obtain the configuration object.
621 	 * @see #get(SectionParser)
622 	 */
623 	public void uncache(SectionParser<?> parser) {
624 		state.get().cache.remove(parser);
625 	}
626 
627 	/**
628 	 * Adds a listener to be notified about changes.
629 	 * <p>
630 	 * Clients are supposed to remove the listeners after they are done with
631 	 * them using the {@link org.eclipse.jgit.events.ListenerHandle#remove()}
632 	 * method
633 	 *
634 	 * @param listener
635 	 *            the listener
636 	 * @return the handle to the registered listener
637 	 */
638 	public ListenerHandle addChangeListener(ConfigChangedListener listener) {
639 		return listeners.addConfigChangedListener(listener);
640 	}
641 
642 	/**
643 	 * Determine whether to issue change events for transient changes.
644 	 * <p>
645 	 * If <code>true</code> is returned (which is the default behavior),
646 	 * {@link #fireConfigChangedEvent()} will be called upon each change.
647 	 * <p>
648 	 * Subclasses that override this to return <code>false</code> are
649 	 * responsible for issuing {@link #fireConfigChangedEvent()} calls
650 	 * themselves.
651 	 *
652 	 * @return <code></code>
653 	 */
654 	protected boolean notifyUponTransientChanges() {
655 		return true;
656 	}
657 
658 	/**
659 	 * Notifies the listeners
660 	 */
661 	protected void fireConfigChangedEvent() {
662 		listeners.dispatch(new ConfigChangedEvent());
663 	}
664 
665 	String getRawString(final String section, final String subsection,
666 			final String name) {
667 		String[] lst = getRawStringList(section, subsection, name);
668 		if (lst != null) {
669 			return lst[lst.length - 1];
670 		} else if (baseConfig != null) {
671 			return baseConfig.getRawString(section, subsection, name);
672 		} else {
673 			return null;
674 		}
675 	}
676 
677 	private String[] getRawStringList(String section, String subsection,
678 			String name) {
679 		return state.get().get(section, subsection, name);
680 	}
681 
682 	private ConfigSnapshot getState() {
683 		ConfigSnapshot cur, upd;
684 		do {
685 			cur = state.get();
686 			final ConfigSnapshot base = getBaseState();
687 			if (cur.baseState == base)
688 				return cur;
689 			upd = new ConfigSnapshot(cur.entryList, base);
690 		} while (!state.compareAndSet(cur, upd));
691 		return upd;
692 	}
693 
694 	private ConfigSnapshot getBaseState() {
695 		return baseConfig != null ? baseConfig.getState() : null;
696 	}
697 
698 	/**
699 	 * Add or modify a configuration value. The parameters will result in a
700 	 * configuration entry like this.
701 	 *
702 	 * <pre>
703 	 * [section &quot;subsection&quot;]
704 	 *         name = value
705 	 * </pre>
706 	 *
707 	 * @param section
708 	 *            section name, e.g "branch"
709 	 * @param subsection
710 	 *            optional subsection value, e.g. a branch name
711 	 * @param name
712 	 *            parameter name, e.g. "filemode"
713 	 * @param value
714 	 *            parameter value
715 	 */
716 	public void setInt(final String section, final String subsection,
717 			final String name, final int value) {
718 		setLong(section, subsection, name, value);
719 	}
720 
721 	/**
722 	 * Add or modify a configuration value. The parameters will result in a
723 	 * configuration entry like this.
724 	 *
725 	 * <pre>
726 	 * [section &quot;subsection&quot;]
727 	 *         name = value
728 	 * </pre>
729 	 *
730 	 * @param section
731 	 *            section name, e.g "branch"
732 	 * @param subsection
733 	 *            optional subsection value, e.g. a branch name
734 	 * @param name
735 	 *            parameter name, e.g. "filemode"
736 	 * @param value
737 	 *            parameter value
738 	 */
739 	public void setLong(final String section, final String subsection,
740 			final String name, final long value) {
741 		final String s;
742 
743 		if (value >= GiB && (value % GiB) == 0)
744 			s = String.valueOf(value / GiB) + "g"; //$NON-NLS-1$
745 		else if (value >= MiB && (value % MiB) == 0)
746 			s = String.valueOf(value / MiB) + "m"; //$NON-NLS-1$
747 		else if (value >= KiB && (value % KiB) == 0)
748 			s = String.valueOf(value / KiB) + "k"; //$NON-NLS-1$
749 		else
750 			s = String.valueOf(value);
751 
752 		setString(section, subsection, name, s);
753 	}
754 
755 	/**
756 	 * Add or modify a configuration value. The parameters will result in a
757 	 * configuration entry like this.
758 	 *
759 	 * <pre>
760 	 * [section &quot;subsection&quot;]
761 	 *         name = value
762 	 * </pre>
763 	 *
764 	 * @param section
765 	 *            section name, e.g "branch"
766 	 * @param subsection
767 	 *            optional subsection value, e.g. a branch name
768 	 * @param name
769 	 *            parameter name, e.g. "filemode"
770 	 * @param value
771 	 *            parameter value
772 	 */
773 	public void setBoolean(final String section, final String subsection,
774 			final String name, final boolean value) {
775 		setString(section, subsection, name, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
776 	}
777 
778 	/**
779 	 * Add or modify a configuration value. The parameters will result in a
780 	 * configuration entry like this.
781 	 *
782 	 * <pre>
783 	 * [section &quot;subsection&quot;]
784 	 *         name = value
785 	 * </pre>
786 	 *
787 	 * @param section
788 	 *            section name, e.g "branch"
789 	 * @param subsection
790 	 *            optional subsection value, e.g. a branch name
791 	 * @param name
792 	 *            parameter name, e.g. "filemode"
793 	 * @param value
794 	 *            parameter value
795 	 */
796 	public <T extends Enum<?>> void setEnum(final String section,
797 			final String subsection, final String name, final T value) {
798 		String n;
799 		if (value instanceof ConfigEnum)
800 			n = ((ConfigEnum) value).toConfigValue();
801 		else
802 			n = value.name().toLowerCase(Locale.ROOT).replace('_', ' ');
803 		setString(section, subsection, name, n);
804 	}
805 
806 	/**
807 	 * Add or modify a configuration value. The parameters will result in a
808 	 * configuration entry like this.
809 	 *
810 	 * <pre>
811 	 * [section &quot;subsection&quot;]
812 	 *         name = value
813 	 * </pre>
814 	 *
815 	 * @param section
816 	 *            section name, e.g "branch"
817 	 * @param subsection
818 	 *            optional subsection value, e.g. a branch name
819 	 * @param name
820 	 *            parameter name, e.g. "filemode"
821 	 * @param value
822 	 *            parameter value, e.g. "true"
823 	 */
824 	public void setString(final String section, final String subsection,
825 			final String name, final String value) {
826 		setStringList(section, subsection, name, Collections
827 				.singletonList(value));
828 	}
829 
830 	/**
831 	 * Remove a configuration value.
832 	 *
833 	 * @param section
834 	 *            section name, e.g "branch"
835 	 * @param subsection
836 	 *            optional subsection value, e.g. a branch name
837 	 * @param name
838 	 *            parameter name, e.g. "filemode"
839 	 */
840 	public void unset(final String section, final String subsection,
841 			final String name) {
842 		setStringList(section, subsection, name, Collections
843 				.<String> emptyList());
844 	}
845 
846 	/**
847 	 * Remove all configuration values under a single section.
848 	 *
849 	 * @param section
850 	 *            section name, e.g "branch"
851 	 * @param subsection
852 	 *            optional subsection value, e.g. a branch name
853 	 */
854 	public void unsetSection(String section, String subsection) {
855 		ConfigSnapshot src, res;
856 		do {
857 			src = state.get();
858 			res = unsetSection(src, section, subsection);
859 		} while (!state.compareAndSet(src, res));
860 	}
861 
862 	private ConfigSnapshotonfigSnapshot">ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
863 			final String section,
864 			final String subsection) {
865 		final int max = srcState.entryList.size();
866 		final ArrayList<ConfigLine> r = new ArrayList<>(max);
867 
868 		boolean lastWasMatch = false;
869 		for (ConfigLine e : srcState.entryList) {
870 			if (e.includedFrom == null && e.match(section, subsection)) {
871 				// Skip this record, it's for the section we are removing.
872 				lastWasMatch = true;
873 				continue;
874 			}
875 
876 			if (lastWasMatch && e.section == null && e.subsection == null)
877 				continue; // skip this padding line in the section.
878 			r.add(e);
879 		}
880 
881 		return newState(r);
882 	}
883 
884 	/**
885 	 * Set a configuration value.
886 	 *
887 	 * <pre>
888 	 * [section &quot;subsection&quot;]
889 	 *         name = value1
890 	 *         name = value2
891 	 * </pre>
892 	 *
893 	 * @param section
894 	 *            section name, e.g "branch"
895 	 * @param subsection
896 	 *            optional subsection value, e.g. a branch name
897 	 * @param name
898 	 *            parameter name, e.g. "filemode"
899 	 * @param values
900 	 *            list of zero or more values for this key.
901 	 */
902 	public void setStringList(final String section, final String subsection,
903 			final String name, final List<String> values) {
904 		ConfigSnapshot src, res;
905 		do {
906 			src = state.get();
907 			res = replaceStringList(src, section, subsection, name, values);
908 		} while (!state.compareAndSet(src, res));
909 		if (notifyUponTransientChanges())
910 			fireConfigChangedEvent();
911 	}
912 
913 	private ConfigSnapshotSnapshot">ConfigSnapshot replaceStringList(final ConfigSnapshot srcState,
914 			final String section, final String subsection, final String name,
915 			final List<String> values) {
916 		final List<ConfigLine> entries = copy(srcState, values);
917 		int entryIndex = 0;
918 		int valueIndex = 0;
919 		int insertPosition = -1;
920 
921 		// Reset the first n Entry objects that match this input name.
922 		//
923 		while (entryIndex < entries.size() && valueIndex < values.size()) {
924 			final ConfigLine e = entries.get(entryIndex);
925 			if (e.includedFrom == null && e.match(section, subsection, name)) {
926 				entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
927 				insertPosition = entryIndex + 1;
928 			}
929 			entryIndex++;
930 		}
931 
932 		// Remove any extra Entry objects that we no longer need.
933 		//
934 		if (valueIndex == values.size() && entryIndex < entries.size()) {
935 			while (entryIndex < entries.size()) {
936 				final ConfigLine e = entries.get(entryIndex++);
937 				if (e.includedFrom == null
938 						&& e.match(section, subsection, name))
939 					entries.remove(--entryIndex);
940 			}
941 		}
942 
943 		// Insert new Entry objects for additional/new values.
944 		//
945 		if (valueIndex < values.size() && entryIndex == entries.size()) {
946 			if (insertPosition < 0) {
947 				// We didn't find a matching key above, but maybe there
948 				// is already a section available that matches. Insert
949 				// after the last key of that section.
950 				//
951 				insertPosition = findSectionEnd(entries, section, subsection,
952 						true);
953 			}
954 			if (insertPosition < 0) {
955 				// We didn't find any matching section header for this key,
956 				// so we must create a new section header at the end.
957 				//
958 				final ConfigLineLine.html#ConfigLine">ConfigLine e = new ConfigLine();
959 				e.section = section;
960 				e.subsection = subsection;
961 				entries.add(e);
962 				insertPosition = entries.size();
963 			}
964 			while (valueIndex < values.size()) {
965 				final ConfigLineLine.html#ConfigLine">ConfigLine e = new ConfigLine();
966 				e.section = section;
967 				e.subsection = subsection;
968 				e.name = name;
969 				e.value = values.get(valueIndex++);
970 				entries.add(insertPosition++, e);
971 			}
972 		}
973 
974 		return newState(entries);
975 	}
976 
977 	private static List<ConfigLine> copy(final ConfigSnapshot src,
978 			final List<String> values) {
979 		// At worst we need to insert 1 line for each value, plus 1 line
980 		// for a new section header. Assume that and allocate the space.
981 		//
982 		final int max = src.entryList.size() + values.size() + 1;
983 		final ArrayList<ConfigLine> r = new ArrayList<>(max);
984 		r.addAll(src.entryList);
985 		return r;
986 	}
987 
988 	private static int findSectionEnd(final List<ConfigLine> entries,
989 			final String section, final String subsection,
990 			boolean skipIncludedLines) {
991 		for (int i = 0; i < entries.size(); i++) {
992 			ConfigLine e = entries.get(i);
993 			if (e.includedFrom != null && skipIncludedLines) {
994 				continue;
995 			}
996 
997 			if (e.match(section, subsection, null)) {
998 				i++;
999 				while (i < entries.size()) {
1000 					e = entries.get(i);
1001 					if (e.match(section, subsection, e.name))
1002 						i++;
1003 					else
1004 						break;
1005 				}
1006 				return i;
1007 			}
1008 		}
1009 		return -1;
1010 	}
1011 
1012 	/**
1013 	 * Get this configuration, formatted as a Git style text file.
1014 	 *
1015 	 * @return this configuration, formatted as a Git style text file.
1016 	 */
1017 	public String toText() {
1018 		final StringBuilder out = new StringBuilder();
1019 		for (ConfigLine e : state.get().entryList) {
1020 			if (e.includedFrom != null)
1021 				continue;
1022 			if (e.prefix != null)
1023 				out.append(e.prefix);
1024 			if (e.section != null && e.name == null) {
1025 				out.append('[');
1026 				out.append(e.section);
1027 				if (e.subsection != null) {
1028 					out.append(' ');
1029 					String escaped = escapeValue(e.subsection);
1030 					// make sure to avoid double quotes here
1031 					boolean quoted = escaped.startsWith("\"") //$NON-NLS-1$
1032 							&& escaped.endsWith("\""); //$NON-NLS-1$
1033 					if (!quoted)
1034 						out.append('"');
1035 					out.append(escaped);
1036 					if (!quoted)
1037 						out.append('"');
1038 				}
1039 				out.append(']');
1040 			} else if (e.section != null && e.name != null) {
1041 				if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$
1042 					out.append('\t');
1043 				out.append(e.name);
1044 				if (MAGIC_EMPTY_VALUE != e.value) {
1045 					out.append(" ="); //$NON-NLS-1$
1046 					if (e.value != null) {
1047 						out.append(' ');
1048 						out.append(escapeValue(e.value));
1049 					}
1050 				}
1051 				if (e.suffix != null)
1052 					out.append(' ');
1053 			}
1054 			if (e.suffix != null)
1055 				out.append(e.suffix);
1056 			out.append('\n');
1057 		}
1058 		return out.toString();
1059 	}
1060 
1061 	/**
1062 	 * Clear this configuration and reset to the contents of the parsed string.
1063 	 *
1064 	 * @param text
1065 	 *            Git style text file listing configuration properties.
1066 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
1067 	 *             the text supplied is not formatted correctly. No changes were
1068 	 *             made to {@code this}.
1069 	 */
1070 	public void fromText(String text) throws ConfigInvalidException {
1071 		state.set(newState(fromTextRecurse(text, 1, null)));
1072 	}
1073 
1074 	private List<ConfigLine> fromTextRecurse(String text, int depth,
1075 			String includedFrom) throws ConfigInvalidException {
1076 		if (depth > MAX_DEPTH) {
1077 			throw new ConfigInvalidException(
1078 					JGitText.get().tooManyIncludeRecursions);
1079 		}
1080 		final List<ConfigLine> newEntries = new ArrayList<>();
1081 		final StringReader in = new StringReader(text);
1082 		ConfigLine last = null;
1083 		ConfigLine e = new ConfigLine();
1084 		e.includedFrom = includedFrom;
1085 		for (;;) {
1086 			int input = in.read();
1087 			if (-1 == input) {
1088 				if (e.section != null)
1089 					newEntries.add(e);
1090 				break;
1091 			}
1092 
1093 			final char c = (char) input;
1094 			if ('\n' == c) {
1095 				// End of this entry.
1096 				newEntries.add(e);
1097 				if (e.section != null)
1098 					last = e;
1099 				e = new ConfigLine();
1100 				e.includedFrom = includedFrom;
1101 			} else if (e.suffix != null) {
1102 				// Everything up until the end-of-line is in the suffix.
1103 				e.suffix += c;
1104 
1105 			} else if (';' == c || '#' == c) {
1106 				// The rest of this line is a comment; put into suffix.
1107 				e.suffix = String.valueOf(c);
1108 
1109 			} else if (e.section == null && Character.isWhitespace(c)) {
1110 				// Save the leading whitespace (if any).
1111 				if (e.prefix == null)
1112 					e.prefix = ""; //$NON-NLS-1$
1113 				e.prefix += c;
1114 
1115 			} else if ('[' == c) {
1116 				// This is a section header.
1117 				e.section = readSectionName(in);
1118 				input = in.read();
1119 				if ('"' == input) {
1120 					e.subsection = readSubsectionName(in);
1121 					input = in.read();
1122 				}
1123 				if (']' != input)
1124 					throw new ConfigInvalidException(JGitText.get().badGroupHeader);
1125 				e.suffix = ""; //$NON-NLS-1$
1126 
1127 			} else if (last != null) {
1128 				// Read a value.
1129 				e.section = last.section;
1130 				e.subsection = last.subsection;
1131 				in.reset();
1132 				e.name = readKeyName(in);
1133 				if (e.name.endsWith("\n")) { //$NON-NLS-1$
1134 					e.name = e.name.substring(0, e.name.length() - 1);
1135 					e.value = MAGIC_EMPTY_VALUE;
1136 				} else
1137 					e.value = readValue(in);
1138 
1139 				if (e.section.equalsIgnoreCase("include")) { //$NON-NLS-1$
1140 					addIncludedConfig(newEntries, e, depth);
1141 				}
1142 			} else
1143 				throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
1144 		}
1145 
1146 		return newEntries;
1147 	}
1148 
1149 	/**
1150 	 * Read the included config from the specified (possibly) relative path
1151 	 *
1152 	 * @param relPath
1153 	 *            possibly relative path to the included config, as specified in
1154 	 *            this config
1155 	 * @return the read bytes, or null if the included config should be ignored
1156 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
1157 	 *             if something went wrong while reading the config
1158 	 * @since 4.10
1159 	 */
1160 	protected byte[] readIncludedConfig(String relPath)
1161 			throws ConfigInvalidException {
1162 		return null;
1163 	}
1164 
1165 	private void addIncludedConfig(final List<ConfigLine> newEntries,
1166 			ConfigLine line, int depth) throws ConfigInvalidException {
1167 		if (!line.name.equalsIgnoreCase("path") || //$NON-NLS-1$
1168 				line.value == null || line.value.equals(MAGIC_EMPTY_VALUE)) {
1169 			throw new ConfigInvalidException(MessageFormat.format(
1170 					JGitText.get().invalidLineInConfigFileWithParam, line));
1171 		}
1172 		byte[] bytes = readIncludedConfig(line.value);
1173 		if (bytes == null) {
1174 			return;
1175 		}
1176 
1177 		String decoded;
1178 		if (isUtf8(bytes)) {
1179 			decoded = RawParseUtils.decode(UTF_8, bytes, 3, bytes.length);
1180 		} else {
1181 			decoded = RawParseUtils.decode(bytes);
1182 		}
1183 		try {
1184 			newEntries.addAll(fromTextRecurse(decoded, depth + 1, line.value));
1185 		} catch (ConfigInvalidException e) {
1186 			throw new ConfigInvalidException(MessageFormat
1187 					.format(JGitText.get().cannotReadFile, line.value), e);
1188 		}
1189 	}
1190 
1191 	private ConfigSnapshot newState() {
1192 		return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
1193 				getBaseState());
1194 	}
1195 
1196 	private ConfigSnapshot newState(List<ConfigLine> entries) {
1197 		return new ConfigSnapshot(Collections.unmodifiableList(entries),
1198 				getBaseState());
1199 	}
1200 
1201 	/**
1202 	 * Clear the configuration file
1203 	 */
1204 	protected void clear() {
1205 		state.set(newState());
1206 	}
1207 
1208 	/**
1209 	 * Check if bytes should be treated as UTF-8 or not.
1210 	 *
1211 	 * @param bytes
1212 	 *            the bytes to check encoding for.
1213 	 * @return true if bytes should be treated as UTF-8, false otherwise.
1214 	 * @since 4.4
1215 	 */
1216 	protected boolean isUtf8(final byte[] bytes) {
1217 		return bytes.length >= 3 && bytes[0] == (byte) 0xEF
1218 				&& bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF;
1219 	}
1220 
1221 	private static String readSectionName(StringReader in)
1222 			throws ConfigInvalidException {
1223 		final StringBuilder name = new StringBuilder();
1224 		for (;;) {
1225 			int c = in.read();
1226 			if (c < 0)
1227 				throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1228 
1229 			if (']' == c) {
1230 				in.reset();
1231 				break;
1232 			}
1233 
1234 			if (' ' == c || '\t' == c) {
1235 				for (;;) {
1236 					c = in.read();
1237 					if (c < 0)
1238 						throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1239 
1240 					if ('"' == c) {
1241 						in.reset();
1242 						break;
1243 					}
1244 
1245 					if (' ' == c || '\t' == c)
1246 						continue; // Skipped...
1247 					throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
1248 				}
1249 				break;
1250 			}
1251 
1252 			if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
1253 				name.append((char) c);
1254 			else
1255 				throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
1256 		}
1257 		return name.toString();
1258 	}
1259 
1260 	private static String readKeyName(StringReader in)
1261 			throws ConfigInvalidException {
1262 		final StringBuilder name = new StringBuilder();
1263 		for (;;) {
1264 			int c = in.read();
1265 			if (c < 0)
1266 				throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1267 
1268 			if ('=' == c)
1269 				break;
1270 
1271 			if (' ' == c || '\t' == c) {
1272 				for (;;) {
1273 					c = in.read();
1274 					if (c < 0)
1275 						throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1276 
1277 					if ('=' == c)
1278 						break;
1279 
1280 					if (';' == c || '#' == c || '\n' == c) {
1281 						in.reset();
1282 						break;
1283 					}
1284 
1285 					if (' ' == c || '\t' == c)
1286 						continue; // Skipped...
1287 					throw new ConfigInvalidException(JGitText.get().badEntryDelimiter);
1288 				}
1289 				break;
1290 			}
1291 
1292 			if (Character.isLetterOrDigit((char) c) || c == '-') {
1293 				// From the git-config man page:
1294 				// The variable names are case-insensitive and only
1295 				// alphanumeric characters and - are allowed.
1296 				name.append((char) c);
1297 			} else if ('\n' == c) {
1298 				in.reset();
1299 				name.append((char) c);
1300 				break;
1301 			} else
1302 				throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name));
1303 		}
1304 		return name.toString();
1305 	}
1306 
1307 	private static String readSubsectionName(StringReader in)
1308 			throws ConfigInvalidException {
1309 		StringBuilder r = new StringBuilder();
1310 		for (;;) {
1311 			int c = in.read();
1312 			if (c < 0) {
1313 				break;
1314 			}
1315 
1316 			if ('\n' == c) {
1317 				throw new ConfigInvalidException(
1318 						JGitText.get().newlineInQuotesNotAllowed);
1319 			}
1320 			if ('\\' == c) {
1321 				c = in.read();
1322 				switch (c) {
1323 				case -1:
1324 					throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
1325 
1326 				case '\\':
1327 				case '"':
1328 					r.append((char) c);
1329 					continue;
1330 
1331 				default:
1332 					// C git simply drops backslashes if the escape sequence is not
1333 					// recognized.
1334 					r.append((char) c);
1335 					continue;
1336 				}
1337 			}
1338 			if ('"' == c) {
1339 				break;
1340 			}
1341 
1342 			r.append((char) c);
1343 		}
1344 		return r.toString();
1345 	}
1346 
1347 	private static String readValue(StringReader in)
1348 			throws ConfigInvalidException {
1349 		StringBuilder value = new StringBuilder();
1350 		StringBuilder trailingSpaces = null;
1351 		boolean quote = false;
1352 		boolean inLeadingSpace = true;
1353 
1354 		for (;;) {
1355 			int c = in.read();
1356 			if (c < 0) {
1357 				break;
1358 			}
1359 			if ('\n' == c) {
1360 				if (quote) {
1361 					throw new ConfigInvalidException(
1362 							JGitText.get().newlineInQuotesNotAllowed);
1363 				}
1364 				in.reset();
1365 				break;
1366 			}
1367 
1368 			if (!quote && (';' == c || '#' == c)) {
1369 				if (trailingSpaces != null) {
1370 					trailingSpaces.setLength(0);
1371 				}
1372 				in.reset();
1373 				break;
1374 			}
1375 
1376 			char cc = (char) c;
1377 			if (Character.isWhitespace(cc)) {
1378 				if (inLeadingSpace) {
1379 					continue;
1380 				}
1381 				if (trailingSpaces == null) {
1382 					trailingSpaces = new StringBuilder();
1383 				}
1384 				trailingSpaces.append(cc);
1385 				continue;
1386 			} else {
1387 				inLeadingSpace = false;
1388 				if (trailingSpaces != null) {
1389 					value.append(trailingSpaces);
1390 					trailingSpaces.setLength(0);
1391 				}
1392 			}
1393 
1394 			if ('\\' == c) {
1395 				c = in.read();
1396 				switch (c) {
1397 				case -1:
1398 					throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
1399 				case '\n':
1400 					continue;
1401 				case 't':
1402 					value.append('\t');
1403 					continue;
1404 				case 'b':
1405 					value.append('\b');
1406 					continue;
1407 				case 'n':
1408 					value.append('\n');
1409 					continue;
1410 				case '\\':
1411 					value.append('\\');
1412 					continue;
1413 				case '"':
1414 					value.append('"');
1415 					continue;
1416 				default:
1417 					throw new ConfigInvalidException(MessageFormat.format(
1418 							JGitText.get().badEscape,
1419 							Character.valueOf(((char) c))));
1420 				}
1421 			}
1422 
1423 			if ('"' == c) {
1424 				quote = !quote;
1425 				continue;
1426 			}
1427 
1428 			value.append(cc);
1429 		}
1430 		return value.length() > 0 ? value.toString() : null;
1431 	}
1432 
1433 	/**
1434 	 * Parses a section of the configuration into an application model object.
1435 	 * <p>
1436 	 * Instances must implement hashCode and equals such that model objects can
1437 	 * be cached by using the {@code SectionParser} as a key of a HashMap.
1438 	 * <p>
1439 	 * As the {@code SectionParser} itself is used as the key of the internal
1440 	 * HashMap applications should be careful to ensure the SectionParser key
1441 	 * does not retain unnecessary application state which may cause memory to
1442 	 * be held longer than expected.
1443 	 *
1444 	 * @param <T>
1445 	 *            type of the application model created by the parser.
1446 	 */
1447 	public static interface SectionParser<T> {
1448 		/**
1449 		 * Create a model object from a configuration.
1450 		 *
1451 		 * @param cfg
1452 		 *            the configuration to read values from.
1453 		 * @return the application model instance.
1454 		 */
1455 		T parse(Config cfg);
1456 	}
1457 
1458 	private static class StringReader {
1459 		private final char[] buf;
1460 
1461 		private int pos;
1462 
1463 		StringReader(String in) {
1464 			buf = in.toCharArray();
1465 		}
1466 
1467 		int read() {
1468 			if (pos >= buf.length) {
1469 				return -1;
1470 			}
1471 			return buf[pos++];
1472 		}
1473 
1474 		void reset() {
1475 			pos--;
1476 		}
1477 	}
1478 
1479 	/**
1480 	 * Converts enumeration values into configuration options and vice-versa,
1481 	 * allowing to match a config option with an enum value.
1482 	 *
1483 	 */
1484 	public static interface ConfigEnum {
1485 		/**
1486 		 * Converts enumeration value into a string to be save in config.
1487 		 *
1488 		 * @return the enum value as config string
1489 		 */
1490 		String toConfigValue();
1491 
1492 		/**
1493 		 * Checks if the given string matches with enum value.
1494 		 *
1495 		 * @param in
1496 		 *            the string to match
1497 		 * @return true if the given string matches enum value, false otherwise
1498 		 */
1499 		boolean matchConfigValue(String in);
1500 	}
1501 }