javafx.collections.transformation.FilteredList Maven / Gradle / Ivy
Show all versions of openjfx-78-backport Show documentation
/*
* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.collections.transformation;
import com.sun.javafx.collections.NonIterableChange.GenericAddRemoveChange;
import com.sun.javafx.collections.SortHelper;
import java.util.*;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.util.Callback;
/**
* Wraps an ObservableList and filters it's content using the provided Predicate.
* All changes in the ObservableList are propagated immediately
* to the FilteredList.
*
* @see TransformationList
* @since JavaFX 8.0
*/
public final class FilteredList extends TransformationList{
private int[] filtered;
private int size;
private SortHelper helper;
private static final Callback ALWAYS_TRUE = new Callback() {
@Override
public Object call(Object o) {
return true;
}
};
/**
* Constructs a new FilteredList wrapper around the source list.
* The provided predicate will match the elements in the source list that will be visible.
* @param source the source list
* @param predicate the predicate to match the elements. Cannot be null.
*/
public FilteredList(ObservableList source, Callback predicate) {
super(source);
if (predicate == null) {
throw new NullPointerException();
}
filtered = new int[source.size() * 3 / 2 + 1];
this.predicate.set(predicate);
}
/**
* Constructs a new FilteredList wrapper around the source list.
* This list has an "always true" predicate, containing all the elements
* of the source list.
*
* This constructor might be useful if you want to bind {@link #predicateProperty()}
* of this list.
* @param source the source list
*/
public FilteredList(ObservableList source) {
this(source, (Callback) ALWAYS_TRUE);
}
/**
* The predicate that will match the elements that will be in this FilteredList.
* Elements not matching the predicate will be filtered-out.
*/
private final ObjectProperty> predicate =
new ObjectPropertyBase>((Callback) ALWAYS_TRUE) {
@Override
protected void invalidated() {
if (get() == null) {
if (isBound()) {
unbind();
set((Callback) ALWAYS_TRUE);
throw new IllegalArgumentException("Predicate cannot be null.");
}
}
refilter();
}
@Override
public Object getBean() {
return FilteredList.this;
}
@Override
public String getName() {
return "predicate";
}
};
public final ObjectProperty> predicateProperty() {
return predicate;
}
public final Callback getPredicate() {
return predicate.get();
}
public final void setPredicate(Callback predicate) {
this.predicate.set(predicate);
}
@Override
protected void sourceChanged(Change c) {
beginChange();
while (c.next()) {
if (c.wasPermutated()) {
permutate(c);
} else if (c.wasUpdated()) {
update(c);
} else {
addRemove(c);
}
}
endChange();
}
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
@Override
public int size() {
return size;
}
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public E get(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException();
}
return getSource().get(filtered[index]);
}
@Override
public int getSourceIndex(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException();
}
return filtered[index];
}
private SortHelper getSortHelper() {
if (helper == null) {
helper = new SortHelper();
}
return helper;
}
private int findPosition(int p) {
if (filtered.length == 0) {
return 0;
}
if (p == 0) {
return 0;
}
int pos = Arrays.binarySearch(filtered, 0, size, p);
if (pos < 0 ) {
pos = ~pos;
}
return pos;
}
@SuppressWarnings("unchecked")
private void ensureSize(int size) {
if (filtered.length < size) {
int[] replacement = new int[size * 3/2 + 1];
System.arraycopy(filtered, 0, replacement, 0, this.size);
filtered = replacement;
}
}
private void updateIndexes(int from, int delta) {
for (int i = from; i < size; ++i) {
filtered[i] += delta;
}
}
private void permutate(Change c) {
int from = findPosition(c.getFrom());
int to = findPosition(c.getTo());
if (to > from) {
for (int i = from; i < to; ++i) {
filtered[i] = c.getPermutation(filtered[i]);
}
int[] perm = getSortHelper().sort(filtered, from, to);
nextPermutation(from, to, perm);
}
}
private void update(Change c) {
int from = findPosition(c.getFrom());
int to = findPosition(c.getTo());
// NOTE: this is sub-optimal, as we may mark some Nodes as "updated" even though
// they will be removed from the list in the next step
for (int i = from; i < to; ++i) {
nextUpdate(i);
}
updateFilter(c.getFrom(), c.getTo());
}
private void addRemove(Change c) {
Callback pred = predicate.get();
ensureSize(getSource().size());
final int from = findPosition(c.getFrom());
final int to = findPosition(c.getFrom() + c.getRemovedSize());
// Mark the nodes that are going to be removed
for (int i = from; i < to; ++i) {
nextRemove(from, c.getRemoved().get(filtered[i] - c.getFrom()));
}
// Update indexes of the sublist following the last element that was removed
updateIndexes(to, c.getAddedSize() - c.getRemovedSize());
// Replace as many removed elements as possible
int fpos = from;
int pos = c.getFrom();
ListIterator it = getSource().listIterator(pos);
for (; fpos < to && it.nextIndex() < c.getTo();) {
if (pred.call(it.next())) {
filtered[fpos] = it.previousIndex();
nextAdd(fpos, fpos + 1);
++fpos;
}
}
if (fpos < to) {
// If there were more removed elements than added
System.arraycopy(filtered, to, filtered, fpos, size - to);
size -= to - fpos;
} else {
// Add the remaining elements
while (it.nextIndex() < c.getTo()) {
if (pred.call(it.next())) {
System.arraycopy(filtered, fpos, filtered, fpos + 1, size - fpos);
filtered[fpos] = it.previousIndex();
nextAdd(fpos, fpos + 1);
++fpos;
++size;
}
++pos;
}
}
}
private void updateFilter(int sourceFrom, int sourceTo) {
Callback pred = predicate.get();
beginChange();
// Fast path for single element update
if (sourceFrom == sourceTo - 1) {
int pos = findPosition(sourceFrom);
final E sourceFromElement = getSource().get(sourceFrom);
if (filtered[pos] == sourceFrom) {
if (!pred.call(sourceFromElement)) {
nextRemove(pos, sourceFromElement);
System.arraycopy(filtered, pos + 1, filtered, pos, size - pos - 1);
--size;
}
} else {
ensureSize(getSource().size());
if (pred.call(sourceFromElement)) {
nextAdd(pos, pos + 1);
System.arraycopy(filtered, pos, filtered, pos + 1, size - pos);
filtered[pos] = sourceFrom;
++size;
}
}
} else {
ensureSize(getSource().size());
int filterFrom = findPosition(sourceFrom);
int filterTo = findPosition(sourceTo);
int i = filterFrom; // The index that traverses filtered[] array
if (i == 0) {
// Look at the beginning
final int jTo = size == 0 ? sourceTo : filtered[0];
final ListIterator it = getSource().listIterator(sourceFrom);
for (; it.nextIndex() < jTo;) {
E el = it.next();
if (pred.call(el)) {
nextAdd(i, i + 1);
System.arraycopy(filtered, i, filtered, i + 1, size - i);
filtered[i] = it.previousIndex();
size++;
filterTo++;
i++;
}
}
}
// Now traverse the rest of the list. We first check the item in the filtered
// array, if it still matches the filter
ListIterator it = getSource().listIterator(filtered[i]);
for (; i < filterTo; ++i) {
advanceTo(it, filtered[i]);
final E el = it.next();
if (!pred.call(el)) {
nextRemove(i, el);
System.arraycopy(filtered, i + 1, filtered, i, size - i - 1);
size--;
filterTo--;
i--;
}
final int jTo = (i == filterTo - 1 ? sourceTo : filtered[i + 1]);
// Then we look at the elements that are between the current element in filtered[] array
// and it's successor
while (it.nextIndex() < jTo) {
final E midEl = it.next();
if (pred.call(midEl)) {
nextAdd(i + 1, i + 2);
System.arraycopy(filtered, i + 1, filtered, i + 2, size - i - 1);
filtered[i + 1] = it.previousIndex();
size++;
filterTo++;
i++;
}
}
}
}
endChange();
}
private static void advanceTo(ListIterator it, int index) {
while(it.nextIndex() < index) {
it.next();
}
}
@SuppressWarnings("unchecked")
private void refilter() {
ensureSize(getSource().size());
List removed = null;
if (hasListeners()) {
removed = new ArrayList(this);
}
size = 0;
int i = 0;
Callback pred = predicate.get();
for (Iterator it = getSource().iterator();it.hasNext(); ) {
final E next = it.next();
if (pred.call(next)) {
filtered[size++] = i;
}
++i;
}
if (hasListeners()) {
fireChange(new GenericAddRemoveChange(0, size, removed, this));
}
}
/**
* Creates a {@link FilteredList} wrapper of this list using
* the specified predicate.
* @param predicate the predicate to use
* @return new {@code FilteredList}
*/
public FilteredList filtered(Callback predicate) {
return new FilteredList(this, predicate);
}
/**
* Creates a {@link SortedList} wrapper of this list using
* the specified comparator.
* @param comparator the comparator to use or null for the natural order
* @return new {@code SortedList}
*/
public SortedList sorted(Comparator comparator) {
return new SortedList(this, comparator);
}
/**
* Creates a {@link SortedList} wrapper of this list with the natural
* ordering.
* @return new {@code SortedList}
*/
public SortedList sorted() {
return sorted(null);
}
}