/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
import java.util.AbstractList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
/**
* {@link List} that filters another {@link List} instance by a type.
*
* <p>
* This filtered list provides a live view of another list. It is
* useful when you want to look for objects of a particular class
* inside a heterogeneous list.
*
* <p>
* The filtered list is "live" in the sense that any modification done
* to this list will be immediately propagated to the underlying list,
* and any modification done on the underlying list will be immediately
* visible through this list.
* In fact, this filter doesn't actually store objects by itself,
* but rather delegate all the work to the underlying list.
*
* <p>
* Note that the performance characteristic of this {@link List} is
* closer to that of {@link java.util.LinkedList}. It's not suited
* for random access by index (but iteration works nicely.)
*
* @author
* Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
*/
public final class FilterList extends AbstractList {
/**
* Instances of this class will be visible through this list.
*/
private final Class type;
/**
* The underlying {@link List} that this object works on.
*/
private final List core;
/**
* We use an iterator from the core to detect modification to it.
* The assumption here is that the iterator is fail-fast,
* and it tells us so when the underlying list is modified
* without our knowledge.
*
* <p>
* Note that the List interface doesn't mandate the fail-fast
* behavior, so there's a risk of this doesn't work correctly.
* But I think the risk is minimal in practice, given that
* all the JDK classes and {@link AbstractList} implements
* the semantics - KK.
*/
private ListIterator itr;
/**
* Cached value of the {@link #size()}.
*
* The above method could be called very frequently, and
* it involves the whole re-scanning of the list, so we
* cache the result and use it.
*/
private int size = -1;
/**
* Creates a new filtered list that filters objects in
* the spcified {@link List}.
*
* @param core
* {@link List} to be filtered.
* @param type
* Out of all the objects in the <tt>core</tt> list,
* only objects of this type will be visible through
* this list.
*/
public FilterList( List core, Class type ) {
this.core = core;
this.type = type;
itr = core.listIterator();
}
/**
* Returns true if the core is modified.
*/
private boolean isCoreModified() {
try {
itr.next();
itr.previous();
return false;
} catch( NoSuchElementException e ) {
; // this is cool.
return false;
} catch( ConcurrentModificationException e ) {
itr = core.listIterator();
return true;
}
}
/**
* Converts the index of this collection to that of the underlying list.
*
* @param allowEnd
* If true, the index maps idx==this.size() to core.size().
* Otherwise it results in {@link IndexOutOfBoundsException}.
*/
private int toCoreIndex(int idx,boolean allowEnd) {
int i=0;
final int index = idx; // keep the original value
for (Iterator itr = core.iterator(); itr.hasNext();i++) {
if(type.isInstance(itr.next())) {
if(idx==0) return i;
idx--;
}
}
if(allowEnd && idx==0)
return i;
else
throw new IndexOutOfBoundsException(Integer.toString(index));
}
public void add(int index, Object element) {
core.add(toCoreIndex(index,true),element);
}
public Iterator iterator() {
return listIterator();
}
public ListIterator listIterator(final int index) {
return new ListIterator() {
/**
* Index of the current item in this list (not the core list).
* This will be the one returned from the next {@link #next()}.
*
* When the iterator hits the end of the list, this equals to
* the size of this filtered list.
*
* Note that just because the iterator is positioned at the
* beginning of the list doesn't mean its thisIndex==0.
*/
private int thisIndex = index;
/**
* Index of the current item in the core list (not this list).
*
* When the iterator hits the end of the list, this equals to
* the size of the core list.
*/
private int coreIndex = toCoreIndex(index,true);
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
private int lastRet = -1;
public int nextIndex() {
return thisIndex;
}
public int previousIndex() {
return thisIndex-1;
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
try {
core.remove(lastRet);
if (lastRet<coreIndex) {
coreIndex--;
thisIndex--;
}
lastRet = -1;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
public boolean hasNext() {
return coreIndex<core.size();
}
public Object next() {
int coreSize = core.size();
if(coreIndex>=coreSize)
throw new NoSuchElementException();
lastRet = coreIndex;
// move forward
thisIndex++;
do {
coreIndex++;
} while( !match(core.get(coreIndex)) && coreIndex<coreSize );
return core.get(lastRet);
}
public boolean hasPrevious() {
// TODO: this could be made bit more efficient
// look for the previous item
int idx = coreIndex-1;
while(idx>=0 && !match(core.get(idx)))
idx--;
return idx>=0;
}
public Object previous() {
// TODO: this could be made bit more efficient
// look for the previous item
int idx = coreIndex-1;
while(idx>=0 && !match(core.get(idx)))
idx--;
if(idx<0)
throw new NoSuchElementException();
lastRet=idx;
thisIndex--;
coreIndex=idx;
return core.get(lastRet);
}
public void add(Object o) {
try {
core.add(coreIndex,o);
lastRet = -1;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
public void set(Object o) {
if (lastRet == -1)
throw new IllegalStateException();
try {
core.set(lastRet,o);
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
};
}
public Object set(int index, Object element) {
return core.set(toCoreIndex(index,false),element);
}
/**
* Remove the object at the specified offset.
*
* @param index The offset of the object.
* @return The Object which was removed.
*/
public Object remove(int index) {
return core.remove(toCoreIndex(index,false));
}
public Object get(int index) {
return core.get(toCoreIndex(index,false));
}
public int size() {
if(isCoreModified() || size==-1) {
size=0;
for (Iterator itr = core.iterator(); itr.hasNext();) {
if(match(itr.next()))
size++;
}
}
return size;
}
/**
* Returns true if the object matches the filtering criteria.
*/
private boolean match(Object o) {
return type.isInstance(o);
}
}