001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.apache.hadoop.util;
019
020 import java.util.Collection;
021 import java.util.Iterator;
022 import java.util.NoSuchElementException;
023
024 import org.apache.commons.logging.Log;
025 import org.apache.commons.logging.LogFactory;
026 import org.apache.hadoop.classification.InterfaceAudience;
027
028 import com.google.common.base.Preconditions;
029
030 /**
031 * Implements an intrusive doubly-linked list.
032 *
033 * An intrusive linked list is one in which the elements themselves are
034 * responsible for storing the pointers to previous and next elements.
035 * This can save a lot of memory if there are many elements in the list or
036 * many lists.
037 */
038 @InterfaceAudience.Private
039 public class IntrusiveCollection<E extends IntrusiveCollection.Element>
040 implements Collection<E> {
041 /**
042 * An element contained in this list.
043 *
044 * We pass the list itself as a parameter so that elements can belong to
045 * multiple lists. (The element will need to store separate prev and next
046 * pointers for each.)
047 */
048 @InterfaceAudience.Private
049 public interface Element {
050 /**
051 * Insert this element into the list. This is the first thing that will
052 * be called on the element.
053 */
054 void insertInternal(IntrusiveCollection<? extends Element> list,
055 Element prev, Element next);
056
057 /**
058 * Set the prev pointer of an element already in the list.
059 */
060 void setPrev(IntrusiveCollection<? extends Element> list, Element prev);
061
062 /**
063 * Set the next pointer of an element already in the list.
064 */
065 void setNext(IntrusiveCollection<? extends Element> list, Element next);
066
067 /**
068 * Remove an element from the list. This is the last thing that will be
069 * called on an element.
070 */
071 void removeInternal(IntrusiveCollection<? extends Element> list);
072
073 /**
074 * Get the prev pointer of an element.
075 */
076 Element getPrev(IntrusiveCollection<? extends Element> list);
077
078 /**
079 * Get the next pointer of an element.
080 */
081 Element getNext(IntrusiveCollection<? extends Element> list);
082
083 /**
084 * Returns true if this element is in the provided list.
085 */
086 boolean isInList(IntrusiveCollection<? extends Element> list);
087 }
088
089 private Element root = new Element() {
090 // We keep references to the first and last elements for easy access.
091 Element first = this;
092 Element last = this;
093
094 @Override
095 public void insertInternal(IntrusiveCollection<? extends Element> list,
096 Element prev, Element next) {
097 throw new RuntimeException("Can't insert root element");
098 }
099
100 @Override
101 public void setPrev(IntrusiveCollection<? extends Element> list,
102 Element prev) {
103 Preconditions.checkState(list == IntrusiveCollection.this);
104 last = prev;
105 }
106
107 @Override
108 public void setNext(IntrusiveCollection<? extends Element> list,
109 Element next) {
110 Preconditions.checkState(list == IntrusiveCollection.this);
111 first = next;
112 }
113
114 @Override
115 public void removeInternal(IntrusiveCollection<? extends Element> list) {
116 throw new RuntimeException("Can't remove root element");
117 }
118
119 @Override
120 public Element getNext(
121 IntrusiveCollection<? extends Element> list) {
122 Preconditions.checkState(list == IntrusiveCollection.this);
123 return first;
124 }
125
126 @Override
127 public Element getPrev(
128 IntrusiveCollection<? extends Element> list) {
129 Preconditions.checkState(list == IntrusiveCollection.this);
130 return last;
131 }
132
133 @Override
134 public boolean isInList(IntrusiveCollection<? extends Element> list) {
135 return list == IntrusiveCollection.this;
136 }
137
138 @Override
139 public String toString() {
140 return "root"; // + IntrusiveCollection.this + "]";
141 }
142 };
143
144 private int size = 0;
145
146 /**
147 * An iterator over the intrusive collection.
148 *
149 * Currently, you can remove elements from the list using
150 * #{IntrusiveIterator#remove()}, but modifying the collection in other
151 * ways during the iteration is not supported.
152 */
153 public class IntrusiveIterator implements Iterator<E> {
154 Element cur;
155 Element next;
156
157 IntrusiveIterator() {
158 this.cur = root;
159 this.next = null;
160 }
161
162 @Override
163 public boolean hasNext() {
164 if (next == null) {
165 next = cur.getNext(IntrusiveCollection.this);
166 }
167 return next != root;
168 }
169
170 @SuppressWarnings("unchecked")
171 @Override
172 public E next() {
173 if (next == null) {
174 next = cur.getNext(IntrusiveCollection.this);
175 }
176 if (next == root) {
177 throw new NoSuchElementException();
178 }
179 cur = next;
180 next = null;
181 return (E)cur;
182 }
183
184 @Override
185 public void remove() {
186 if (cur == null) {
187 throw new IllegalStateException("Already called remove " +
188 "once on this element.");
189 }
190 next = removeElement(cur);
191 cur = null;
192 }
193 }
194
195 private Element removeElement(Element elem) {
196 Element prev = elem.getPrev(IntrusiveCollection.this);
197 Element next = elem.getNext(IntrusiveCollection.this);
198 elem.removeInternal(IntrusiveCollection.this);
199 prev.setNext(IntrusiveCollection.this, next);
200 next.setPrev(IntrusiveCollection.this, prev);
201 size--;
202 return next;
203 }
204
205 /**
206 * Get an iterator over the list. This can be used to remove elements.
207 * It is not safe to do concurrent modifications from other threads while
208 * using this iterator.
209 *
210 * @return The iterator.
211 */
212 public Iterator<E> iterator() {
213 return new IntrusiveIterator();
214 }
215
216 @Override
217 public int size() {
218 return size;
219 }
220
221 @Override
222 public boolean isEmpty() {
223 return size == 0;
224 }
225
226 @Override
227 public boolean contains(Object o) {
228 try {
229 Element element = (Element)o;
230 return element.isInList(this);
231 } catch (ClassCastException e) {
232 return false;
233 }
234 }
235
236 @Override
237 public Object[] toArray() {
238 Object ret[] = new Object[size];
239 int i = 0;
240 for (Iterator<E> iter = iterator(); iter.hasNext(); ) {
241 ret[i++] = iter.next();
242 }
243 return ret;
244 }
245
246 @SuppressWarnings("unchecked")
247 @Override
248 public <T> T[] toArray(T[] array) {
249 if (array.length < size) {
250 return (T[])toArray();
251 } else {
252 int i = 0;
253 for (Iterator<E> iter = iterator(); iter.hasNext(); ) {
254 array[i++] = (T)iter.next();
255 }
256 }
257 return array;
258 }
259
260 /**
261 * Add an element to the end of the list.
262 *
263 * @param elem The new element to add.
264 */
265 @Override
266 public boolean add(E elem) {
267 if (elem == null) {
268 return false;
269 }
270 if (elem.isInList(this)) {
271 return false;
272 }
273 Element prev = root.getPrev(IntrusiveCollection.this);
274 prev.setNext(IntrusiveCollection.this, elem);
275 root.setPrev(IntrusiveCollection.this, elem);
276 elem.insertInternal(IntrusiveCollection.this, prev, root);
277 size++;
278 return true;
279 }
280
281 /**
282 * Add an element to the front of the list.
283 *
284 * @param elem The new element to add.
285 */
286 public boolean addFirst(Element elem) {
287 if (elem == null) {
288 return false;
289 }
290 if (elem.isInList(this)) {
291 return false;
292 }
293 Element next = root.getNext(IntrusiveCollection.this);
294 next.setPrev(IntrusiveCollection.this, elem);
295 root.setNext(IntrusiveCollection.this, elem);
296 elem.insertInternal(IntrusiveCollection.this, root, next);
297 size++;
298 return true;
299 }
300
301 public static final Log LOG = LogFactory.getLog(IntrusiveCollection.class);
302
303 @Override
304 public boolean remove(Object o) {
305 try {
306 Element elem = (Element)o;
307 if (!elem.isInList(this)) {
308 return false;
309 }
310 removeElement(elem);
311 return true;
312 } catch (ClassCastException e) {
313 return false;
314 }
315 }
316
317 @Override
318 public boolean containsAll(Collection<?> collection) {
319 for (Object o : collection) {
320 if (!contains(o)) {
321 return false;
322 }
323 }
324 return true;
325 }
326
327 @Override
328 public boolean addAll(Collection<? extends E> collection) {
329 boolean changed = false;
330 for (E elem : collection) {
331 if (add(elem)) {
332 changed = true;
333 }
334 }
335 return changed;
336 }
337
338 @Override
339 public boolean removeAll(Collection<?> collection) {
340 boolean changed = false;
341 for (Object elem : collection) {
342 if (remove(elem)) {
343 changed = true;
344 }
345 }
346 return changed;
347 }
348
349 @Override
350 public boolean retainAll(Collection<?> collection) {
351 boolean changed = false;
352 for (Iterator<E> iter = iterator();
353 iter.hasNext(); ) {
354 Element elem = iter.next();
355 if (!collection.contains(elem)) {
356 iter.remove();
357 changed = true;
358 }
359 }
360 return changed;
361 }
362
363 /**
364 * Remove all elements.
365 */
366 @Override
367 public void clear() {
368 for (Iterator<E> iter = iterator(); iter.hasNext(); ) {
369 iter.next();
370 iter.remove();
371 }
372 }
373 }