Refactor: Extract group list manipulation

The group list manipulation is now encapsulated in a dedicated GroupList
class.

Change-Id: Id134d6a2eaab255ae167f4c401fca1690ac0985d
This commit is contained in:
Adrian Görler 2014-10-20 17:05:38 +02:00
parent bbe5b1376c
commit cc7c1c1ff0
4 changed files with 290 additions and 81 deletions

View File

@ -0,0 +1,142 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.git;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroup.UUID;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class GroupList {
public static final String FILE_NAME = "groups";
private final Map<AccountGroup.UUID, GroupReference> byUUID;
private GroupList(Map<AccountGroup.UUID, GroupReference> byUUID) {
this.byUUID = byUUID;
}
public static GroupList parse(String text, ValidationError.Sink errors) throws IOException {
Map<AccountGroup.UUID, GroupReference> groupsByUUID = new HashMap<>();
BufferedReader br = new BufferedReader(new StringReader(text));
String s;
for (int lineNumber = 1; (s = br.readLine()) != null; lineNumber++) {
if (s.isEmpty() || s.startsWith("#")) {
continue;
}
int tab = s.indexOf('\t');
if (tab < 0) {
errors.error(new ValidationError(FILE_NAME, lineNumber, "missing tab delimiter"));
continue;
}
AccountGroup.UUID uuid = new AccountGroup.UUID(s.substring(0, tab).trim());
String name = s.substring(tab + 1).trim();
GroupReference ref = new GroupReference(uuid, name);
groupsByUUID.put(uuid, ref);
}
return new GroupList(groupsByUUID);
}
public GroupReference byUUID(AccountGroup.UUID uuid) {
return byUUID.get(uuid);
}
public GroupReference resolve(GroupReference group) {
if (group != null) {
GroupReference ref = byUUID.get(group.getUUID());
if (ref != null) {
return ref;
}
byUUID.put(group.getUUID(), group);
}
return group;
}
public Collection<GroupReference> references() {
return byUUID.values();
}
public Set<AccountGroup.UUID> uuids() {
return byUUID.keySet();
}
public void put(UUID uuid, GroupReference reference) {
byUUID.put(uuid, reference);
}
private static String pad(int len, String src) {
if (len <= src.length()) {
return src;
}
StringBuilder r = new StringBuilder(len);
r.append(src);
while (r.length() < len) {
r.append(' ');
}
return r.toString();
}
private static <T extends Comparable<? super T>> List<T> sort(Collection<T> m) {
ArrayList<T> r = new ArrayList<>(m);
Collections.sort(r);
return r;
}
public String asText() {
if (byUUID.isEmpty()) {
return null;
}
final int uuidLen = 40;
StringBuilder buf = new StringBuilder();
buf.append(pad(uuidLen, "# UUID"));
buf.append('\t');
buf.append("Group Name");
buf.append('\n');
buf.append('#');
buf.append('\n');
for (GroupReference g : sort(byUUID.values())) {
if (g.getUUID() != null && g.getName() != null) {
buf.append(pad(uuidLen, g.getUUID().get()));
buf.append('\t');
buf.append(g.getName());
buf.append('\n');
}
}
return buf.toString();
}
public void retainUUIDs(Collection<AccountGroup.UUID> toBeRetained) {
byUUID.keySet().retainAll(toBeRetained);
}
}

View File

@ -58,9 +58,7 @@ import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.StringUtils; import org.eclipse.jgit.util.StringUtils;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -75,7 +73,7 @@ import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException; import java.util.regex.PatternSyntaxException;
public class ProjectConfig extends VersionedMetaData { public class ProjectConfig extends VersionedMetaData implements ValidationError.Sink {
public static final String COMMENTLINK = "commentlink"; public static final String COMMENTLINK = "commentlink";
private static final String KEY_MATCH = "match"; private static final String KEY_MATCH = "match";
private static final String KEY_HTML = "html"; private static final String KEY_HTML = "html";
@ -83,7 +81,6 @@ public class ProjectConfig extends VersionedMetaData {
private static final String KEY_ENABLED = "enabled"; private static final String KEY_ENABLED = "enabled";
public static final String PROJECT_CONFIG = "project.config"; public static final String PROJECT_CONFIG = "project.config";
private static final String GROUP_LIST = "groups";
private static final String PROJECT = "project"; private static final String PROJECT = "project";
private static final String KEY_DESCRIPTION = "description"; private static final String KEY_DESCRIPTION = "description";
@ -154,7 +151,7 @@ public class ProjectConfig extends VersionedMetaData {
private Project.NameKey projectName; private Project.NameKey projectName;
private Project project; private Project project;
private AccountsSection accountsSection; private AccountsSection accountsSection;
private Map<AccountGroup.UUID, GroupReference> groupsByUUID; private GroupList groupList;
private Map<String, AccessSection> accessSections; private Map<String, AccessSection> accessSections;
private BranchOrderSection branchOrderSection; private BranchOrderSection branchOrderSection;
private Map<String, ContributorAgreement> contributorAgreements; private Map<String, ContributorAgreement> contributorAgreements;
@ -324,24 +321,17 @@ public class ProjectConfig extends VersionedMetaData {
} }
public GroupReference resolve(GroupReference group) { public GroupReference resolve(GroupReference group) {
if (group != null) { return groupList.resolve(group);
GroupReference ref = groupsByUUID.get(group.getUUID());
if (ref != null) {
return ref;
}
groupsByUUID.put(group.getUUID(), group);
}
return group;
} }
/** @return the group reference, if the group is used by at least one rule. */ /** @return the group reference, if the group is used by at least one rule. */
public GroupReference getGroup(AccountGroup.UUID uuid) { public GroupReference getGroup(AccountGroup.UUID uuid) {
return groupsByUUID.get(uuid); return groupList.byUUID(uuid);
} }
/** @return set of all groups used by this configuration. */ /** @return set of all groups used by this configuration. */
public Set<AccountGroup.UUID> getAllGroupUUIDs() { public Set<AccountGroup.UUID> getAllGroupUUIDs() {
return Collections.unmodifiableSet(groupsByUUID.keySet()); return groupList.uuids();
} }
/** /**
@ -375,7 +365,7 @@ public class ProjectConfig extends VersionedMetaData {
*/ */
public boolean updateGroupNames(GroupBackend groupBackend) { public boolean updateGroupNames(GroupBackend groupBackend) {
boolean dirty = false; boolean dirty = false;
for (GroupReference ref : groupsByUUID.values()) { for (GroupReference ref : groupList.references()) {
GroupDescription.Basic g = groupBackend.get(ref.getUUID()); GroupDescription.Basic g = groupBackend.get(ref.getUUID());
if (g != null && !g.getName().equals(ref.getName())) { if (g != null && !g.getName().equals(ref.getName())) {
dirty = true; dirty = true;
@ -405,7 +395,8 @@ public class ProjectConfig extends VersionedMetaData {
@Override @Override
protected void onLoad() throws IOException, ConfigInvalidException { protected void onLoad() throws IOException, ConfigInvalidException {
Map<String, GroupReference> groupsByName = readGroupList(); readGroupList();
Map<String, GroupReference> groupsByName = mapGroupReferences();
rulesId = getObjectId("rules.pl"); rulesId = getObjectId("rules.pl");
Config rc = readConfig(PROJECT_CONFIG); Config rc = readConfig(PROJECT_CONFIG);
@ -531,7 +522,7 @@ public class ProjectConfig extends VersionedMetaData {
n.addEmail(ref); n.addEmail(ref);
} else { } else {
error(new ValidationError(PROJECT_CONFIG, error(new ValidationError(PROJECT_CONFIG,
"group \"" + ref.getName() + "\" not in " + GROUP_LIST)); "group \"" + ref.getName() + "\" not in " + GroupList.FILE_NAME));
} }
} else if (dst.startsWith("user ")) { } else if (dst.startsWith("user ")) {
error(new ValidationError(PROJECT_CONFIG, dst + " not supported")); error(new ValidationError(PROJECT_CONFIG, dst + " not supported"));
@ -627,7 +618,7 @@ public class ProjectConfig extends VersionedMetaData {
ref = rule.getGroup(); ref = rule.getGroup();
groupsByName.put(ref.getName(), ref); groupsByName.put(ref.getName(), ref);
error(new ValidationError(PROJECT_CONFIG, error(new ValidationError(PROJECT_CONFIG,
"group \"" + ref.getName() + "\" not in " + GROUP_LIST)); "group \"" + ref.getName() + "\" not in " + GroupList.FILE_NAME));
} }
rule.setGroup(ref); rule.setGroup(ref);
@ -774,31 +765,18 @@ public class ProjectConfig extends VersionedMetaData {
return new PluginConfig(pluginName, pluginConfig, this); return new PluginConfig(pluginName, pluginConfig, this);
} }
private Map<String, GroupReference> readGroupList() throws IOException { private void readGroupList() throws IOException {
groupsByUUID = new HashMap<>(); groupList = GroupList.parse(readUTF8(GroupList.FILE_NAME), this);
Map<String, GroupReference> groupsByName = new HashMap<>();
BufferedReader br = new BufferedReader(new StringReader(readUTF8(GROUP_LIST)));
String s;
for (int lineNumber = 1; (s = br.readLine()) != null; lineNumber++) {
if (s.isEmpty() || s.startsWith("#")) {
continue;
} }
int tab = s.indexOf('\t'); private Map<String, GroupReference> mapGroupReferences() {
if (tab < 0) { Collection<GroupReference> references = groupList.references();
error(new ValidationError(GROUP_LIST, lineNumber, "missing tab delimiter")); Map<String, GroupReference> result = new HashMap<>(references.size());
continue; for (GroupReference ref : references) {
result.put(ref.getName(), ref);
} }
AccountGroup.UUID uuid = new AccountGroup.UUID(s.substring(0, tab).trim()); return result;
String name = s.substring(tab + 1).trim();
GroupReference ref = new GroupReference(uuid, name);
groupsByUUID.put(uuid, ref);
groupsByName.put(name, ref);
}
return groupsByName;
} }
@Override @Override
@ -837,7 +815,7 @@ public class ProjectConfig extends VersionedMetaData {
saveContributorAgreements(rc, keepGroups); saveContributorAgreements(rc, keepGroups);
saveAccessSections(rc, keepGroups); saveAccessSections(rc, keepGroups);
saveNotifySections(rc, keepGroups); saveNotifySections(rc, keepGroups);
groupsByUUID.keySet().retainAll(keepGroups); groupList.retainUUIDs(keepGroups);
saveLabelSections(rc); saveLabelSections(rc);
savePluginSections(rc); savePluginSections(rc);
@ -1110,30 +1088,7 @@ public class ProjectConfig extends VersionedMetaData {
} }
private void saveGroupList() throws IOException { private void saveGroupList() throws IOException {
if (groupsByUUID.isEmpty()) { saveUTF8(GroupList.FILE_NAME, groupList.asText());
saveFile(GROUP_LIST, null);
return;
}
final int uuidLen = 40;
StringBuilder buf = new StringBuilder();
buf.append(pad(uuidLen, "# UUID"));
buf.append('\t');
buf.append("Group Name");
buf.append('\n');
buf.append('#');
buf.append('\n');
for (GroupReference g : sort(groupsByUUID.values())) {
if (g.getUUID() != null && g.getName() != null) {
buf.append(pad(uuidLen, g.getUUID().get()));
buf.append('\t');
buf.append(g.getName());
buf.append('\n');
}
}
saveUTF8(GROUP_LIST, buf.toString());
} }
private <E extends Enum<?>> E getEnum(Config rc, String section, private <E extends Enum<?>> E getEnum(Config rc, String section,
@ -1146,26 +1101,13 @@ public class ProjectConfig extends VersionedMetaData {
} }
} }
private void error(ValidationError error) { public void error(ValidationError error) {
if (validationErrors == null) { if (validationErrors == null) {
validationErrors = new ArrayList<>(4); validationErrors = new ArrayList<>(4);
} }
validationErrors.add(error); validationErrors.add(error);
} }
private static String pad(int len, String src) {
if (len <= src.length()) {
return src;
}
StringBuilder r = new StringBuilder(len);
r.append(src);
while (r.length() < len) {
r.append(' ');
}
return r.toString();
}
private static <T extends Comparable<? super T>> List<T> sort(Collection<T> m) { private static <T extends Comparable<? super T>> List<T> sort(Collection<T> m) {
ArrayList<T> r = new ArrayList<>(m); ArrayList<T> r = new ArrayList<>(m);
Collections.sort(r); Collections.sort(r);

View File

@ -38,4 +38,8 @@ public class ValidationError {
public String toString() { public String toString() {
return "ValidationError[" + message + "]"; return "ValidationError[" + message + "]";
} }
public interface Sink {
void error(ValidationError error);
}
} }

View File

@ -0,0 +1,121 @@
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.git;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
public class GroupListTest {
private static final String TEXT =
"# UUID \tGroup Name\n" + "#\n"
+ "d96b998f8a66ff433af50befb975d0e2bb6e0999\tNon-Interactive Users\n"
+ "ebe31c01aec2c9ac3b3c03e87a47450829ff4310\tAdministrators\n";
private GroupList groupList;
@Before
public void setup() throws IOException {
ValidationError.Sink sink = createNiceMock(ValidationError.Sink.class);
replay(sink);
groupList = GroupList.parse(TEXT, sink);
}
@Test
public void testByUUID() throws Exception {
AccountGroup.UUID uuid =
new AccountGroup.UUID("d96b998f8a66ff433af50befb975d0e2bb6e0999");
GroupReference groupReference = groupList.byUUID(uuid);
assertEquals(uuid, groupReference.getUUID());
assertEquals("Non-Interactive Users", groupReference.getName());
}
@Test
public void testPut() {
AccountGroup.UUID uuid = new AccountGroup.UUID("abc");
GroupReference groupReference = new GroupReference(uuid, "Hutzliputz");
groupList.put(uuid, groupReference);
assertEquals(3, groupList.references().size());
GroupReference found = groupList.byUUID(uuid);
assertEquals(groupReference, found);
}
@Test
public void testReferences() throws Exception {
Collection<GroupReference> result = groupList.references();
assertEquals(2, result.size());
AccountGroup.UUID uuid =
new AccountGroup.UUID("ebe31c01aec2c9ac3b3c03e87a47450829ff4310");
GroupReference expected = new GroupReference(uuid, "Administrators");
assertTrue(result.contains(expected));
}
@Test
public void testUUIDs() throws Exception {
Set<AccountGroup.UUID> result = groupList.uuids();
assertEquals(2, result.size());
AccountGroup.UUID expected =
new AccountGroup.UUID("ebe31c01aec2c9ac3b3c03e87a47450829ff4310");
assertTrue(result.contains(expected));
}
@Test
public void testValidationError() throws Exception {
ValidationError.Sink sink = createMock(ValidationError.Sink.class);
sink.error(anyObject(ValidationError.class));
expectLastCall().times(2);
replay(sink);
groupList = GroupList.parse(TEXT.replace("\t", " "), sink);
verify(sink);
}
@Test
public void testRetainAll() throws Exception {
AccountGroup.UUID uuid =
new AccountGroup.UUID("d96b998f8a66ff433af50befb975d0e2bb6e0999");
groupList.retainUUIDs(Collections.singleton(uuid));
assertNotNull(groupList.byUUID(uuid));
assertNull(groupList.byUUID(new AccountGroup.UUID(
"ebe31c01aec2c9ac3b3c03e87a47450829ff4310")));
}
}