Config: Preserve existing case of names in sections

When an application asks for the names in a section, it may want to
see the existing case that was stored by the user.  For example,
Gerrit Code Review wants to store a configuration block like:

  [access "refs/heads/master"]
    label-Code-Review = group Developers

and although the name label-Code-Review is case-insensitive, it wants
to display the case as it appeared in the configuration file.

When enumerating section names or variable names (both of which are
case-insensitive), Config now keeps track of the string that first
appeared, and presents them in file order, permitting applications to
use this information.  To maintain case-insensitive behavior, the
contains() method of the returned Set<String> still performs a
case-insensitive compare.

This is a behavior change if the caller enumerates the returned
Set<String> and copies it to his own Set<String>, and then performs
contains() tests against that, as the strings are now the original
case from the configuration block.  But I don't think anyone actually
does this, as the returned sets are immutable and are cached.

Change-Id: Ie4e060ef7772958b2062679e462c34c506371740
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
This commit is contained in:
Shawn O. Pearce 2011-01-06 10:45:25 -08:00
parent 7cd812940d
commit b2d528887c
2 changed files with 64 additions and 16 deletions

View File

@ -58,6 +58,7 @@
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
@ -386,13 +387,25 @@ public void test008_readSectionNames() throws ConfigInvalidException {
@Test
public void test009_readNamesInSection() throws ConfigInvalidException {
String configString = "[core]\n" + "repositoryformatversion = 0\n"
+ "filemode = false\n" + "logallrefupdates = true\n";
String configString = "[core]\n" + "repositoryFormatVersion = 0\n"
+ "filemode = false\n" + "logAllRefUpdates = true\n";
final Config c = parse(configString);
Set<String> names = c.getNames("core");
assertEquals("Core section size", 3, names.size());
assertTrue("Core section should contain \"filemode\"", names
.contains("filemode"));
assertTrue("Core section should contain \"repositoryFormatVersion\"",
names.contains("repositoryFormatVersion"));
assertTrue("Core section should contain \"repositoryformatversion\"",
names.contains("repositoryformatversion"));
Iterator<String> itr = names.iterator();
assertEquals("repositoryFormatVersion", itr.next());
assertEquals("filemode", itr.next());
assertEquals("logAllRefUpdates", itr.next());
assertFalse(itr.hasNext());
}
@Test

View File

@ -52,9 +52,12 @@
package org.eclipse.jgit.lib;
import java.text.MessageFormat;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -1327,39 +1330,71 @@ public boolean equals(Object obj) {
}
public Set<String> parse(Config cfg) {
final Set<String> result = new HashSet<String>();
final Map<String, String> m = new LinkedHashMap<String, String>();
while (cfg != null) {
for (final Entry e : cfg.state.get().entryList) {
if (e.name != null
&& StringUtils.equalsIgnoreCase(e.section, section)) {
if (subsection == null && e.subsection == null)
result.add(StringUtils.toLowerCase(e.name));
else if (e.subsection != null
&& e.subsection.equals(subsection))
result.add(StringUtils.toLowerCase(e.name));
if (e.name == null)
continue;
if (!StringUtils.equalsIgnoreCase(section, e.section))
continue;
if ((subsection == null && e.subsection == null)
|| (subsection != null && subsection
.equals(e.subsection))) {
String lc = StringUtils.toLowerCase(e.name);
if (!m.containsKey(lc))
m.put(lc, e.name);
}
}
cfg = cfg.baseConfig;
}
return Collections.unmodifiableSet(result);
return new CaseFoldingSet(m);
}
}
private static class SectionNames implements SectionParser<Set<String>> {
public Set<String> parse(Config cfg) {
final Set<String> result = new HashSet<String>();
final Map<String, String> m = new LinkedHashMap<String, String>();
while (cfg != null) {
for (final Entry e : cfg.state.get().entryList) {
if (e.section != null)
result.add(StringUtils.toLowerCase(e.section));
if (e.section != null) {
String lc = StringUtils.toLowerCase(e.section);
if (!m.containsKey(lc))
m.put(lc, e.section);
}
}
cfg = cfg.baseConfig;
}
return Collections.unmodifiableSet(result);
return new CaseFoldingSet(m);
}
}
private static class CaseFoldingSet extends AbstractSet<String> {
private final Map<String, String> names;
CaseFoldingSet(Map<String, String> names) {
this.names = Collections.unmodifiableMap(names);
}
@Override
public boolean contains(Object needle) {
if (!(needle instanceof String))
return false;
String n = (String) needle;
return names.containsKey(n)
|| names.containsKey(StringUtils.toLowerCase(n));
}
@Override
public Iterator<String> iterator() {
return names.values().iterator();
}
@Override
public int size() {
return names.size();
}
}
private static class State {
final List<Entry> entryList;