blob: 7ef301e54282f275f3e8c25f0414bd7b1ccc7d6d [file] [log] [blame]
/*
* Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
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);
}
}