1 /*
2 * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.opensaml.xml.util;
18
19 import java.util.AbstractSet;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.Set;
24
25 /**
26 * Set implementation which provides indexed access to set members via their class,
27 * and which allows only one instance of a given class to be present in the set. Null
28 * members are not allowed.
29 *
30 * @param <T> the type of object stored by this class
31 */
32 public class ClassIndexedSet<T> extends AbstractSet<T> implements Set<T> {
33
34 /** Storage for set members. */
35 private HashSet<T> set;
36
37 /** Storage for index of class -> member. */
38 private HashMap<Class<? extends T>, T> index;
39
40 /**
41 * Constructor.
42 *
43 */
44 public ClassIndexedSet() {
45 set = new HashSet<T>();
46 index = new HashMap<Class<? extends T>, T>();
47 }
48
49 /** {@inheritDoc} */
50 public boolean add(T o) {
51 return add(o, false);
52 }
53
54 /**
55 * Add member to set, optionally replacing any existing instance of the
56 * same class.
57 *
58 * @param o the object to add
59 * @param replace flag indicating whether to replace an existing class type
60 * @return true if object was added
61 * @throws IllegalArgumentException if set already contained an instance of the object's class
62 * and replacement was specified as false
63 * @throws NullPointerException if the object to add was null
64 */
65 @SuppressWarnings("unchecked")
66 public boolean add(T o, boolean replace) throws NullPointerException, IllegalArgumentException {
67 if (o == null) {
68 throw new NullPointerException("Null elements are not allowed");
69 }
70 boolean replacing = false;
71 Class<? extends T> indexClass = getIndexClass(o);
72 T existing = get(indexClass);
73 if (existing != null) {
74 replacing = true;
75 if (replace) {
76 remove(existing);
77 } else {
78 throw new IllegalArgumentException("Set already contains a member of index class "
79 + indexClass.getName());
80 }
81 }
82 index.put(indexClass, o);
83 set.add(o);
84 return replacing;
85 }
86
87 /** {@inheritDoc} */
88 public void clear() {
89 set.clear();
90 index.clear();
91 }
92
93 /** {@inheritDoc} */
94 @SuppressWarnings("unchecked")
95 public boolean remove(Object o) {
96 if (set.contains(o)) {
97 removeFromIndex((T) o);
98 set.remove(o);
99 return true;
100 }
101 return false;
102 }
103
104 /** {@inheritDoc} */
105 public Iterator<T> iterator() {
106 return new ClassIndexedSetIterator(this, set.iterator());
107 }
108
109 /** {@inheritDoc} */
110 public int size() {
111 return set.size();
112 }
113
114 /**
115 * Check whether set contains an instance of the specified class.
116 *
117 * @param clazz the class to check
118 * @return true if set contains an instance of the specified class, false otherwise
119 */
120 public boolean contains(Class<? extends T> clazz) {
121 return get(clazz)!=null;
122 }
123
124 /**
125 * Get the set element specified by the class parameter.
126 * @param <X> generic parameter which eliminates need for casting by the caller
127 * @param clazz the class to whose instance is to be retrieved
128 * @return the element whose class is of the type specified, or null
129 *
130 */
131 @SuppressWarnings("unchecked")
132 public <X extends T> X get(Class<X> clazz) {
133 return (X) index.get(clazz);
134 }
135
136 /**
137 * Get the index class of the specified object. Subclasses may override to use
138 * a class index other than the main runtime class of the object.
139 *
140 * @param o the object whose class index to determine
141 * @return the class index value associated with the object instance
142 */
143 @SuppressWarnings("unchecked")
144 protected Class<? extends T> getIndexClass(Object o) {
145 return (Class<? extends T>) o.getClass();
146 }
147
148 /**
149 * Remove the specified object from the index.
150 *
151 * @param o the object to remove
152 */
153 private void removeFromIndex(T o) {
154 index.remove(getIndexClass(o));
155 }
156
157 /**
158 * Iterator for set implementation {@link ClassIndexedSet}.
159 *
160 */
161 protected class ClassIndexedSetIterator implements Iterator<T> {
162
163 /** The set instance over which this instance is an iterator. */
164 private ClassIndexedSet<T> set;
165
166 /** The iterator for the owner's underlying storage. */
167 private Iterator<T> iterator;
168
169 /** Flag which tracks whether next() has been called at least once. */
170 private boolean nextCalled;
171
172 /** Flag which tracks whether remove can currently be called. */
173 private boolean removeStateValid;;
174
175 /** The element most recently returned by next(), and the target for any subsequent
176 * remove() operation. */
177 private T current;
178
179 /**
180 * Constructor.
181 *
182 * @param parentSet the {@link ClassIndexedSet} over which this instance is an iterator
183 * @param parentIterator the iterator for the parent's underlying storage
184 */
185 protected ClassIndexedSetIterator(ClassIndexedSet<T> parentSet, Iterator<T> parentIterator) {
186 set = parentSet;
187 iterator = parentIterator;
188 current = null;
189 nextCalled = false;
190 removeStateValid = false;
191 }
192
193 /** {@inheritDoc} */
194 public boolean hasNext() {
195 return iterator.hasNext();
196 }
197
198 /** {@inheritDoc} */
199 public T next() {
200 current = iterator.next();
201 nextCalled = true;
202 removeStateValid = true;
203 return current;
204 }
205
206 /** {@inheritDoc} */
207 public void remove() {
208 if (! nextCalled) {
209 throw new IllegalStateException("remove() was called before calling next()");
210 }
211 if (! removeStateValid) {
212 throw new IllegalStateException("remove() has already been called since the last call to next()");
213 }
214 iterator.remove();
215 set.removeFromIndex(current);
216 removeStateValid = false;
217 }
218
219 }
220
221 }