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    }