javafx.collections.transformation.FilteredList Maven / Gradle / Ivy
/*
* Copyright (c) 2011, 2017, 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.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Predicate;
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
/**
* Wraps an ObservableList and filters its 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 Predicate ALWAYS_TRUE = t -> 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.
* If the predicate is null, all elements will be matched and the list is equal to the source list.
* @param source the source list
* @param predicate the predicate to match the elements or null to match all elements.
*/
public FilteredList(@NamedArg("source") ObservableList source, @NamedArg("predicate") Predicate predicate) {
super(source);
filtered = new int[source.size() * 3 / 2 + 1];
if (predicate != null) {
setPredicate(predicate);
} else {
for (size = 0; size < source.size(); size++) {
filtered[size] = size;
}
}
}
/**
* 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(@NamedArg("source") ObservableList source) {
this(source, null);
}
/**
* The predicate that will match the elements that will be in this FilteredList.
* Elements not matching the predicate will be filtered-out.
* Null predicate means "always true" predicate, all elements will be matched.
*/
private ObjectProperty> predicate;
public final ObjectProperty> predicateProperty() {
if (predicate == null) {
predicate = new ObjectPropertyBase>() {
@Override
protected void invalidated() {
refilter();
}
@Override
public Object getBean() {
return FilteredList.this;
}
@Override
public String getName() {
return "predicate";
}
};
}
return predicate;
}
public final Predicate getPredicate() {
return predicate == null ? null : predicate.get();
}
public final void setPredicate(Predicate predicate) {
predicateProperty().set(predicate);
}
private Predicate getPredicateImpl() {
if (getPredicate() != null) {
return getPredicate();
}
return ALWAYS_TRUE;
}
@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];
}
@Override
public int getViewIndex(int index) {
return Arrays.binarySearch(filtered, 0, size, 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 addRemove(Change c) {
Predicate pred = getPredicateImpl();
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.test(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.test(it.next())) {
System.arraycopy(filtered, fpos, filtered, fpos + 1, size - fpos);
filtered[fpos] = it.previousIndex();
nextAdd(fpos, fpos + 1);
++fpos;
++size;
}
++pos;
}
}
}
private void update(Change c) {
Predicate pred = getPredicateImpl();
ensureSize(getSource().size());
int sourceFrom = c.getFrom();
int sourceTo = c.getTo();
int filterFrom = findPosition(sourceFrom);
int filterTo = findPosition(sourceTo);
ListIterator it = getSource().listIterator(sourceFrom);
int pos = filterFrom;
while (pos < filterTo || sourceFrom < sourceTo) {
E el = it.next();
if (pos < size && filtered[pos] == sourceFrom) {
if (!pred.test(el)) {
nextRemove(pos, el);
System.arraycopy(filtered, pos + 1, filtered, pos, size - pos - 1);
--size;
--filterTo;
} else {
nextUpdate(pos);
++pos;
}
} else {
if (pred.test(el)) {
nextAdd(pos, pos + 1);
System.arraycopy(filtered, pos, filtered, pos + 1, size - pos);
filtered[pos] = sourceFrom;
++size;
++pos;
++filterTo;
}
}
sourceFrom++;
}
}
@SuppressWarnings("unchecked")
private void refilter() {
ensureSize(getSource().size());
List removed = null;
if (hasListeners()) {
removed = new ArrayList<>(this);
}
size = 0;
int i = 0;
Predicate pred = getPredicateImpl();
for (Iterator it = getSource().iterator();it.hasNext(); ) {
final E next = it.next();
if (pred.test(next)) {
filtered[size++] = i;
}
++i;
}
if (hasListeners()) {
fireChange(new GenericAddRemoveChange<>(0, size, removed, this));
}
}
}