From b2d528887c087fd9c63eaa3ab291d19e81d24b36 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 6 Jan 2011 10:45:25 -0800 Subject: [PATCH] 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 still performs a case-insensitive compare. This is a behavior change if the caller enumerates the returned Set and copies it to his own Set, 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 --- .../tst/org/eclipse/jgit/lib/ConfigTest.java | 17 ++++- .../src/org/eclipse/jgit/lib/Config.java | 63 ++++++++++++++----- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java index 8737b697c..d5da16ad8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java @@ -58,6 +58,7 @@ import static org.junit.Assert.fail; 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 class ConfigTest { @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 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 itr = names.iterator(); + assertEquals("repositoryFormatVersion", itr.next()); + assertEquals("filemode", itr.next()); + assertEquals("logAllRefUpdates", itr.next()); + assertFalse(itr.hasNext()); } @Test diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index daad67e29..ce86dc20f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -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 class Config { } public Set parse(Config cfg) { - final Set result = new HashSet(); + final Map m = new LinkedHashMap(); 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> { public Set parse(Config cfg) { - final Set result = new HashSet(); + final Map m = new LinkedHashMap(); 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 { + private final Map names; + + CaseFoldingSet(Map 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 iterator() { + return names.values().iterator(); + } + + @Override + public int size() { + return names.size(); + } + } private static class State { final List entryList;