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 java.text.MessageFormat;
55  import java.util.ArrayList;
56  import java.util.Collections;
57  import java.util.List;
58  import java.util.Set;
59  import java.util.concurrent.atomic.AtomicReference;
60  
61  import org.eclipse.jgit.errors.ConfigInvalidException;
62  import org.eclipse.jgit.events.ConfigChangedEvent;
63  import org.eclipse.jgit.events.ConfigChangedListener;
64  import org.eclipse.jgit.events.ListenerHandle;
65  import org.eclipse.jgit.events.ListenerList;
66  import org.eclipse.jgit.internal.JGitText;
67  import org.eclipse.jgit.util.StringUtils;
68  
69  
70  /**
71   * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
72   */
73  public class Config {
74  	private static final String[] EMPTY_STRING_ARRAY = {};
75  	private static final long KiB = 1024;
76  	private static final long MiB = 1024 * KiB;
77  	private static final long GiB = 1024 * MiB;
78  
79  	/** the change listeners */
80  	private final ListenerList listeners = new ListenerList();
81  
82  	/**
83  	 * Immutable current state of the configuration data.
84  	 * <p>
85  	 * This state is copy-on-write. It should always contain an immutable list
86  	 * of the configuration keys/values.
87  	 */
88  	private final AtomicReference<ConfigSnapshot> state;
89  
90  	private final Config baseConfig;
91  
92  	/**
93  	 * Magic value indicating a missing entry.
94  	 * <p>
95  	 * This value is tested for reference equality in some contexts, so we
96  	 * must ensure it is a special copy of the empty string.  It also must
97  	 * be treated like the empty string.
98  	 */
99  	private static final String MAGIC_EMPTY_VALUE = new String();
100 
101 	/** Create a configuration with no default fallback. */
102 	public Config() {
103 		this(null);
104 	}
105 
106 	/**
107 	 * Create an empty configuration with a fallback for missing keys.
108 	 *
109 	 * @param defaultConfig
110 	 *            the base configuration to be consulted when a key is missing
111 	 *            from this configuration instance.
112 	 */
113 	public Config(Config defaultConfig) {
114 		baseConfig = defaultConfig;
115 		state = new AtomicReference<ConfigSnapshot>(newState());
116 	}
117 
118 	/**
119 	 * Escape the value before saving
120 	 *
121 	 * @param x
122 	 *            the value to escape
123 	 * @return the escaped value
124 	 */
125 	private static String escapeValue(final String x) {
126 		boolean inquote = false;
127 		int lineStart = 0;
128 		final StringBuilder r = new StringBuilder(x.length());
129 		for (int k = 0; k < x.length(); k++) {
130 			final char c = x.charAt(k);
131 			switch (c) {
132 			case '\n':
133 				if (inquote) {
134 					r.append('"');
135 					inquote = false;
136 				}
137 				r.append("\\n\\\n"); //$NON-NLS-1$
138 				lineStart = r.length();
139 				break;
140 
141 			case '\t':
142 				r.append("\\t"); //$NON-NLS-1$
143 				break;
144 
145 			case '\b':
146 				r.append("\\b"); //$NON-NLS-1$
147 				break;
148 
149 			case '\\':
150 				r.append("\\\\"); //$NON-NLS-1$
151 				break;
152 
153 			case '"':
154 				r.append("\\\""); //$NON-NLS-1$
155 				break;
156 
157 			case ';':
158 			case '#':
159 				if (!inquote) {
160 					r.insert(lineStart, '"');
161 					inquote = true;
162 				}
163 				r.append(c);
164 				break;
165 
166 			case ' ':
167 				if (!inquote && r.length() > 0
168 						&& r.charAt(r.length() - 1) == ' ') {
169 					r.insert(lineStart, '"');
170 					inquote = true;
171 				}
172 				r.append(' ');
173 				break;
174 
175 			default:
176 				r.append(c);
177 				break;
178 			}
179 		}
180 		if (inquote) {
181 			r.append('"');
182 		}
183 		return r.toString();
184 	}
185 
186 	/**
187 	 * Obtain an integer value from the configuration.
188 	 *
189 	 * @param section
190 	 *            section the key is grouped within.
191 	 * @param name
192 	 *            name of the key to get.
193 	 * @param defaultValue
194 	 *            default value to return if no value was present.
195 	 * @return an integer value from the configuration, or defaultValue.
196 	 */
197 	public int getInt(final String section, final String name,
198 			final int defaultValue) {
199 		return getInt(section, null, name, defaultValue);
200 	}
201 
202 	/**
203 	 * Obtain an integer value from the configuration.
204 	 *
205 	 * @param section
206 	 *            section the key is grouped within.
207 	 * @param subsection
208 	 *            subsection name, such a remote or branch name.
209 	 * @param name
210 	 *            name of the key to get.
211 	 * @param defaultValue
212 	 *            default value to return if no value was present.
213 	 * @return an integer value from the configuration, or defaultValue.
214 	 */
215 	public int getInt(final String section, String subsection,
216 			final String name, final int defaultValue) {
217 		final long val = getLong(section, subsection, name, defaultValue);
218 		if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE)
219 			return (int) val;
220 		throw new IllegalArgumentException(MessageFormat.format(JGitText.get().integerValueOutOfRange
221 				, section, name));
222 	}
223 
224 	/**
225 	 * Obtain an integer value from the configuration.
226 	 *
227 	 * @param section
228 	 *            section the key is grouped within.
229 	 * @param name
230 	 *            name of the key to get.
231 	 * @param defaultValue
232 	 *            default value to return if no value was present.
233 	 * @return an integer value from the configuration, or defaultValue.
234 	 */
235 	public long getLong(String section, String name, long defaultValue) {
236 		return getLong(section, null, name, defaultValue);
237 	}
238 
239 	/**
240 	 * Obtain an integer value from the configuration.
241 	 *
242 	 * @param section
243 	 *            section the key is grouped within.
244 	 * @param subsection
245 	 *            subsection name, such a remote or branch name.
246 	 * @param name
247 	 *            name of the key to get.
248 	 * @param defaultValue
249 	 *            default value to return if no value was present.
250 	 * @return an integer value from the configuration, or defaultValue.
251 	 */
252 	public long getLong(final String section, String subsection,
253 			final String name, final long defaultValue) {
254 		final String str = getString(section, subsection, name);
255 		if (str == null)
256 			return defaultValue;
257 
258 		String n = str.trim();
259 		if (n.length() == 0)
260 			return defaultValue;
261 
262 		long mul = 1;
263 		switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) {
264 		case 'g':
265 			mul = GiB;
266 			break;
267 		case 'm':
268 			mul = MiB;
269 			break;
270 		case 'k':
271 			mul = KiB;
272 			break;
273 		}
274 		if (mul > 1)
275 			n = n.substring(0, n.length() - 1).trim();
276 		if (n.length() == 0)
277 			return defaultValue;
278 
279 		try {
280 			return mul * Long.parseLong(n);
281 		} catch (NumberFormatException nfe) {
282 			throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidIntegerValue
283 					, section, name, str));
284 		}
285 	}
286 
287 	/**
288 	 * Get a boolean value from the git config
289 	 *
290 	 * @param section
291 	 *            section the key is grouped within.
292 	 * @param name
293 	 *            name of the key to get.
294 	 * @param defaultValue
295 	 *            default value to return if no value was present.
296 	 * @return true if any value or defaultValue is true, false for missing or
297 	 *         explicit false
298 	 */
299 	public boolean getBoolean(final String section, final String name,
300 			final boolean defaultValue) {
301 		return getBoolean(section, null, name, defaultValue);
302 	}
303 
304 	/**
305 	 * Get a boolean value from the git config
306 	 *
307 	 * @param section
308 	 *            section the key is grouped within.
309 	 * @param subsection
310 	 *            subsection name, such a remote or branch name.
311 	 * @param name
312 	 *            name of the key to get.
313 	 * @param defaultValue
314 	 *            default value to return if no value was present.
315 	 * @return true if any value or defaultValue is true, false for missing or
316 	 *         explicit false
317 	 */
318 	public boolean getBoolean(final String section, String subsection,
319 			final String name, final boolean defaultValue) {
320 		String n = getRawString(section, subsection, name);
321 		if (n == null)
322 			return defaultValue;
323 		if (MAGIC_EMPTY_VALUE == n)
324 			return true;
325 		try {
326 			return StringUtils.toBoolean(n);
327 		} catch (IllegalArgumentException err) {
328 			throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidBooleanValue
329 					, section, name, n));
330 		}
331 	}
332 
333 	/**
334 	 * Parse an enumeration from the configuration.
335 	 *
336 	 * @param <T>
337 	 *            type of the enumeration object.
338 	 * @param section
339 	 *            section the key is grouped within.
340 	 * @param subsection
341 	 *            subsection name, such a remote or branch name.
342 	 * @param name
343 	 *            name of the key to get.
344 	 * @param defaultValue
345 	 *            default value to return if no value was present.
346 	 * @return the selected enumeration value, or {@code defaultValue}.
347 	 */
348 	public <T extends Enum<?>> T getEnum(final String section,
349 			final String subsection, final String name, final T defaultValue) {
350 		final T[] all = allValuesOf(defaultValue);
351 		return getEnum(all, section, subsection, name, defaultValue);
352 	}
353 
354 	@SuppressWarnings("unchecked")
355 	private static <T> T[] allValuesOf(final T value) {
356 		try {
357 			return (T[]) value.getClass().getMethod("values").invoke(null); //$NON-NLS-1$
358 		} catch (Exception err) {
359 			String typeName = value.getClass().getName();
360 			String msg = MessageFormat.format(
361 					JGitText.get().enumValuesNotAvailable, typeName);
362 			throw new IllegalArgumentException(msg, err);
363 		}
364 	}
365 
366 	/**
367 	 * Parse an enumeration from the configuration.
368 	 *
369 	 * @param <T>
370 	 *            type of the enumeration object.
371 	 * @param all
372 	 *            all possible values in the enumeration which should be
373 	 *            recognized. Typically {@code EnumType.values()}.
374 	 * @param section
375 	 *            section the key is grouped within.
376 	 * @param subsection
377 	 *            subsection name, such a remote or branch name.
378 	 * @param name
379 	 *            name of the key to get.
380 	 * @param defaultValue
381 	 *            default value to return if no value was present.
382 	 * @return the selected enumeration value, or {@code defaultValue}.
383 	 */
384 	public <T extends Enum<?>> T getEnum(final T[] all, final String section,
385 			final String subsection, final String name, final T defaultValue) {
386 		String value = getString(section, subsection, name);
387 		if (value == null)
388 			return defaultValue;
389 
390 		if (all[0] instanceof ConfigEnum) {
391 			for (T t : all) {
392 				if (((ConfigEnum) t).matchConfigValue(value))
393 					return t;
394 			}
395 		}
396 
397 		String n = value.replace(' ', '_');
398 
399 		// Because of c98abc9c0586c73ef7df4172644b7dd21c979e9d being used in
400 		// the real world before its breakage was fully understood, we must
401 		// also accept '-' as though it were ' '.
402 		n = n.replace('-', '_');
403 
404 		T trueState = null;
405 		T falseState = null;
406 		for (T e : all) {
407 			if (StringUtils.equalsIgnoreCase(e.name(), n))
408 				return e;
409 			else if (StringUtils.equalsIgnoreCase(e.name(), "TRUE")) //$NON-NLS-1$
410 				trueState = e;
411 			else if (StringUtils.equalsIgnoreCase(e.name(), "FALSE")) //$NON-NLS-1$
412 				falseState = e;
413 		}
414 
415 		// This is an odd little fallback. C Git sometimes allows boolean
416 		// values in a tri-state with other things. If we have both a true
417 		// and a false value in our enumeration, assume its one of those.
418 		//
419 		if (trueState != null && falseState != null) {
420 			try {
421 				return StringUtils.toBoolean(n) ? trueState : falseState;
422 			} catch (IllegalArgumentException err) {
423 				// Fall through and use our custom error below.
424 			}
425 		}
426 
427 		if (subsection != null)
428 			throw new IllegalArgumentException(MessageFormat.format(
429 					JGitText.get().enumValueNotSupported3, section, subsection,
430 					name, value));
431 		else
432 			throw new IllegalArgumentException(
433 					MessageFormat.format(JGitText.get().enumValueNotSupported2,
434 							section, name, value));
435 	}
436 
437 	/**
438 	 * Get string value or null if not found.
439 	 *
440 	 * @param section
441 	 *            the section
442 	 * @param subsection
443 	 *            the subsection for the value
444 	 * @param name
445 	 *            the key name
446 	 * @return a String value from the config, <code>null</code> if not found
447 	 */
448 	public String getString(final String section, String subsection,
449 			final String name) {
450 		return getRawString(section, subsection, name);
451 	}
452 
453 	/**
454 	 * Get a list of string values
455 	 * <p>
456 	 * If this instance was created with a base, the base's values are returned
457 	 * first (if any).
458 	 *
459 	 * @param section
460 	 *            the section
461 	 * @param subsection
462 	 *            the subsection for the value
463 	 * @param name
464 	 *            the key name
465 	 * @return array of zero or more values from the configuration.
466 	 */
467 	public String[] getStringList(final String section, String subsection,
468 			final String name) {
469 		String[] base;
470 		if (baseConfig != null)
471 			base = baseConfig.getStringList(section, subsection, name);
472 		else
473 			base = EMPTY_STRING_ARRAY;
474 
475 		String[] self = getRawStringList(section, subsection, name);
476 		if (self == null)
477 			return base;
478 		if (base.length == 0)
479 			return self;
480 		String[] res = new String[base.length + self.length];
481 		int n = base.length;
482 		System.arraycopy(base, 0, res, 0, n);
483 		System.arraycopy(self, 0, res, n, self.length);
484 		return res;
485 	}
486 
487 	/**
488 	 * @param section
489 	 *            section to search for.
490 	 * @return set of all subsections of specified section within this
491 	 *         configuration and its base configuration; may be empty if no
492 	 *         subsection exists. The set's iterator returns sections in the
493 	 *         order they are declared by the configuration starting from this
494 	 *         instance and progressing through the base.
495 	 */
496 	public Set<String> getSubsections(final String section) {
497 		return getState().getSubsections(section);
498 	}
499 
500 	/**
501 	 * @return the sections defined in this {@link Config}. The set's iterator
502 	 *         returns sections in the order they are declared by the
503 	 *         configuration starting from this instance and progressing through
504 	 *         the base.
505 	 */
506 	public Set<String> getSections() {
507 		return getState().getSections();
508 	}
509 
510 	/**
511 	 * @param section
512 	 *            the section
513 	 * @return the list of names defined for this section
514 	 */
515 	public Set<String> getNames(String section) {
516 		return getNames(section, null);
517 	}
518 
519 	/**
520 	 * @param section
521 	 *            the section
522 	 * @param subsection
523 	 *            the subsection
524 	 * @return the list of names defined for this subsection
525 	 */
526 	public Set<String> getNames(String section, String subsection) {
527 		return getState().getNames(section, subsection);
528 	}
529 
530 	/**
531 	 * @param section
532 	 *            the section
533 	 * @param recursive
534 	 *            if {@code true} recursively adds the names defined in all base
535 	 *            configurations
536 	 * @return the list of names defined for this section
537 	 * @since 3.2
538 	 */
539 	public Set<String> getNames(String section, boolean recursive) {
540 		return getState().getNames(section, null, recursive);
541 	}
542 
543 	/**
544 	 * @param section
545 	 *            the section
546 	 * @param subsection
547 	 *            the subsection
548 	 * @param recursive
549 	 *            if {@code true} recursively adds the names defined in all base
550 	 *            configurations
551 	 * @return the list of names defined for this subsection
552 	 * @since 3.2
553 	 */
554 	public Set<String> getNames(String section, String subsection,
555 			boolean recursive) {
556 		return getState().getNames(section, subsection, recursive);
557 	}
558 
559 	/**
560 	 * Obtain a handle to a parsed set of configuration values.
561 	 *
562 	 * @param <T>
563 	 *            type of configuration model to return.
564 	 * @param parser
565 	 *            parser which can create the model if it is not already
566 	 *            available in this configuration file. The parser is also used
567 	 *            as the key into a cache and must obey the hashCode and equals
568 	 *            contract in order to reuse a parsed model.
569 	 * @return the parsed object instance, which is cached inside this config.
570 	 */
571 	@SuppressWarnings("unchecked")
572 	public <T> T get(final SectionParser<T> parser) {
573 		final ConfigSnapshot myState = getState();
574 		T obj = (T) myState.cache.get(parser);
575 		if (obj == null) {
576 			obj = parser.parse(this);
577 			myState.cache.put(parser, obj);
578 		}
579 		return obj;
580 	}
581 
582 	/**
583 	 * Remove a cached configuration object.
584 	 * <p>
585 	 * If the associated configuration object has not yet been cached, this
586 	 * method has no effect.
587 	 *
588 	 * @param parser
589 	 *            parser used to obtain the configuration object.
590 	 * @see #get(SectionParser)
591 	 */
592 	public void uncache(final SectionParser<?> parser) {
593 		state.get().cache.remove(parser);
594 	}
595 
596 	/**
597 	 * Adds a listener to be notified about changes.
598 	 * <p>
599 	 * Clients are supposed to remove the listeners after they are done with
600 	 * them using the {@link ListenerHandle#remove()} method
601 	 *
602 	 * @param listener
603 	 *            the listener
604 	 * @return the handle to the registered listener
605 	 */
606 	public ListenerHandle addChangeListener(ConfigChangedListener listener) {
607 		return listeners.addConfigChangedListener(listener);
608 	}
609 
610 	/**
611 	 * Determine whether to issue change events for transient changes.
612 	 * <p>
613 	 * If <code>true</code> is returned (which is the default behavior),
614 	 * {@link #fireConfigChangedEvent()} will be called upon each change.
615 	 * <p>
616 	 * Subclasses that override this to return <code>false</code> are
617 	 * responsible for issuing {@link #fireConfigChangedEvent()} calls
618 	 * themselves.
619 	 *
620 	 * @return <code></code>
621 	 */
622 	protected boolean notifyUponTransientChanges() {
623 		return true;
624 	}
625 
626 	/**
627 	 * Notifies the listeners
628 	 */
629 	protected void fireConfigChangedEvent() {
630 		listeners.dispatch(new ConfigChangedEvent());
631 	}
632 
633 	private String getRawString(final String section, final String subsection,
634 			final String name) {
635 		String[] lst = getRawStringList(section, subsection, name);
636 		if (lst != null)
637 			return lst[0];
638 		else if (baseConfig != null)
639 			return baseConfig.getRawString(section, subsection, name);
640 		else
641 			return null;
642 	}
643 
644 	private String[] getRawStringList(String section, String subsection,
645 			String name) {
646 		return state.get().get(section, subsection, name);
647 	}
648 
649 	private ConfigSnapshot getState() {
650 		ConfigSnapshot cur, upd;
651 		do {
652 			cur = state.get();
653 			final ConfigSnapshot base = getBaseState();
654 			if (cur.baseState == base)
655 				return cur;
656 			upd = new ConfigSnapshot(cur.entryList, base);
657 		} while (!state.compareAndSet(cur, upd));
658 		return upd;
659 	}
660 
661 	private ConfigSnapshot getBaseState() {
662 		return baseConfig != null ? baseConfig.getState() : null;
663 	}
664 
665 	/**
666 	 * Add or modify a configuration value. The parameters will result in a
667 	 * configuration entry like this.
668 	 *
669 	 * <pre>
670 	 * [section &quot;subsection&quot;]
671 	 *         name = value
672 	 * </pre>
673 	 *
674 	 * @param section
675 	 *            section name, e.g "branch"
676 	 * @param subsection
677 	 *            optional subsection value, e.g. a branch name
678 	 * @param name
679 	 *            parameter name, e.g. "filemode"
680 	 * @param value
681 	 *            parameter value
682 	 */
683 	public void setInt(final String section, final String subsection,
684 			final String name, final int value) {
685 		setLong(section, subsection, name, value);
686 	}
687 
688 	/**
689 	 * Add or modify a configuration value. The parameters will result in a
690 	 * configuration entry like this.
691 	 *
692 	 * <pre>
693 	 * [section &quot;subsection&quot;]
694 	 *         name = value
695 	 * </pre>
696 	 *
697 	 * @param section
698 	 *            section name, e.g "branch"
699 	 * @param subsection
700 	 *            optional subsection value, e.g. a branch name
701 	 * @param name
702 	 *            parameter name, e.g. "filemode"
703 	 * @param value
704 	 *            parameter value
705 	 */
706 	public void setLong(final String section, final String subsection,
707 			final String name, final long value) {
708 		final String s;
709 
710 		if (value >= GiB && (value % GiB) == 0)
711 			s = String.valueOf(value / GiB) + " g"; //$NON-NLS-1$
712 		else if (value >= MiB && (value % MiB) == 0)
713 			s = String.valueOf(value / MiB) + " m"; //$NON-NLS-1$
714 		else if (value >= KiB && (value % KiB) == 0)
715 			s = String.valueOf(value / KiB) + " k"; //$NON-NLS-1$
716 		else
717 			s = String.valueOf(value);
718 
719 		setString(section, subsection, name, s);
720 	}
721 
722 	/**
723 	 * Add or modify a configuration value. The parameters will result in a
724 	 * configuration entry like this.
725 	 *
726 	 * <pre>
727 	 * [section &quot;subsection&quot;]
728 	 *         name = value
729 	 * </pre>
730 	 *
731 	 * @param section
732 	 *            section name, e.g "branch"
733 	 * @param subsection
734 	 *            optional subsection value, e.g. a branch name
735 	 * @param name
736 	 *            parameter name, e.g. "filemode"
737 	 * @param value
738 	 *            parameter value
739 	 */
740 	public void setBoolean(final String section, final String subsection,
741 			final String name, final boolean value) {
742 		setString(section, subsection, name, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
743 	}
744 
745 	/**
746 	 * Add or modify a configuration value. The parameters will result in a
747 	 * configuration entry like this.
748 	 *
749 	 * <pre>
750 	 * [section &quot;subsection&quot;]
751 	 *         name = value
752 	 * </pre>
753 	 *
754 	 * @param <T>
755 	 *            type of the enumeration object.
756 	 * @param section
757 	 *            section name, e.g "branch"
758 	 * @param subsection
759 	 *            optional subsection value, e.g. a branch name
760 	 * @param name
761 	 *            parameter name, e.g. "filemode"
762 	 * @param value
763 	 *            parameter value
764 	 */
765 	public <T extends Enum<?>> void setEnum(final String section,
766 			final String subsection, final String name, final T value) {
767 		String n;
768 		if (value instanceof ConfigEnum)
769 			n = ((ConfigEnum) value).toConfigValue();
770 		else
771 			n = value.name().toLowerCase().replace('_', ' ');
772 		setString(section, subsection, name, n);
773 	}
774 
775 	/**
776 	 * Add or modify a configuration value. The parameters will result in a
777 	 * configuration entry like this.
778 	 *
779 	 * <pre>
780 	 * [section &quot;subsection&quot;]
781 	 *         name = value
782 	 * </pre>
783 	 *
784 	 * @param section
785 	 *            section name, e.g "branch"
786 	 * @param subsection
787 	 *            optional subsection value, e.g. a branch name
788 	 * @param name
789 	 *            parameter name, e.g. "filemode"
790 	 * @param value
791 	 *            parameter value, e.g. "true"
792 	 */
793 	public void setString(final String section, final String subsection,
794 			final String name, final String value) {
795 		setStringList(section, subsection, name, Collections
796 				.singletonList(value));
797 	}
798 
799 	/**
800 	 * Remove a configuration value.
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 	 */
809 	public void unset(final String section, final String subsection,
810 			final String name) {
811 		setStringList(section, subsection, name, Collections
812 				.<String> emptyList());
813 	}
814 
815 	/**
816 	 * Remove all configuration values under a single section.
817 	 *
818 	 * @param section
819 	 *            section name, e.g "branch"
820 	 * @param subsection
821 	 *            optional subsection value, e.g. a branch name
822 	 */
823 	public void unsetSection(String section, String subsection) {
824 		ConfigSnapshot src, res;
825 		do {
826 			src = state.get();
827 			res = unsetSection(src, section, subsection);
828 		} while (!state.compareAndSet(src, res));
829 	}
830 
831 	private ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
832 			final String section,
833 			final String subsection) {
834 		final int max = srcState.entryList.size();
835 		final ArrayList<ConfigLine> r = new ArrayList<ConfigLine>(max);
836 
837 		boolean lastWasMatch = false;
838 		for (ConfigLine e : srcState.entryList) {
839 			if (e.match(section, subsection)) {
840 				// Skip this record, it's for the section we are removing.
841 				lastWasMatch = true;
842 				continue;
843 			}
844 
845 			if (lastWasMatch && e.section == null && e.subsection == null)
846 				continue; // skip this padding line in the section.
847 			r.add(e);
848 		}
849 
850 		return newState(r);
851 	}
852 
853 	/**
854 	 * Set a configuration value.
855 	 *
856 	 * <pre>
857 	 * [section &quot;subsection&quot;]
858 	 *         name = value
859 	 * </pre>
860 	 *
861 	 * @param section
862 	 *            section name, e.g "branch"
863 	 * @param subsection
864 	 *            optional subsection value, e.g. a branch name
865 	 * @param name
866 	 *            parameter name, e.g. "filemode"
867 	 * @param values
868 	 *            list of zero or more values for this key.
869 	 */
870 	public void setStringList(final String section, final String subsection,
871 			final String name, final List<String> values) {
872 		ConfigSnapshot src, res;
873 		do {
874 			src = state.get();
875 			res = replaceStringList(src, section, subsection, name, values);
876 		} while (!state.compareAndSet(src, res));
877 		if (notifyUponTransientChanges())
878 			fireConfigChangedEvent();
879 	}
880 
881 	private ConfigSnapshot replaceStringList(final ConfigSnapshot srcState,
882 			final String section, final String subsection, final String name,
883 			final List<String> values) {
884 		final List<ConfigLine> entries = copy(srcState, values);
885 		int entryIndex = 0;
886 		int valueIndex = 0;
887 		int insertPosition = -1;
888 
889 		// Reset the first n Entry objects that match this input name.
890 		//
891 		while (entryIndex < entries.size() && valueIndex < values.size()) {
892 			final ConfigLine e = entries.get(entryIndex);
893 			if (e.match(section, subsection, name)) {
894 				entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
895 				insertPosition = entryIndex + 1;
896 			}
897 			entryIndex++;
898 		}
899 
900 		// Remove any extra Entry objects that we no longer need.
901 		//
902 		if (valueIndex == values.size() && entryIndex < entries.size()) {
903 			while (entryIndex < entries.size()) {
904 				final ConfigLine e = entries.get(entryIndex++);
905 				if (e.match(section, subsection, name))
906 					entries.remove(--entryIndex);
907 			}
908 		}
909 
910 		// Insert new Entry objects for additional/new values.
911 		//
912 		if (valueIndex < values.size() && entryIndex == entries.size()) {
913 			if (insertPosition < 0) {
914 				// We didn't find a matching key above, but maybe there
915 				// is already a section available that matches. Insert
916 				// after the last key of that section.
917 				//
918 				insertPosition = findSectionEnd(entries, section, subsection);
919 			}
920 			if (insertPosition < 0) {
921 				// We didn't find any matching section header for this key,
922 				// so we must create a new section header at the end.
923 				//
924 				final ConfigLine e = new ConfigLine();
925 				e.section = section;
926 				e.subsection = subsection;
927 				entries.add(e);
928 				insertPosition = entries.size();
929 			}
930 			while (valueIndex < values.size()) {
931 				final ConfigLine e = new ConfigLine();
932 				e.section = section;
933 				e.subsection = subsection;
934 				e.name = name;
935 				e.value = values.get(valueIndex++);
936 				entries.add(insertPosition++, e);
937 			}
938 		}
939 
940 		return newState(entries);
941 	}
942 
943 	private static List<ConfigLine> copy(final ConfigSnapshot src,
944 			final List<String> values) {
945 		// At worst we need to insert 1 line for each value, plus 1 line
946 		// for a new section header. Assume that and allocate the space.
947 		//
948 		final int max = src.entryList.size() + values.size() + 1;
949 		final ArrayList<ConfigLine> r = new ArrayList<ConfigLine>(max);
950 		r.addAll(src.entryList);
951 		return r;
952 	}
953 
954 	private static int findSectionEnd(final List<ConfigLine> entries,
955 			final String section, final String subsection) {
956 		for (int i = 0; i < entries.size(); i++) {
957 			ConfigLine e = entries.get(i);
958 			if (e.match(section, subsection, null)) {
959 				i++;
960 				while (i < entries.size()) {
961 					e = entries.get(i);
962 					if (e.match(section, subsection, e.name))
963 						i++;
964 					else
965 						break;
966 				}
967 				return i;
968 			}
969 		}
970 		return -1;
971 	}
972 
973 	/**
974 	 * @return this configuration, formatted as a Git style text file.
975 	 */
976 	public String toText() {
977 		final StringBuilder out = new StringBuilder();
978 		for (final ConfigLine e : state.get().entryList) {
979 			if (e.prefix != null)
980 				out.append(e.prefix);
981 			if (e.section != null && e.name == null) {
982 				out.append('[');
983 				out.append(e.section);
984 				if (e.subsection != null) {
985 					out.append(' ');
986 					String escaped = escapeValue(e.subsection);
987 					// make sure to avoid double quotes here
988 					boolean quoted = escaped.startsWith("\"") //$NON-NLS-1$
989 							&& escaped.endsWith("\""); //$NON-NLS-1$
990 					if (!quoted)
991 						out.append('"');
992 					out.append(escaped);
993 					if (!quoted)
994 						out.append('"');
995 				}
996 				out.append(']');
997 			} else if (e.section != null && e.name != null) {
998 				if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$
999 					out.append('\t');
1000 				out.append(e.name);
1001 				if (MAGIC_EMPTY_VALUE != e.value) {
1002 					out.append(" ="); //$NON-NLS-1$
1003 					if (e.value != null) {
1004 						out.append(' ');
1005 						out.append(escapeValue(e.value));
1006 					}
1007 				}
1008 				if (e.suffix != null)
1009 					out.append(' ');
1010 			}
1011 			if (e.suffix != null)
1012 				out.append(e.suffix);
1013 			out.append('\n');
1014 		}
1015 		return out.toString();
1016 	}
1017 
1018 	/**
1019 	 * Clear this configuration and reset to the contents of the parsed string.
1020 	 *
1021 	 * @param text
1022 	 *            Git style text file listing configuration properties.
1023 	 * @throws ConfigInvalidException
1024 	 *             the text supplied is not formatted correctly. No changes were
1025 	 *             made to {@code this}.
1026 	 */
1027 	public void fromText(final String text) throws ConfigInvalidException {
1028 		final List<ConfigLine> newEntries = new ArrayList<ConfigLine>();
1029 		final StringReader in = new StringReader(text);
1030 		ConfigLine last = null;
1031 		ConfigLine e = new ConfigLine();
1032 		for (;;) {
1033 			int input = in.read();
1034 			if (-1 == input) {
1035 				if (e.section != null)
1036 					newEntries.add(e);
1037 				break;
1038 			}
1039 
1040 			final char c = (char) input;
1041 			if ('\n' == c) {
1042 				// End of this entry.
1043 				newEntries.add(e);
1044 				if (e.section != null)
1045 					last = e;
1046 				e = new ConfigLine();
1047 
1048 			} else if (e.suffix != null) {
1049 				// Everything up until the end-of-line is in the suffix.
1050 				e.suffix += c;
1051 
1052 			} else if (';' == c || '#' == c) {
1053 				// The rest of this line is a comment; put into suffix.
1054 				e.suffix = String.valueOf(c);
1055 
1056 			} else if (e.section == null && Character.isWhitespace(c)) {
1057 				// Save the leading whitespace (if any).
1058 				if (e.prefix == null)
1059 					e.prefix = ""; //$NON-NLS-1$
1060 				e.prefix += c;
1061 
1062 			} else if ('[' == c) {
1063 				// This is a section header.
1064 				e.section = readSectionName(in);
1065 				input = in.read();
1066 				if ('"' == input) {
1067 					e.subsection = readValue(in, true, '"');
1068 					input = in.read();
1069 				}
1070 				if (']' != input)
1071 					throw new ConfigInvalidException(JGitText.get().badGroupHeader);
1072 				e.suffix = ""; //$NON-NLS-1$
1073 
1074 			} else if (last != null) {
1075 				// Read a value.
1076 				e.section = last.section;
1077 				e.subsection = last.subsection;
1078 				in.reset();
1079 				e.name = readKeyName(in);
1080 				if (e.name.endsWith("\n")) { //$NON-NLS-1$
1081 					e.name = e.name.substring(0, e.name.length() - 1);
1082 					e.value = MAGIC_EMPTY_VALUE;
1083 				} else
1084 					e.value = readValue(in, false, -1);
1085 
1086 			} else
1087 				throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
1088 		}
1089 
1090 		state.set(newState(newEntries));
1091 	}
1092 
1093 	private ConfigSnapshot newState() {
1094 		return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
1095 				getBaseState());
1096 	}
1097 
1098 	private ConfigSnapshot newState(final List<ConfigLine> entries) {
1099 		return new ConfigSnapshot(Collections.unmodifiableList(entries),
1100 				getBaseState());
1101 	}
1102 
1103 	/**
1104 	 * Clear the configuration file
1105 	 */
1106 	protected void clear() {
1107 		state.set(newState());
1108 	}
1109 
1110 	private static String readSectionName(final StringReader in)
1111 			throws ConfigInvalidException {
1112 		final StringBuilder name = new StringBuilder();
1113 		for (;;) {
1114 			int c = in.read();
1115 			if (c < 0)
1116 				throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1117 
1118 			if (']' == c) {
1119 				in.reset();
1120 				break;
1121 			}
1122 
1123 			if (' ' == c || '\t' == c) {
1124 				for (;;) {
1125 					c = in.read();
1126 					if (c < 0)
1127 						throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1128 
1129 					if ('"' == c) {
1130 						in.reset();
1131 						break;
1132 					}
1133 
1134 					if (' ' == c || '\t' == c)
1135 						continue; // Skipped...
1136 					throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
1137 				}
1138 				break;
1139 			}
1140 
1141 			if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
1142 				name.append((char) c);
1143 			else
1144 				throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
1145 		}
1146 		return name.toString();
1147 	}
1148 
1149 	private static String readKeyName(final StringReader in)
1150 			throws ConfigInvalidException {
1151 		final StringBuilder name = new StringBuilder();
1152 		for (;;) {
1153 			int c = in.read();
1154 			if (c < 0)
1155 				throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1156 
1157 			if ('=' == c)
1158 				break;
1159 
1160 			if (' ' == c || '\t' == c) {
1161 				for (;;) {
1162 					c = in.read();
1163 					if (c < 0)
1164 						throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1165 
1166 					if ('=' == c)
1167 						break;
1168 
1169 					if (';' == c || '#' == c || '\n' == c) {
1170 						in.reset();
1171 						break;
1172 					}
1173 
1174 					if (' ' == c || '\t' == c)
1175 						continue; // Skipped...
1176 					throw new ConfigInvalidException(JGitText.get().badEntryDelimiter);
1177 				}
1178 				break;
1179 			}
1180 
1181 			if (Character.isLetterOrDigit((char) c) || c == '-') {
1182 				// From the git-config man page:
1183 				// The variable names are case-insensitive and only
1184 				// alphanumeric characters and - are allowed.
1185 				name.append((char) c);
1186 			} else if ('\n' == c) {
1187 				in.reset();
1188 				name.append((char) c);
1189 				break;
1190 			} else
1191 				throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name));
1192 		}
1193 		return name.toString();
1194 	}
1195 
1196 	private static String readValue(final StringReader in, boolean quote,
1197 			final int eol) throws ConfigInvalidException {
1198 		final StringBuilder value = new StringBuilder();
1199 		boolean space = false;
1200 		for (;;) {
1201 			int c = in.read();
1202 			if (c < 0) {
1203 				break;
1204 			}
1205 
1206 			if ('\n' == c) {
1207 				if (quote)
1208 					throw new ConfigInvalidException(JGitText.get().newlineInQuotesNotAllowed);
1209 				in.reset();
1210 				break;
1211 			}
1212 
1213 			if (eol == c)
1214 				break;
1215 
1216 			if (!quote) {
1217 				if (Character.isWhitespace((char) c)) {
1218 					space = true;
1219 					continue;
1220 				}
1221 				if (';' == c || '#' == c) {
1222 					in.reset();
1223 					break;
1224 				}
1225 			}
1226 
1227 			if (space) {
1228 				if (value.length() > 0)
1229 					value.append(' ');
1230 				space = false;
1231 			}
1232 
1233 			if ('\\' == c) {
1234 				c = in.read();
1235 				switch (c) {
1236 				case -1:
1237 					throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
1238 				case '\n':
1239 					continue;
1240 				case 't':
1241 					value.append('\t');
1242 					continue;
1243 				case 'b':
1244 					value.append('\b');
1245 					continue;
1246 				case 'n':
1247 					value.append('\n');
1248 					continue;
1249 				case '\\':
1250 					value.append('\\');
1251 					continue;
1252 				case '"':
1253 					value.append('"');
1254 					continue;
1255 				default:
1256 					throw new ConfigInvalidException(MessageFormat.format(
1257 							JGitText.get().badEscape,
1258 							Character.valueOf(((char) c))));
1259 				}
1260 			}
1261 
1262 			if ('"' == c) {
1263 				quote = !quote;
1264 				continue;
1265 			}
1266 
1267 			value.append((char) c);
1268 		}
1269 		return value.length() > 0 ? value.toString() : null;
1270 	}
1271 
1272 	/**
1273 	 * Parses a section of the configuration into an application model object.
1274 	 * <p>
1275 	 * Instances must implement hashCode and equals such that model objects can
1276 	 * be cached by using the {@code SectionParser} as a key of a HashMap.
1277 	 * <p>
1278 	 * As the {@code SectionParser} itself is used as the key of the internal
1279 	 * HashMap applications should be careful to ensure the SectionParser key
1280 	 * does not retain unnecessary application state which may cause memory to
1281 	 * be held longer than expected.
1282 	 *
1283 	 * @param <T>
1284 	 *            type of the application model created by the parser.
1285 	 */
1286 	public static interface SectionParser<T> {
1287 		/**
1288 		 * Create a model object from a configuration.
1289 		 *
1290 		 * @param cfg
1291 		 *            the configuration to read values from.
1292 		 * @return the application model instance.
1293 		 */
1294 		T parse(Config cfg);
1295 	}
1296 
1297 	private static class StringReader {
1298 		private final char[] buf;
1299 
1300 		private int pos;
1301 
1302 		StringReader(final String in) {
1303 			buf = in.toCharArray();
1304 		}
1305 
1306 		int read() {
1307 			try {
1308 				return buf[pos++];
1309 			} catch (ArrayIndexOutOfBoundsException e) {
1310 				pos = buf.length;
1311 				return -1;
1312 			}
1313 		}
1314 
1315 		void reset() {
1316 			pos--;
1317 		}
1318 	}
1319 
1320 	/**
1321 	 * Converts enumeration values into configuration options and vice-versa,
1322 	 * allowing to match a config option with an enum value.
1323 	 *
1324 	 */
1325 	public static interface ConfigEnum {
1326 		/**
1327 		 * Converts enumeration value into a string to be save in config.
1328 		 *
1329 		 * @return the enum value as config string
1330 		 */
1331 		String toConfigValue();
1332 
1333 		/**
1334 		 * Checks if the given string matches with enum value.
1335 		 *
1336 		 * @param in
1337 		 *            the string to match
1338 		 * @return true if the given string matches enum value, false otherwise
1339 		 */
1340 		boolean matchConfigValue(String in);
1341 	}
1342 }