|
|
@@ -61,7 +61,6 @@ import java.util.LinkedHashSet; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.concurrent.ConcurrentHashMap; |
|
|
|
import java.util.concurrent.atomic.AtomicReference; |
|
|
|
|
|
|
|
import org.eclipse.jgit.errors.ConfigInvalidException; |
|
|
@@ -91,7 +90,7 @@ public class Config { |
|
|
|
* This state is copy-on-write. It should always contain an immutable list |
|
|
|
* of the configuration keys/values. |
|
|
|
*/ |
|
|
|
private final AtomicReference<State> state; |
|
|
|
private final AtomicReference<ConfigSnapshot> state; |
|
|
|
|
|
|
|
private final Config baseConfig; |
|
|
|
|
|
|
@@ -118,7 +117,7 @@ public class Config { |
|
|
|
*/ |
|
|
|
public Config(Config defaultConfig) { |
|
|
|
baseConfig = defaultConfig; |
|
|
|
state = new AtomicReference<State>(newState()); |
|
|
|
state = new AtomicReference<ConfigSnapshot>(newState()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@@ -457,22 +456,22 @@ public class Config { |
|
|
|
*/ |
|
|
|
public String[] getStringList(final String section, String subsection, |
|
|
|
final String name) { |
|
|
|
final String[] baseList; |
|
|
|
String[] base; |
|
|
|
if (baseConfig != null) |
|
|
|
baseList = baseConfig.getStringList(section, subsection, name); |
|
|
|
base = baseConfig.getStringList(section, subsection, name); |
|
|
|
else |
|
|
|
baseList = EMPTY_STRING_ARRAY; |
|
|
|
|
|
|
|
final List<String> lst = getRawStringList(section, subsection, name); |
|
|
|
if (lst != null) { |
|
|
|
final String[] res = new String[baseList.length + lst.size()]; |
|
|
|
int idx = baseList.length; |
|
|
|
System.arraycopy(baseList, 0, res, 0, idx); |
|
|
|
for (final String val : lst) |
|
|
|
res[idx++] = val; |
|
|
|
return res; |
|
|
|
} |
|
|
|
return baseList; |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@@ -527,7 +526,7 @@ public class Config { |
|
|
|
*/ |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
public <T> T get(final SectionParser<T> parser) { |
|
|
|
final State myState = getState(); |
|
|
|
final ConfigSnapshot myState = getState(); |
|
|
|
T obj = (T) myState.cache.get(parser); |
|
|
|
if (obj == null) { |
|
|
|
obj = parser.parse(this); |
|
|
@@ -589,51 +588,33 @@ public class Config { |
|
|
|
|
|
|
|
private String getRawString(final String section, final String subsection, |
|
|
|
final String name) { |
|
|
|
final List<String> lst = getRawStringList(section, subsection, name); |
|
|
|
String[] lst = getRawStringList(section, subsection, name); |
|
|
|
if (lst != null) |
|
|
|
return lst.get(0); |
|
|
|
return lst[0]; |
|
|
|
else if (baseConfig != null) |
|
|
|
return baseConfig.getRawString(section, subsection, name); |
|
|
|
else |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
private List<String> getRawStringList(final String section, |
|
|
|
final String subsection, final String name) { |
|
|
|
List<String> r = null; |
|
|
|
for (final Entry e : state.get().entryList) { |
|
|
|
if (e.match(section, subsection, name)) |
|
|
|
r = add(r, e.value); |
|
|
|
} |
|
|
|
return r; |
|
|
|
private String[] getRawStringList(String section, String subsection, |
|
|
|
String name) { |
|
|
|
return state.get().get(section, subsection, name); |
|
|
|
} |
|
|
|
|
|
|
|
private static List<String> add(final List<String> curr, final String value) { |
|
|
|
if (curr == null) |
|
|
|
return Collections.singletonList(value); |
|
|
|
if (curr.size() == 1) { |
|
|
|
final List<String> r = new ArrayList<String>(2); |
|
|
|
r.add(curr.get(0)); |
|
|
|
r.add(value); |
|
|
|
return r; |
|
|
|
} |
|
|
|
curr.add(value); |
|
|
|
return curr; |
|
|
|
} |
|
|
|
|
|
|
|
private State getState() { |
|
|
|
State cur, upd; |
|
|
|
private ConfigSnapshot getState() { |
|
|
|
ConfigSnapshot cur, upd; |
|
|
|
do { |
|
|
|
cur = state.get(); |
|
|
|
final State base = getBaseState(); |
|
|
|
final ConfigSnapshot base = getBaseState(); |
|
|
|
if (cur.baseState == base) |
|
|
|
return cur; |
|
|
|
upd = new State(cur.entryList, base); |
|
|
|
upd = new ConfigSnapshot(cur.entryList, base); |
|
|
|
} while (!state.compareAndSet(cur, upd)); |
|
|
|
return upd; |
|
|
|
} |
|
|
|
|
|
|
|
private State getBaseState() { |
|
|
|
private ConfigSnapshot getBaseState() { |
|
|
|
return baseConfig != null ? baseConfig.getState() : null; |
|
|
|
} |
|
|
|
|
|
|
@@ -792,20 +773,21 @@ public class Config { |
|
|
|
* optional subsection value, e.g. a branch name |
|
|
|
*/ |
|
|
|
public void unsetSection(String section, String subsection) { |
|
|
|
State src, res; |
|
|
|
ConfigSnapshot src, res; |
|
|
|
do { |
|
|
|
src = state.get(); |
|
|
|
res = unsetSection(src, section, subsection); |
|
|
|
} while (!state.compareAndSet(src, res)); |
|
|
|
} |
|
|
|
|
|
|
|
private State unsetSection(final State srcState, final String section, |
|
|
|
private ConfigSnapshot unsetSection(final ConfigSnapshot srcState, |
|
|
|
final String section, |
|
|
|
final String subsection) { |
|
|
|
final int max = srcState.entryList.size(); |
|
|
|
final ArrayList<Entry> r = new ArrayList<Entry>(max); |
|
|
|
final ArrayList<ConfigLine> r = new ArrayList<ConfigLine>(max); |
|
|
|
|
|
|
|
boolean lastWasMatch = false; |
|
|
|
for (Entry e : srcState.entryList) { |
|
|
|
for (ConfigLine e : srcState.entryList) { |
|
|
|
if (e.match(section, subsection)) { |
|
|
|
// Skip this record, it's for the section we are removing. |
|
|
|
lastWasMatch = true; |
|
|
@@ -839,7 +821,7 @@ public class Config { |
|
|
|
*/ |
|
|
|
public void setStringList(final String section, final String subsection, |
|
|
|
final String name, final List<String> values) { |
|
|
|
State src, res; |
|
|
|
ConfigSnapshot src, res; |
|
|
|
do { |
|
|
|
src = state.get(); |
|
|
|
res = replaceStringList(src, section, subsection, name, values); |
|
|
@@ -848,10 +830,10 @@ public class Config { |
|
|
|
fireConfigChangedEvent(); |
|
|
|
} |
|
|
|
|
|
|
|
private State replaceStringList(final State srcState, |
|
|
|
private ConfigSnapshot replaceStringList(final ConfigSnapshot srcState, |
|
|
|
final String section, final String subsection, final String name, |
|
|
|
final List<String> values) { |
|
|
|
final List<Entry> entries = copy(srcState, values); |
|
|
|
final List<ConfigLine> entries = copy(srcState, values); |
|
|
|
int entryIndex = 0; |
|
|
|
int valueIndex = 0; |
|
|
|
int insertPosition = -1; |
|
|
@@ -859,7 +841,7 @@ public class Config { |
|
|
|
// Reset the first n Entry objects that match this input name. |
|
|
|
// |
|
|
|
while (entryIndex < entries.size() && valueIndex < values.size()) { |
|
|
|
final Entry e = entries.get(entryIndex); |
|
|
|
final ConfigLine e = entries.get(entryIndex); |
|
|
|
if (e.match(section, subsection, name)) { |
|
|
|
entries.set(entryIndex, e.forValue(values.get(valueIndex++))); |
|
|
|
insertPosition = entryIndex + 1; |
|
|
@@ -871,7 +853,7 @@ public class Config { |
|
|
|
// |
|
|
|
if (valueIndex == values.size() && entryIndex < entries.size()) { |
|
|
|
while (entryIndex < entries.size()) { |
|
|
|
final Entry e = entries.get(entryIndex++); |
|
|
|
final ConfigLine e = entries.get(entryIndex++); |
|
|
|
if (e.match(section, subsection, name)) |
|
|
|
entries.remove(--entryIndex); |
|
|
|
} |
|
|
@@ -891,14 +873,14 @@ public class Config { |
|
|
|
// We didn't find any matching section header for this key, |
|
|
|
// so we must create a new section header at the end. |
|
|
|
// |
|
|
|
final Entry e = new Entry(); |
|
|
|
final ConfigLine e = new ConfigLine(); |
|
|
|
e.section = section; |
|
|
|
e.subsection = subsection; |
|
|
|
entries.add(e); |
|
|
|
insertPosition = entries.size(); |
|
|
|
} |
|
|
|
while (valueIndex < values.size()) { |
|
|
|
final Entry e = new Entry(); |
|
|
|
final ConfigLine e = new ConfigLine(); |
|
|
|
e.section = section; |
|
|
|
e.subsection = subsection; |
|
|
|
e.name = name; |
|
|
@@ -910,20 +892,21 @@ public class Config { |
|
|
|
return newState(entries); |
|
|
|
} |
|
|
|
|
|
|
|
private static List<Entry> copy(final State src, final List<String> values) { |
|
|
|
private static List<ConfigLine> copy(final ConfigSnapshot src, |
|
|
|
final List<String> 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<Entry> r = new ArrayList<Entry>(max); |
|
|
|
final ArrayList<ConfigLine> r = new ArrayList<ConfigLine>(max); |
|
|
|
r.addAll(src.entryList); |
|
|
|
return r; |
|
|
|
} |
|
|
|
|
|
|
|
private static int findSectionEnd(final List<Entry> entries, |
|
|
|
private static int findSectionEnd(final List<ConfigLine> entries, |
|
|
|
final String section, final String subsection) { |
|
|
|
for (int i = 0; i < entries.size(); i++) { |
|
|
|
Entry e = entries.get(i); |
|
|
|
ConfigLine e = entries.get(i); |
|
|
|
if (e.match(section, subsection, null)) { |
|
|
|
i++; |
|
|
|
while (i < entries.size()) { |
|
|
@@ -944,7 +927,7 @@ public class Config { |
|
|
|
*/ |
|
|
|
public String toText() { |
|
|
|
final StringBuilder out = new StringBuilder(); |
|
|
|
for (final Entry e : state.get().entryList) { |
|
|
|
for (final ConfigLine e : state.get().entryList) { |
|
|
|
if (e.prefix != null) |
|
|
|
out.append(e.prefix); |
|
|
|
if (e.section != null && e.name == null) { |
|
|
@@ -994,10 +977,10 @@ public class Config { |
|
|
|
* made to {@code this}. |
|
|
|
*/ |
|
|
|
public void fromText(final String text) throws ConfigInvalidException { |
|
|
|
final List<Entry> newEntries = new ArrayList<Entry>(); |
|
|
|
final List<ConfigLine> newEntries = new ArrayList<ConfigLine>(); |
|
|
|
final StringReader in = new StringReader(text); |
|
|
|
Entry last = null; |
|
|
|
Entry e = new Entry(); |
|
|
|
ConfigLine last = null; |
|
|
|
ConfigLine e = new ConfigLine(); |
|
|
|
for (;;) { |
|
|
|
int input = in.read(); |
|
|
|
if (-1 == input) |
|
|
@@ -1009,7 +992,7 @@ public class Config { |
|
|
|
newEntries.add(e); |
|
|
|
if (e.section != null) |
|
|
|
last = e; |
|
|
|
e = new Entry(); |
|
|
|
e = new ConfigLine(); |
|
|
|
|
|
|
|
} else if (e.suffix != null) { |
|
|
|
// Everything up until the end-of-line is in the suffix. |
|
|
@@ -1056,12 +1039,14 @@ public class Config { |
|
|
|
state.set(newState(newEntries)); |
|
|
|
} |
|
|
|
|
|
|
|
private State newState() { |
|
|
|
return new State(Collections.<Entry> emptyList(), getBaseState()); |
|
|
|
private ConfigSnapshot newState() { |
|
|
|
return new ConfigSnapshot(Collections.<ConfigLine> emptyList(), |
|
|
|
getBaseState()); |
|
|
|
} |
|
|
|
|
|
|
|
private State newState(final List<Entry> entries) { |
|
|
|
return new State(Collections.unmodifiableList(entries), getBaseState()); |
|
|
|
private ConfigSnapshot newState(final List<ConfigLine> entries) { |
|
|
|
return new ConfigSnapshot(Collections.unmodifiableList(entries), |
|
|
|
getBaseState()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@@ -1279,7 +1264,7 @@ public class Config { |
|
|
|
public Set<String> parse(Config cfg) { |
|
|
|
final Set<String> result = new LinkedHashSet<String>(); |
|
|
|
while (cfg != null) { |
|
|
|
for (final Entry e : cfg.state.get().entryList) { |
|
|
|
for (final ConfigLine e : cfg.state.get().entryList) { |
|
|
|
if (e.subsection != null && e.name == null |
|
|
|
&& StringUtils.equalsIgnoreCase(section, e.section)) |
|
|
|
result.add(e.subsection); |
|
|
@@ -1332,7 +1317,7 @@ public class Config { |
|
|
|
public Set<String> parse(Config cfg) { |
|
|
|
final Map<String, String> m = new LinkedHashMap<String, String>(); |
|
|
|
while (cfg != null) { |
|
|
|
for (final Entry e : cfg.state.get().entryList) { |
|
|
|
for (final ConfigLine e : cfg.state.get().entryList) { |
|
|
|
if (e.name == null) |
|
|
|
continue; |
|
|
|
if (!StringUtils.equalsIgnoreCase(section, e.section)) |
|
|
@@ -1355,7 +1340,7 @@ public class Config { |
|
|
|
public Set<String> parse(Config cfg) { |
|
|
|
final Map<String, String> m = new LinkedHashMap<String, String>(); |
|
|
|
while (cfg != null) { |
|
|
|
for (final Entry e : cfg.state.get().entryList) { |
|
|
|
for (final ConfigLine e : cfg.state.get().entryList) { |
|
|
|
if (e.section != null) { |
|
|
|
String lc = StringUtils.toLowerCase(e.section); |
|
|
|
if (!m.containsKey(lc)) |
|
|
@@ -1396,109 +1381,6 @@ public class Config { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static class State { |
|
|
|
final List<Entry> entryList; |
|
|
|
|
|
|
|
final Map<Object, Object> cache; |
|
|
|
|
|
|
|
final State baseState; |
|
|
|
|
|
|
|
State(List<Entry> entries, State base) { |
|
|
|
entryList = entries; |
|
|
|
cache = new ConcurrentHashMap<Object, Object>(16, 0.75f, 1); |
|
|
|
baseState = base; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* The configuration file entry |
|
|
|
*/ |
|
|
|
private static class Entry { |
|
|
|
|
|
|
|
/** |
|
|
|
* The text content before entry |
|
|
|
*/ |
|
|
|
String prefix; |
|
|
|
|
|
|
|
/** |
|
|
|
* The section name for the entry |
|
|
|
*/ |
|
|
|
String section; |
|
|
|
|
|
|
|
/** |
|
|
|
* Subsection name |
|
|
|
*/ |
|
|
|
String subsection; |
|
|
|
|
|
|
|
/** |
|
|
|
* The key name |
|
|
|
*/ |
|
|
|
String name; |
|
|
|
|
|
|
|
/** |
|
|
|
* The value |
|
|
|
*/ |
|
|
|
String value; |
|
|
|
|
|
|
|
/** |
|
|
|
* The text content after entry |
|
|
|
*/ |
|
|
|
String suffix; |
|
|
|
|
|
|
|
Entry forValue(final String newValue) { |
|
|
|
final Entry e = new Entry(); |
|
|
|
e.prefix = prefix; |
|
|
|
e.section = section; |
|
|
|
e.subsection = subsection; |
|
|
|
e.name = name; |
|
|
|
e.value = newValue; |
|
|
|
e.suffix = suffix; |
|
|
|
return e; |
|
|
|
} |
|
|
|
|
|
|
|
boolean match(final String aSection, final String aSubsection, |
|
|
|
final String aKey) { |
|
|
|
return eqIgnoreCase(section, aSection) |
|
|
|
&& eqSameCase(subsection, aSubsection) |
|
|
|
&& eqIgnoreCase(name, aKey); |
|
|
|
} |
|
|
|
|
|
|
|
boolean match(final String aSection, final String aSubsection) { |
|
|
|
return eqIgnoreCase(section, aSection) |
|
|
|
&& eqSameCase(subsection, aSubsection); |
|
|
|
} |
|
|
|
|
|
|
|
private static boolean eqIgnoreCase(final String a, final String b) { |
|
|
|
if (a == null && b == null) |
|
|
|
return true; |
|
|
|
if (a == null || b == null) |
|
|
|
return false; |
|
|
|
return StringUtils.equalsIgnoreCase(a, b); |
|
|
|
} |
|
|
|
|
|
|
|
private static boolean eqSameCase(final String a, final String b) { |
|
|
|
if (a == null && b == null) |
|
|
|
return true; |
|
|
|
if (a == null || b == null) |
|
|
|
return false; |
|
|
|
return a.equals(b); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public String toString() { |
|
|
|
if (section == null) |
|
|
|
return "<empty>"; |
|
|
|
StringBuilder b = new StringBuilder(section); |
|
|
|
if (subsection != null) |
|
|
|
b.append(".").append(subsection); |
|
|
|
if (name != null) |
|
|
|
b.append(".").append(name); |
|
|
|
if (value != null) |
|
|
|
b.append("=").append(value); |
|
|
|
return b.toString(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static class StringReader { |
|
|
|
private final char[] buf; |
|
|
|
|