All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jgit.lib.Config Maven / Gradle / Ivy

/*
 * Copyright (C) 2010, Mathias Kinzler 
 * Copyright (C) 2009, Constantine Plotnikov 
 * Copyright (C) 2007, Dave Watson 
 * Copyright (C) 2008-2010, Google Inc.
 * Copyright (C) 2009, Google, Inc.
 * Copyright (C) 2009, JetBrains s.r.o.
 * Copyright (C) 2007-2008, Robin Rosenberg 
 * Copyright (C) 2006-2008, Shawn O. Pearce 
 * Copyright (C) 2008, Thad Hughes 
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.eclipse.jgit.lib;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.events.ConfigChangedEvent;
import org.eclipse.jgit.events.ConfigChangedListener;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.ListenerList;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.util.StringUtils;


/**
 * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
 */
public class Config {
	private static final String[] EMPTY_STRING_ARRAY = {};
	private static final long KiB = 1024;
	private static final long MiB = 1024 * KiB;
	private static final long GiB = 1024 * MiB;

	/** the change listeners */
	private final ListenerList listeners = new ListenerList();

	/**
	 * Immutable current state of the configuration data.
	 * 

* This state is copy-on-write. It should always contain an immutable list * of the configuration keys/values. */ private final AtomicReference state; private final Config baseConfig; /** * Magic value indicating a missing entry. *

* This value is tested for reference equality in some contexts, so we * must ensure it is a special copy of the empty string. It also must * be treated like the empty string. */ private static final String MAGIC_EMPTY_VALUE = new String(); /** Create a configuration with no default fallback. */ public Config() { this(null); } /** * Create an empty configuration with a fallback for missing keys. * * @param defaultConfig * the base configuration to be consulted when a key is missing * from this configuration instance. */ public Config(Config defaultConfig) { baseConfig = defaultConfig; state = new AtomicReference(newState()); } /** * Escape the value before saving * * @param x * the value to escape * @return the escaped value */ private static String escapeValue(final String x) { boolean inquote = false; int lineStart = 0; final StringBuilder r = new StringBuilder(x.length()); for (int k = 0; k < x.length(); k++) { final char c = x.charAt(k); switch (c) { case '\n': if (inquote) { r.append('"'); inquote = false; } r.append("\\n\\\n"); //$NON-NLS-1$ lineStart = r.length(); break; case '\t': r.append("\\t"); //$NON-NLS-1$ break; case '\b': r.append("\\b"); //$NON-NLS-1$ break; case '\\': r.append("\\\\"); //$NON-NLS-1$ break; case '"': r.append("\\\""); //$NON-NLS-1$ break; case ';': case '#': if (!inquote) { r.insert(lineStart, '"'); inquote = true; } r.append(c); break; case ' ': if (!inquote && r.length() > 0 && r.charAt(r.length() - 1) == ' ') { r.insert(lineStart, '"'); inquote = true; } r.append(' '); break; default: r.append(c); break; } } if (inquote) { r.append('"'); } return r.toString(); } /** * Obtain an integer value from the configuration. * * @param section * section the key is grouped within. * @param name * name of the key to get. * @param defaultValue * default value to return if no value was present. * @return an integer value from the configuration, or defaultValue. */ public int getInt(final String section, final String name, final int defaultValue) { return getInt(section, null, name, defaultValue); } /** * Obtain an integer value from the configuration. * * @param section * section the key is grouped within. * @param subsection * subsection name, such a remote or branch name. * @param name * name of the key to get. * @param defaultValue * default value to return if no value was present. * @return an integer value from the configuration, or defaultValue. */ public int getInt(final String section, String subsection, final String name, final int defaultValue) { final long val = getLong(section, subsection, name, defaultValue); if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) return (int) val; throw new IllegalArgumentException(MessageFormat.format(JGitText.get().integerValueOutOfRange , section, name)); } /** * Obtain an integer value from the configuration. * * @param section * section the key is grouped within. * @param name * name of the key to get. * @param defaultValue * default value to return if no value was present. * @return an integer value from the configuration, or defaultValue. */ public long getLong(String section, String name, long defaultValue) { return getLong(section, null, name, defaultValue); } /** * Obtain an integer value from the configuration. * * @param section * section the key is grouped within. * @param subsection * subsection name, such a remote or branch name. * @param name * name of the key to get. * @param defaultValue * default value to return if no value was present. * @return an integer value from the configuration, or defaultValue. */ public long getLong(final String section, String subsection, final String name, final long defaultValue) { final String str = getString(section, subsection, name); if (str == null) return defaultValue; String n = str.trim(); if (n.length() == 0) return defaultValue; long mul = 1; switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) { case 'g': mul = GiB; break; case 'm': mul = MiB; break; case 'k': mul = KiB; break; } if (mul > 1) n = n.substring(0, n.length() - 1).trim(); if (n.length() == 0) return defaultValue; try { return mul * Long.parseLong(n); } catch (NumberFormatException nfe) { throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidIntegerValue , section, name, str)); } } /** * Get a boolean value from the git config * * @param section * section the key is grouped within. * @param name * name of the key to get. * @param defaultValue * default value to return if no value was present. * @return true if any value or defaultValue is true, false for missing or * explicit false */ public boolean getBoolean(final String section, final String name, final boolean defaultValue) { return getBoolean(section, null, name, defaultValue); } /** * Get a boolean value from the git config * * @param section * section the key is grouped within. * @param subsection * subsection name, such a remote or branch name. * @param name * name of the key to get. * @param defaultValue * default value to return if no value was present. * @return true if any value or defaultValue is true, false for missing or * explicit false */ public boolean getBoolean(final String section, String subsection, final String name, final boolean defaultValue) { String n = getRawString(section, subsection, name); if (n == null) return defaultValue; if (MAGIC_EMPTY_VALUE == n) return true; try { return StringUtils.toBoolean(n); } catch (IllegalArgumentException err) { throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidBooleanValue , section, name, n)); } } /** * Parse an enumeration from the configuration. * * @param * type of the enumeration object. * @param section * section the key is grouped within. * @param subsection * subsection name, such a remote or branch name. * @param name * name of the key to get. * @param defaultValue * default value to return if no value was present. * @return the selected enumeration value, or {@code defaultValue}. */ public > T getEnum(final String section, final String subsection, final String name, final T defaultValue) { final T[] all = allValuesOf(defaultValue); return getEnum(all, section, subsection, name, defaultValue); } @SuppressWarnings("unchecked") private static T[] allValuesOf(final T value) { try { return (T[]) value.getClass().getMethod("values").invoke(null); //$NON-NLS-1$ } catch (Exception err) { String typeName = value.getClass().getName(); String msg = MessageFormat.format( JGitText.get().enumValuesNotAvailable, typeName); throw new IllegalArgumentException(msg, err); } } /** * Parse an enumeration from the configuration. * * @param * type of the enumeration object. * @param all * all possible values in the enumeration which should be * recognized. Typically {@code EnumType.values()}. * @param section * section the key is grouped within. * @param subsection * subsection name, such a remote or branch name. * @param name * name of the key to get. * @param defaultValue * default value to return if no value was present. * @return the selected enumeration value, or {@code defaultValue}. */ public > T getEnum(final T[] all, final String section, final String subsection, final String name, final T defaultValue) { String value = getString(section, subsection, name); if (value == null) return defaultValue; if (all[0] instanceof ConfigEnum) { for (T t : all) { if (((ConfigEnum) t).matchConfigValue(value)) return t; } } String n = value.replace(' ', '_'); // Because of c98abc9c0586c73ef7df4172644b7dd21c979e9d being used in // the real world before its breakage was fully understood, we must // also accept '-' as though it were ' '. n = n.replace('-', '_'); T trueState = null; T falseState = null; for (T e : all) { if (StringUtils.equalsIgnoreCase(e.name(), n)) return e; else if (StringUtils.equalsIgnoreCase(e.name(), "TRUE")) //$NON-NLS-1$ trueState = e; else if (StringUtils.equalsIgnoreCase(e.name(), "FALSE")) //$NON-NLS-1$ falseState = e; } // This is an odd little fallback. C Git sometimes allows boolean // values in a tri-state with other things. If we have both a true // and a false value in our enumeration, assume its one of those. // if (trueState != null && falseState != null) { try { return StringUtils.toBoolean(n) ? trueState : falseState; } catch (IllegalArgumentException err) { // Fall through and use our custom error below. } } if (subsection != null) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().enumValueNotSupported3, section, subsection, name, value)); else throw new IllegalArgumentException( MessageFormat.format(JGitText.get().enumValueNotSupported2, section, name, value)); } /** * Get string value * * @param section * the section * @param subsection * the subsection for the value * @param name * the key name * @return a String value from git config. */ public String getString(final String section, String subsection, final String name) { return getRawString(section, subsection, name); } /** * Get a list of string values *

* If this instance was created with a base, the base's values are returned * first (if any). * * @param section * the section * @param subsection * the subsection for the value * @param name * the key name * @return array of zero or more values from the configuration. */ public String[] getStringList(final String section, String subsection, final String name) { String[] base; if (baseConfig != null) base = baseConfig.getStringList(section, subsection, name); else base = EMPTY_STRING_ARRAY; String[] self = getRawStringList(section, subsection, name); if (self == null) return base; if (base.length == 0) return self; String[] res = new String[base.length + self.length]; int n = base.length; System.arraycopy(base, 0, res, 0, n); System.arraycopy(self, 0, res, n, self.length); return res; } /** * @param section * section to search for. * @return set of all subsections of specified section within this * configuration and its base configuration; may be empty if no * subsection exists. The set's iterator returns sections in the * order they are declared by the configuration starting from this * instance and progressing through the base. */ public Set getSubsections(final String section) { return getState().getSubsections(section); } /** * @return the sections defined in this {@link Config}. The set's iterator * returns sections in the order they are declared by the * configuration starting from this instance and progressing through * the base. */ public Set getSections() { return getState().getSections(); } /** * @param section * the section * @return the list of names defined for this section */ public Set getNames(String section) { return getNames(section, null); } /** * @param section * the section * @param subsection * the subsection * @return the list of names defined for this subsection */ public Set getNames(String section, String subsection) { return getState().getNames(section, subsection); } /** * Obtain a handle to a parsed set of configuration values. * * @param * type of configuration model to return. * @param parser * parser which can create the model if it is not already * available in this configuration file. The parser is also used * as the key into a cache and must obey the hashCode and equals * contract in order to reuse a parsed model. * @return the parsed object instance, which is cached inside this config. */ @SuppressWarnings("unchecked") public T get(final SectionParser parser) { final ConfigSnapshot myState = getState(); T obj = (T) myState.cache.get(parser); if (obj == null) { obj = parser.parse(this); myState.cache.put(parser, obj); } return obj; } /** * Remove a cached configuration object. *

* If the associated configuration object has not yet been cached, this * method has no effect. * * @param parser * parser used to obtain the configuration object. * @see #get(SectionParser) */ public void uncache(final SectionParser parser) { state.get().cache.remove(parser); } /** * Adds a listener to be notified about changes. *

* Clients are supposed to remove the listeners after they are done with * them using the {@link ListenerHandle#remove()} method * * @param listener * the listener * @return the handle to the registered listener */ public ListenerHandle addChangeListener(ConfigChangedListener listener) { return listeners.addConfigChangedListener(listener); } /** * Determine whether to issue change events for transient changes. *

* If true is returned (which is the default behavior), * {@link #fireConfigChangedEvent()} will be called upon each change. *

* Subclasses that override this to return false are * responsible for issuing {@link #fireConfigChangedEvent()} calls * themselves. * * @return */ protected boolean notifyUponTransientChanges() { return true; } /** * Notifies the listeners */ protected void fireConfigChangedEvent() { listeners.dispatch(new ConfigChangedEvent()); } private String getRawString(final String section, final String subsection, final String name) { String[] lst = getRawStringList(section, subsection, name); if (lst != null) return lst[0]; else if (baseConfig != null) return baseConfig.getRawString(section, subsection, name); else return null; } private String[] getRawStringList(String section, String subsection, String name) { return state.get().get(section, subsection, name); } private ConfigSnapshot getState() { ConfigSnapshot cur, upd; do { cur = state.get(); final ConfigSnapshot base = getBaseState(); if (cur.baseState == base) return cur; upd = new ConfigSnapshot(cur.entryList, base); } while (!state.compareAndSet(cur, upd)); return upd; } private ConfigSnapshot getBaseState() { return baseConfig != null ? baseConfig.getState() : null; } /** * Add or modify a configuration value. The parameters will result in a * configuration entry like this. * *

	 * [section "subsection"]
	 *         name = value
	 * 
* * @param section * section name, e.g "branch" * @param subsection * optional subsection value, e.g. a branch name * @param name * parameter name, e.g. "filemode" * @param value * parameter value */ public void setInt(final String section, final String subsection, final String name, final int value) { setLong(section, subsection, name, value); } /** * Add or modify a configuration value. The parameters will result in a * configuration entry like this. * *
	 * [section "subsection"]
	 *         name = value
	 * 
* * @param section * section name, e.g "branch" * @param subsection * optional subsection value, e.g. a branch name * @param name * parameter name, e.g. "filemode" * @param value * parameter value */ public void setLong(final String section, final String subsection, final String name, final long value) { final String s; if (value >= GiB && (value % GiB) == 0) s = String.valueOf(value / GiB) + " g"; //$NON-NLS-1$ else if (value >= MiB && (value % MiB) == 0) s = String.valueOf(value / MiB) + " m"; //$NON-NLS-1$ else if (value >= KiB && (value % KiB) == 0) s = String.valueOf(value / KiB) + " k"; //$NON-NLS-1$ else s = String.valueOf(value); setString(section, subsection, name, s); } /** * Add or modify a configuration value. The parameters will result in a * configuration entry like this. * *
	 * [section "subsection"]
	 *         name = value
	 * 
* * @param section * section name, e.g "branch" * @param subsection * optional subsection value, e.g. a branch name * @param name * parameter name, e.g. "filemode" * @param value * parameter value */ public void setBoolean(final String section, final String subsection, final String name, final boolean value) { setString(section, subsection, name, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Add or modify a configuration value. The parameters will result in a * configuration entry like this. * *
	 * [section "subsection"]
	 *         name = value
	 * 
* * @param * type of the enumeration object. * @param section * section name, e.g "branch" * @param subsection * optional subsection value, e.g. a branch name * @param name * parameter name, e.g. "filemode" * @param value * parameter value */ public > void setEnum(final String section, final String subsection, final String name, final T value) { String n; if (value instanceof ConfigEnum) n = ((ConfigEnum) value).toConfigValue(); else n = value.name().toLowerCase().replace('_', ' '); setString(section, subsection, name, n); } /** * Add or modify a configuration value. The parameters will result in a * configuration entry like this. * *
	 * [section "subsection"]
	 *         name = value
	 * 
* * @param section * section name, e.g "branch" * @param subsection * optional subsection value, e.g. a branch name * @param name * parameter name, e.g. "filemode" * @param value * parameter value, e.g. "true" */ public void setString(final String section, final String subsection, final String name, final String value) { setStringList(section, subsection, name, Collections .singletonList(value)); } /** * Remove a configuration value. * * @param section * section name, e.g "branch" * @param subsection * optional subsection value, e.g. a branch name * @param name * parameter name, e.g. "filemode" */ public void unset(final String section, final String subsection, final String name) { setStringList(section, subsection, name, Collections . emptyList()); } /** * Remove all configuration values under a single section. * * @param section * section name, e.g "branch" * @param subsection * optional subsection value, e.g. a branch name */ public void unsetSection(String section, String subsection) { ConfigSnapshot src, res; do { src = state.get(); res = unsetSection(src, section, subsection); } while (!state.compareAndSet(src, res)); } private ConfigSnapshot unsetSection(final ConfigSnapshot srcState, final String section, final String subsection) { final int max = srcState.entryList.size(); final ArrayList r = new ArrayList(max); boolean lastWasMatch = false; for (ConfigLine e : srcState.entryList) { if (e.match(section, subsection)) { // Skip this record, it's for the section we are removing. lastWasMatch = true; continue; } if (lastWasMatch && e.section == null && e.subsection == null) continue; // skip this padding line in the section. r.add(e); } return newState(r); } /** * Set a configuration value. * *
	 * [section "subsection"]
	 *         name = value
	 * 
* * @param section * section name, e.g "branch" * @param subsection * optional subsection value, e.g. a branch name * @param name * parameter name, e.g. "filemode" * @param values * list of zero or more values for this key. */ public void setStringList(final String section, final String subsection, final String name, final List values) { ConfigSnapshot src, res; do { src = state.get(); res = replaceStringList(src, section, subsection, name, values); } while (!state.compareAndSet(src, res)); if (notifyUponTransientChanges()) fireConfigChangedEvent(); } private ConfigSnapshot replaceStringList(final ConfigSnapshot srcState, final String section, final String subsection, final String name, final List values) { final List entries = copy(srcState, values); int entryIndex = 0; int valueIndex = 0; int insertPosition = -1; // Reset the first n Entry objects that match this input name. // while (entryIndex < entries.size() && valueIndex < values.size()) { final ConfigLine e = entries.get(entryIndex); if (e.match(section, subsection, name)) { entries.set(entryIndex, e.forValue(values.get(valueIndex++))); insertPosition = entryIndex + 1; } entryIndex++; } // Remove any extra Entry objects that we no longer need. // if (valueIndex == values.size() && entryIndex < entries.size()) { while (entryIndex < entries.size()) { final ConfigLine e = entries.get(entryIndex++); if (e.match(section, subsection, name)) entries.remove(--entryIndex); } } // Insert new Entry objects for additional/new values. // if (valueIndex < values.size() && entryIndex == entries.size()) { if (insertPosition < 0) { // We didn't find a matching key above, but maybe there // is already a section available that matches. Insert // after the last key of that section. // insertPosition = findSectionEnd(entries, section, subsection); } if (insertPosition < 0) { // We didn't find any matching section header for this key, // so we must create a new section header at the end. // final ConfigLine e = new ConfigLine(); e.section = section; e.subsection = subsection; entries.add(e); insertPosition = entries.size(); } while (valueIndex < values.size()) { final ConfigLine e = new ConfigLine(); e.section = section; e.subsection = subsection; e.name = name; e.value = values.get(valueIndex++); entries.add(insertPosition++, e); } } return newState(entries); } private static List copy(final ConfigSnapshot src, final List values) { // At worst we need to insert 1 line for each value, plus 1 line // for a new section header. Assume that and allocate the space. // final int max = src.entryList.size() + values.size() + 1; final ArrayList r = new ArrayList(max); r.addAll(src.entryList); return r; } private static int findSectionEnd(final List entries, final String section, final String subsection) { for (int i = 0; i < entries.size(); i++) { ConfigLine e = entries.get(i); if (e.match(section, subsection, null)) { i++; while (i < entries.size()) { e = entries.get(i); if (e.match(section, subsection, e.name)) i++; else break; } return i; } } return -1; } /** * @return this configuration, formatted as a Git style text file. */ public String toText() { final StringBuilder out = new StringBuilder(); for (final ConfigLine e : state.get().entryList) { if (e.prefix != null) out.append(e.prefix); if (e.section != null && e.name == null) { out.append('['); out.append(e.section); if (e.subsection != null) { out.append(' '); String escaped = escapeValue(e.subsection); // make sure to avoid double quotes here boolean quoted = escaped.startsWith("\"") //$NON-NLS-1$ && escaped.endsWith("\""); //$NON-NLS-1$ if (!quoted) out.append('"'); out.append(escaped); if (!quoted) out.append('"'); } out.append(']'); } else if (e.section != null && e.name != null) { if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$ out.append('\t'); out.append(e.name); if (MAGIC_EMPTY_VALUE != e.value) { out.append(" ="); //$NON-NLS-1$ if (e.value != null) { out.append(' '); out.append(escapeValue(e.value)); } } if (e.suffix != null) out.append(' '); } if (e.suffix != null) out.append(e.suffix); out.append('\n'); } return out.toString(); } /** * Clear this configuration and reset to the contents of the parsed string. * * @param text * Git style text file listing configuration properties. * @throws ConfigInvalidException * the text supplied is not formatted correctly. No changes were * made to {@code this}. */ public void fromText(final String text) throws ConfigInvalidException { final List newEntries = new ArrayList(); final StringReader in = new StringReader(text); ConfigLine last = null; ConfigLine e = new ConfigLine(); for (;;) { int input = in.read(); if (-1 == input) break; final char c = (char) input; if ('\n' == c) { // End of this entry. newEntries.add(e); if (e.section != null) last = e; e = new ConfigLine(); } else if (e.suffix != null) { // Everything up until the end-of-line is in the suffix. e.suffix += c; } else if (';' == c || '#' == c) { // The rest of this line is a comment; put into suffix. e.suffix = String.valueOf(c); } else if (e.section == null && Character.isWhitespace(c)) { // Save the leading whitespace (if any). if (e.prefix == null) e.prefix = ""; //$NON-NLS-1$ e.prefix += c; } else if ('[' == c) { // This is a section header. e.section = readSectionName(in); input = in.read(); if ('"' == input) { e.subsection = readValue(in, true, '"'); input = in.read(); } if (']' != input) throw new ConfigInvalidException(JGitText.get().badGroupHeader); e.suffix = ""; //$NON-NLS-1$ } else if (last != null) { // Read a value. e.section = last.section; e.subsection = last.subsection; in.reset(); e.name = readKeyName(in); if (e.name.endsWith("\n")) { //$NON-NLS-1$ e.name = e.name.substring(0, e.name.length() - 1); e.value = MAGIC_EMPTY_VALUE; } else e.value = readValue(in, false, -1); } else throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile); } state.set(newState(newEntries)); } private ConfigSnapshot newState() { return new ConfigSnapshot(Collections. emptyList(), getBaseState()); } private ConfigSnapshot newState(final List entries) { return new ConfigSnapshot(Collections.unmodifiableList(entries), getBaseState()); } /** * Clear the configuration file */ protected void clear() { state.set(newState()); } private static String readSectionName(final StringReader in) throws ConfigInvalidException { final StringBuilder name = new StringBuilder(); for (;;) { int c = in.read(); if (c < 0) throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile); if (']' == c) { in.reset(); break; } if (' ' == c || '\t' == c) { for (;;) { c = in.read(); if (c < 0) throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile); if ('"' == c) { in.reset(); break; } if (' ' == c || '\t' == c) continue; // Skipped... throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name)); } break; } if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c) name.append((char) c); else throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name)); } return name.toString(); } private static String readKeyName(final StringReader in) throws ConfigInvalidException { final StringBuilder name = new StringBuilder(); for (;;) { int c = in.read(); if (c < 0) throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile); if ('=' == c) break; if (' ' == c || '\t' == c) { for (;;) { c = in.read(); if (c < 0) throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile); if ('=' == c) break; if (';' == c || '#' == c || '\n' == c) { in.reset(); break; } if (' ' == c || '\t' == c) continue; // Skipped... throw new ConfigInvalidException(JGitText.get().badEntryDelimiter); } break; } if (Character.isLetterOrDigit((char) c) || c == '-') { // From the git-config man page: // The variable names are case-insensitive and only // alphanumeric characters and - are allowed. name.append((char) c); } else if ('\n' == c) { in.reset(); name.append((char) c); break; } else throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name)); } return name.toString(); } private static String readValue(final StringReader in, boolean quote, final int eol) throws ConfigInvalidException { final StringBuilder value = new StringBuilder(); boolean space = false; for (;;) { int c = in.read(); if (c < 0) { if (value.length() == 0) throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile); break; } if ('\n' == c) { if (quote) throw new ConfigInvalidException(JGitText.get().newlineInQuotesNotAllowed); in.reset(); break; } if (eol == c) break; if (!quote) { if (Character.isWhitespace((char) c)) { space = true; continue; } if (';' == c || '#' == c) { in.reset(); break; } } if (space) { if (value.length() > 0) value.append(' '); space = false; } if ('\\' == c) { c = in.read(); switch (c) { case -1: throw new ConfigInvalidException(JGitText.get().endOfFileInEscape); case '\n': continue; case 't': value.append('\t'); continue; case 'b': value.append('\b'); continue; case 'n': value.append('\n'); continue; case '\\': value.append('\\'); continue; case '"': value.append('"'); continue; default: throw new ConfigInvalidException(MessageFormat.format( JGitText.get().badEscape, Character.valueOf(((char) c)))); } } if ('"' == c) { quote = !quote; continue; } value.append((char) c); } return value.length() > 0 ? value.toString() : null; } /** * Parses a section of the configuration into an application model object. *

* Instances must implement hashCode and equals such that model objects can * be cached by using the {@code SectionParser} as a key of a HashMap. *

* As the {@code SectionParser} itself is used as the key of the internal * HashMap applications should be careful to ensure the SectionParser key * does not retain unnecessary application state which may cause memory to * be held longer than expected. * * @param * type of the application model created by the parser. */ public static interface SectionParser { /** * Create a model object from a configuration. * * @param cfg * the configuration to read values from. * @return the application model instance. */ T parse(Config cfg); } private static class StringReader { private final char[] buf; private int pos; StringReader(final String in) { buf = in.toCharArray(); } int read() { try { return buf[pos++]; } catch (ArrayIndexOutOfBoundsException e) { pos = buf.length; return -1; } } void reset() { pos--; } } /** * Converts enumeration values into configuration options and vice-versa, * allowing to match a config option with an enum value. * */ public static interface ConfigEnum { /** * Converts enumeration value into a string to be save in config. * * @return the enum value as config string */ String toConfigValue(); /** * Checks if the given string matches with enum value. * * @param in * the string to match * @return true if the given string matches enum value, false otherwise */ boolean matchConfigValue(String in); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy