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.hdfs.util;
019    
020    import java.util.ConcurrentModificationException;
021    import java.util.Iterator;
022    import java.util.ArrayList;
023    import java.util.List;
024    import java.util.NoSuchElementException;
025    
026    /**
027     * A low memory linked hash set implementation, which uses an array for storing
028     * the elements and linked lists for collision resolution. In addition it stores
029     * elements in a linked list to ensure ordered traversal. This class does not
030     * support null element.
031     *
032     * This class is not thread safe.
033     *
034     */
035    public class LightWeightLinkedSet<T> extends LightWeightHashSet<T> {
036      /**
037       * Elements of {@link LightWeightLinkedSet}.
038       */
039      static class DoubleLinkedElement<T> extends LinkedElement<T> {
040        // references to elements within all-element linked list
041        private DoubleLinkedElement<T> before;
042        private DoubleLinkedElement<T> after;
043    
044        public DoubleLinkedElement(T elem, int hashCode) {
045          super(elem, hashCode);
046          this.before = null;
047          this.after = null;
048        }
049    
050        @Override
051        public String toString() {
052          return super.toString();
053        }
054      }
055    
056      private DoubleLinkedElement<T> head;
057      private DoubleLinkedElement<T> tail;
058    
059      /**
060       * @param initCapacity
061       *          Recommended size of the internal array.
062       * @param maxLoadFactor
063       *          used to determine when to expand the internal array
064       * @param minLoadFactor
065       *          used to determine when to shrink the internal array
066       */
067      public LightWeightLinkedSet(int initCapacity, float maxLoadFactor,
068          float minLoadFactor) {
069        super(initCapacity, maxLoadFactor, minLoadFactor);
070        head = null;
071        tail = null;
072      }
073    
074      public LightWeightLinkedSet() {
075        this(MINIMUM_CAPACITY, DEFAULT_MAX_LOAD_FACTOR, DEFAUT_MIN_LOAD_FACTOR);
076      }
077    
078      /**
079       * Add given element to the hash table
080       *
081       * @return true if the element was not present in the table, false otherwise
082       */
083      @Override
084      protected boolean addElem(final T element) {
085        // validate element
086        if (element == null) {
087          throw new IllegalArgumentException("Null element is not supported.");
088        }
089        // find hashCode & index
090        final int hashCode = element.hashCode();
091        final int index = getIndex(hashCode);
092        // return false if already present
093        if (getContainedElem(index, element, hashCode) != null) {
094          return false;
095        }
096    
097        modification++;
098        size++;
099    
100        // update bucket linked list
101        DoubleLinkedElement<T> le = new DoubleLinkedElement<T>(element, hashCode);
102        le.next = entries[index];
103        entries[index] = le;
104    
105        // insert to the end of the all-element linked list
106        le.after = null;
107        le.before = tail;
108        if (tail != null) {
109          tail.after = le;
110        }
111        tail = le;
112        if (head == null) {
113          head = le;
114        }
115        return true;
116      }
117    
118      /**
119       * Remove the element corresponding to the key, given key.hashCode() == index.
120       *
121       * @return Return the entry with the element if exists. Otherwise return null.
122       */
123      @Override
124      protected DoubleLinkedElement<T> removeElem(final T key) {
125        DoubleLinkedElement<T> found = (DoubleLinkedElement<T>) (super
126            .removeElem(key));
127        if (found == null) {
128          return null;
129        }
130    
131        // update linked list
132        if (found.after != null) {
133          found.after.before = found.before;
134        }
135        if (found.before != null) {
136          found.before.after = found.after;
137        }
138        if (head == found) {
139          head = head.after;
140        }
141        if (tail == found) {
142          tail = tail.before;
143        }
144        return found;
145      }
146    
147      /**
148       * Remove and return first element on the linked list of all elements.
149       *
150       * @return first element
151       */
152      public T pollFirst() {
153        if (head == null) {
154          return null;
155        }
156        T first = head.element;
157        this.remove(first);
158        return first;
159      }
160    
161      /**
162       * Remove and return n elements from the hashtable.
163       * The order in which entries are removed is corresponds 
164       * to the order in which they were inserted.
165       *
166       * @return first element
167       */
168      @Override
169      public List<T> pollN(int n) {
170        if (n >= size) {
171          // if we need to remove all elements then do fast polling
172          return pollAll();
173        }
174        List<T> retList = new ArrayList<T>(n);
175        while (n-- > 0 && head != null) {
176          T curr = head.element;
177          this.removeElem(curr);
178          retList.add(curr);
179        }
180        shrinkIfNecessary();
181        return retList;
182      }
183    
184      /**
185       * Remove all elements from the set and return them in order. Traverse the
186       * link list, don't worry about hashtable - faster version of the parent
187       * method.
188       */
189      @Override
190      public List<T> pollAll() {
191        List<T> retList = new ArrayList<T>(size);
192        while (head != null) {
193          retList.add(head.element);
194          head = head.after;
195        }
196        this.clear();
197        return retList;
198      }
199    
200      @Override
201      @SuppressWarnings("unchecked")
202      public <U> U[] toArray(U[] a) {
203        if (a == null) {
204          throw new NullPointerException("Input array can not be null");
205        }
206        if (a.length < size) {
207          a = (U[]) java.lang.reflect.Array.newInstance(a.getClass()
208              .getComponentType(), size);
209        }
210        int currentIndex = 0;
211        DoubleLinkedElement<T> current = head;
212        while (current != null) {
213          T curr = current.element;
214          a[currentIndex++] = (U) curr;
215          current = current.after;
216        }
217        return a;
218      }
219    
220      @Override
221      public Iterator<T> iterator() {
222        return new LinkedSetIterator();
223      }
224    
225      private class LinkedSetIterator implements Iterator<T> {
226        /** The starting modification for fail-fast. */
227        private final int startModification = modification;
228        /** The next element to return. */
229        private DoubleLinkedElement<T> next = head;
230    
231        @Override
232        public boolean hasNext() {
233          return next != null;
234        }
235    
236        @Override
237        public T next() {
238          if (modification != startModification) {
239            throw new ConcurrentModificationException("modification="
240                + modification + " != startModification = " + startModification);
241          }
242          if (next == null) {
243            throw new NoSuchElementException();
244          }
245          final T e = next.element;
246          // find the next element
247          next = next.after;
248          return e;
249        }
250    
251        @Override
252        public void remove() {
253          throw new UnsupportedOperationException("Remove is not supported.");
254        }
255      }
256    
257      /**
258       * Clear the set. Resize it to the original capacity.
259       */
260      @Override
261      public void clear() {
262        super.clear();
263        this.head = null;
264        this.tail = null;
265      }
266    }