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