/** * Copyright (C) 2015 Valkyrie RCP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.valkyriercp.list; import org.springframework.util.Assert; import org.valkyriercp.rules.constraint.Constraint; import javax.swing.*; import javax.swing.event.ListDataEvent; import java.util.Observable; import java.util.Observer; /** * Decorates an existing {@link javax.swing.ListModel} by applying a constraint. The constraint can implement {@link java.util.Observable} to * notify a change of the filter condition. * * @author Keith Donald * @author Mathias Broekelmann */ public class DefaultFilteredListModel extends AbstractFilteredListModel implements Observer { private Constraint constraint; private int[] indexes; private int filteredSize; /** * Constructs a new instance * * @param listModel * the list model to filter. * @param constraint * the constraint which is applied to the list model elements * * @throws IllegalArgumentException * if list model or constraint parameters where null */ public DefaultFilteredListModel(ListModel listModel, Constraint constraint) { super(listModel); setConstraint(constraint); } protected void fireContentsChanged(Object source, int index0, int index1) { reallocateIndexes(); super.fireContentsChanged(source, index0, index1); } /** * Defines the constraint which is applied to the list model elements * * @param constraint * the constraint to set * * @throws IllegalArgumentException * if constraint is null */ public final void setConstraint(Constraint constraint) { Assert.notNull(constraint); if (!constraint.equals(this.constraint)) { if (this.constraint instanceof Observable) { ((Observable) constraint).deleteObserver(this); } this.constraint = constraint; if (constraint instanceof Observable) { ((Observable) constraint).addObserver(this); } fireContentsChanged(this, -1, -1); } } /** * @return the constraint */ public Constraint getConstraint() { return constraint; } /** * Internally called to reallocate the indexes. This method should be called when the filtered model changes its * element size */ protected void reallocateIndexes() { if (this.indexes == null || this.indexes.length != getFilteredModel().getSize()) { this.indexes = new int[getFilteredModel().getSize()]; } applyConstraint(); } /** * If the constraint implements {@link Observable} this method is called and will apply the constraint to the list * model elements */ public void update(Observable changed, Object arg) { fireContentsChanged(this, -1, -1); } private void applyConstraint() { filteredSize = 0; ListModel filteredListModel = getFilteredModel(); for (int i = 0, size = filteredListModel.getSize(); i < size; i++) { Object element = filteredListModel.getElementAt(i); if (constraint.test(element)) { indexes[filteredSize++] = i; onMatchingElement(element); } } postConstraintApplied(); } /** * Called to notify that an element has matched the filter constraint. This implementation does nothing. * * @param element * the element which was accepted by the filter */ protected void onMatchingElement(Object element) { } /** * Called to notify that the constraint was applied to all elements. This implementation does nothing. */ protected void postConstraintApplied() { } /** * Returns the size of the elements which passes the filter constraint. */ public int getSize() { return filteredSize; } /** * Returns the element index for a filtered index * * @param filteredIndex * the filtered index * @return the unfiltered index of the filtered model */ public int getElementIndex(int filteredIndex) { return indexes[filteredIndex]; } public void contentsChanged(ListDataEvent e) { reallocateIndexes(); super.contentsChanged(e); } public void intervalAdded(ListDataEvent e) { reallocateIndexes(); super.intervalAdded(e); } public void intervalRemoved(ListDataEvent e) { reallocateIndexes(); super.intervalRemoved(e); } }