package nl.minicom.gitolite.manager.models;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import nl.minicom.gitolite.manager.exceptions.ModificationException;
import nl.minicom.gitolite.manager.models.Recorder.Modification;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ImmutableSortedSet.Builder;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* This class represents a {@link Group} in the gitolite configuration.
*
* @author Michael de Jong <<a href="mailto:michaelj@minicom.nl">michaelj@minicom.nl</a>>
*/
public final class Group implements Identifiable {
static final Comparator<Group> SORT_BY_NAME = new Comparator<Group>() {
@Override
public int compare(Group arg0, Group arg1) {
return arg0.getName().compareTo(arg1.getName());
}
};
private final String name;
private final Recorder recorder;
private final SortedSet<Group> groups;
private final SortedSet<User> users;
/**
* Constructs a new {@link Group} object with the specified name.
*
* @param name
* The name of the group. The name must be a non-null, not-empty value.
*/
Group(String name) {
this(name, new Recorder());
}
/**
* Constructs a new {@link Group} object with the specified name.
*
* @param name
* The name of the group. The name must be a non-null, not-empty value.
*
* @param recorder
* The {@link Recorder} to use when recording changes of this {@link Group}.
*/
Group(String name, Recorder recorder) {
Preconditions.checkNotNull(name);
Preconditions.checkArgument(!name.isEmpty());
Preconditions.checkArgument(name.matches("^\\@\\w[\\w._\\@+-]+$"), "\"" + name + "\" is not a valid group name");
Preconditions.checkNotNull(recorder);
this.name = name;
this.recorder = recorder;
this.groups = Sets.newTreeSet(Group.SORT_BY_NAME);
this.users = Sets.newTreeSet(User.SORT_BY_NAME);
}
/**
* @return
* The name of this {@link Group}.
*/
@Override
public String getName() {
return name;
}
/**
* This method adds a {@link User} object to the {@link Group}.
*
* @param user
* The {@link User} to add to this {@link Group}. This may not be NULL.
* In case the {@link User} is already a member of this group an
* {@link IllegalArgumentException} is thrown.
*/
public void add(User user) {
Preconditions.checkArgument(!isAllGroup());
Preconditions.checkNotNull(user);
synchronized (users) {
if (users.contains(user)) {
throw new IllegalArgumentException("Cannot add user: '" + user.getName() + "'. It's already added!");
}
users.add(user);
}
final String childName = user.getName();
recorder.append(new Modification("Adding user: '%s' to group: '%s'", childName, getName()) {
@Override
public void apply(Config config) throws ModificationException {
Group parent = config.getGroup(getName());
User child = config.getUser(childName);
parent.add(child);
}
});
}
/**
* This method removes a {@link User} object from the {@link Group}.
*
* @param user
* The {@link User} to remove from this {@link Group}. This may not be NULL.
* In case the {@link User} is not a member of this group an
* {@link IllegalArgumentException} is thrown.
*/
public void remove(User user) {
Preconditions.checkArgument(!isAllGroup());
Preconditions.checkNotNull(user);
synchronized (users) {
if (!users.contains(user)) {
throw new IllegalArgumentException("Cannot remove user: '" + user.getName() + "'. It's not a member!");
}
users.remove(user);
}
final String childName = user.getName();
recorder.append(new Modification("Removing user: '%s' from group: '%s'", childName, getName()) {
@Override
public void apply(Config config) throws ModificationException {
Group parent = config.getGroup(getName());
User child = config.getUser(childName);
parent.remove(child);
}
});
}
/**
* This method adds a child {@link Group} object to the {@link Group}.
*
* @param group
* The {@link Group} to add to this {@link Group}. This may not be NULL.
* In case the {@link Group} is already a member of this group an
* {@link IllegalArgumentException} is thrown.
*/
public void add(Group group) {
Preconditions.checkArgument(!isAllGroup());
Preconditions.checkNotNull(group);
synchronized (groups) {
if (groups.contains(group)) {
throw new IllegalArgumentException("Cannot add group: '" + group.getName() + "'. It's already added!");
}
if (group.isAllGroup()) {
throw new IllegalArgumentException("Cannot add group: '" + group.getName() + "'. The @all group cannot be a member!");
}
groups.add(group);
if (cycleDetected()) {
groups.remove(group);
throw new IllegalArgumentException("Cannot add group: '" + group.getName() + "'. This would create a cycle!");
}
}
final String groupName = group.getName();
recorder.append(new Modification("Adding group: '%s' to group: '%s'", groupName, getName()) {
@Override
public void apply(Config config) throws ModificationException {
Group parent = config.getGroup(getName());
Group child = config.getGroup(groupName);
parent.add(child);
}
});
}
/**
* This method removes a child {@link Group} object from the {@link Group}.
*
* @param group
* The {@link Group} to remove from this {@link Group}. This may not be NULL.
* In case the {@link Group} is already a member of this group an
* {@link IllegalArgumentException} is thrown.
*/
public void remove(Group group) {
Preconditions.checkArgument(!isAllGroup());
Preconditions.checkNotNull(group);
synchronized (groups) {
if (!groups.contains(group)) {
throw new IllegalArgumentException("Cannot remove group: '" + group.getName() + "'. It's not a member!");
}
groups.remove(group);
}
final String groupName = group.getName();
recorder.append(new Modification("Removing group: '%s' from group: '%s'", groupName, getName()) {
@Override
public void apply(Config config) throws ModificationException {
Group parent = config.getGroup(getName());
Group child = config.getGroup(groupName);
parent.remove(child);
}
});
}
/**
* This method returns true if the specified {@link User} is a member
* of this {@link Group}.
*
* @param user
* The {@link User} to look for.
*
* @return
* True if the specified {@link User} is a member of this {@link Group}.
*/
public boolean containsUser(User user) {
Preconditions.checkNotNull(user);
return getUsers().contains(user);
}
/**
* This method returns true if the specified {@link Group} is a child
* of this {@link Group}.
*
* @param group
* The {@link Group} to look for.
*
* @return
* True if the specified {@link Group} is a child of this {@link Group}.
*/
public boolean containsGroup(Group group) {
Preconditions.checkNotNull(group);
if (getGroups().contains(group)) {
return true;
}
for (Group child : getGroups()) {
if (child.containsGroup(group)) {
return true;
}
}
return false;
}
/**
* @return
* An {@link ImmutableSet} of child {@link Group}s of this {@link Group}.
*/
public ImmutableSet<Group> getGroups() {
synchronized (groups) {
return ImmutableSortedSet.copyOf(SORT_BY_NAME, groups);
}
}
/**
* @return
* An {@link ImmutableSet} of child {@link User}s of this {@link Group}.
*/
public ImmutableSet<User> getUsers() {
synchronized (users) {
return ImmutableSortedSet.copyOf(User.SORT_BY_NAME, users);
}
}
/**
* @return
* A {@link ImmutableSet} containing all {@link User}s and {@link Group}s.
*/
public ImmutableSet<Identifiable> getAllMembers() {
Builder<Identifiable> builder = ImmutableSortedSet.orderedBy(Identifiable.SORT_BY_TYPE_AND_NAME);
synchronized (groups) {
builder.addAll(groups);
}
synchronized (users) {
builder.addAll(users);
}
return builder.build();
}
private boolean isAllGroup() {
return name.equals("@all");
}
private boolean cycleDetected() {
List<Group> toVisit = Lists.newArrayList(groups);
while (!toVisit.isEmpty()) {
Group visiting = toVisit.remove(0);
if (visiting.equals(this)) {
return true;
}
toVisit.addAll(visiting.groups);
}
return false;
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.append(name)
.toHashCode();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Group)) {
return false;
}
return new EqualsBuilder()
.append(name, ((Group) other).name)
.isEquals();
}
}