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.hdfs.util;
019
020import java.util.ConcurrentModificationException;
021import java.util.Iterator;
022import java.util.ArrayList;
023import java.util.List;
024import 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 */
035public 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}