ConcurrentGroupList.java 6.06 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
package org.jivesoftware.openfire.group;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import org.xmpp.packet.JID;

/**
 * This list specifies additional methods that understand groups among 
 * the items in the list.
 * 
 * @author Tom Evans
 */
public class ConcurrentGroupList<T> extends CopyOnWriteArrayList<T> implements GroupAwareList<T> {

19 20
	private static final long serialVersionUID = 7735849143650412115L;

21 22 23 24
	// This set is used to optimize group operations within this list.
	// We only populate this set when it's needed to dereference the
	// groups in the base list, but once it exists we keep it in sync
	// via the various add/remove operations.
25 26
	// NOTE: added volatile keyword for double-check idiom (lazy instantiation)
	private volatile transient Set<String> knownGroupNamesInList;
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
	
	public ConcurrentGroupList() {
		super();
	}

	public ConcurrentGroupList(Collection<? extends T> c) {
		super(c);
	}

	/**
	 * Returns true if the list contains the given JID. If the JID
	 * is not found in the list, search the list for groups and look
	 * for the JID in each of the corresponding groups.
	 * 
	 * @param value The target, presumably a JID
	 * @return True if the target is in the list, or in any groups in the list
	 */
	@Override
	public boolean includes(Object value) {
		boolean found = false;
		if (contains(value)) {
			found = true;
		} else if (value instanceof JID) {
			JID target = (JID) value;
			Iterator<Group> iterator = getGroups().iterator();
			while (!found && iterator.hasNext()) {
				found = iterator.next().isUser(target);
			}
		}
		return found;
	}

	/**
	 * Returns the groups that are implied (resolvable) from the items in the list.
	 * 
	 * @return A Set containing the groups in the list
	 */
	@Override
65
	public Set<Group> getGroups() {
66
		Set<Group> result = new HashSet<>();
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
		for (String groupName : getKnownGroupNamesInList()) {
			result.add(Group.resolveFrom(groupName));
		}
		return result;
	}
	
	/**
	 * Accessor uses the  "double-check idiom" (j2se 5.0+) for proper lazy instantiation.
	 * Additionally, the set is not cached until there is at least one group in the list.
	 * 
	 * @return the known group names among the items in the list
	 */
	private Set<String> getKnownGroupNamesInList() {
		Set<String> result = knownGroupNamesInList;
		if (result == null) {
			synchronized(this) {
				result = knownGroupNamesInList;
				if (result == null) {
85
					result = new HashSet<>();
86 87 88 89 90 91 92 93 94 95 96
					// add all the groups into the group set
					Iterator<T> iterator = iterator();
					while (iterator.hasNext()) {
						T listItem = iterator.next();
						Group group = Group.resolveFrom(listItem);
						if (group != null) {
							result.add(group.getName());
						};
					}
					knownGroupNamesInList = result.isEmpty() ? null : result;
				}
97 98
			}
		}
99
		return result;
100 101 102 103 104 105 106 107 108 109 110 111 112
	}

	/**
	 * This method is called from several of the mutators to keep
	 * the group set in sync with the full list. 
	 * 
	 * @param item The item to be added or removed if it is in the group set
	 * @param addOrRemove True to add, false to remove
	 * @return true if the given item is a group
	 */
	private synchronized boolean syncGroups(Object item, boolean addOrRemove) {
		boolean result = false;
		// only sync if the group list has been instantiated
113
		if (knownGroupNamesInList != null) {
114 115 116 117
			Group group = Group.resolveFrom(item);
			if (group != null) {
				result = true;
				if (addOrRemove == ADD) {
118
					knownGroupNamesInList.add(group.getName());
119
				} else if (addOrRemove == REMOVE) {
120 121 122 123
					knownGroupNamesInList.remove(group.getName());
					if (knownGroupNamesInList.isEmpty()) {
						knownGroupNamesInList = null;
					}
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
				}
			}
		}
		return result;
	}
	
	// below are overrides for the various mutators
	
	@Override
	public T set(int index, T element) {
		T result = super.set(index, element);
		syncGroups(element, ADD);
		return result;
	}

	@Override
	public boolean add(T e) {
		boolean result = super.add(e);
		syncGroups(e, ADD);
		return result;
	}

	@Override
	public void add(int index, T element) {
		super.add(index, element);
		syncGroups(element, ADD);
	}

	@Override
	public T remove(int index) {
		T result = super.remove(index);
		syncGroups(result, REMOVE);
		return result;
	}

	@Override
	public boolean remove(Object o) {
		boolean removed = super.remove(o);
		if (removed) {
			syncGroups(o, REMOVE);
		}
		return removed;
	}

	@Override
	public boolean addIfAbsent(T e) {
		boolean added = super.addIfAbsent(e);
		if (added) {
			syncGroups(e, ADD);
		}
		return added;
	}

	@Override
	public boolean removeAll(Collection<?> c) {
		boolean changed = super.removeAll(c);
		if (changed) {
			// drop the transient set, will be rebuilt when/if needed
			synchronized(this) {
183
				knownGroupNamesInList = null;
184 185 186 187 188 189 190 191 192 193 194
			}
		}
		return changed;
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		boolean changed = super.retainAll(c);
		if (changed) {
			// drop the transient set, will be rebuilt when/if needed
			synchronized(this) {
195
				knownGroupNamesInList = null;
196 197 198 199 200 201 202 203 204 205 206
			}
		}
		return changed;
	}

	@Override
	public int addAllAbsent(Collection<? extends T> c) {
		int added = super.addAllAbsent(c);
		if (added > 0) {
			// drop the transient set, will be rebuilt when/if needed
			synchronized(this) {
207
				knownGroupNamesInList = null;
208 209 210 211 212 213 214 215 216
			}
		}
		return added;
	}

	@Override
	public void clear() {
		super.clear();
		synchronized(this) {
217
			knownGroupNamesInList = null;
218 219 220 221 222 223 224 225 226
		}
	}

	@Override
	public boolean addAll(Collection<? extends T> c) {
		boolean changed = super.addAll(c);
		if (changed) {
			// drop the transient set, will be rebuilt when/if needed
			synchronized(this) {
227
				knownGroupNamesInList = null;
228 229 230 231 232 233 234 235 236 237 238
			}
		}
		return changed;
	}

	@Override
	public boolean addAll(int index, Collection<? extends T> c) {
		boolean changed = super.addAll(index, c);
		if (changed) {
			// drop the transient set, will be rebuilt when/if needed
			synchronized(this) {
239
				knownGroupNamesInList = null;
240 241 242 243 244 245 246 247
			}
		}
		return changed;
	}

	private static final boolean ADD = true;
	private static final boolean REMOVE = false;
}