| /* |
| * 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); |
| } |
| } |