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 */
018package org.apache.hadoop.util;
019
020import java.util.Collection;
021import java.util.Iterator;
022import java.util.NoSuchElementException;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.apache.hadoop.classification.InterfaceAudience;
027
028import 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
039public 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}