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