/******************************************************************************* * Copyright (c) 2006, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.debug.internal.ui.viewers.model; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.eclipse.jface.viewers.TreePath; /** * Helper class to support filtering in virtual tree viewer. * Translates indexes from viewer to model coordinate space (and visa versa). * <p> * This filter transform maintains a tree representing filtered elements in the * viewer. The filtering is performed dynamically as elements are 'replaced' in the tree * by a lazy tree content provider. * </p> * <p> * This class not intended to be subclassed or instantiated. For internal use only. * </p> * @since 3.3 */ public class FilterTransform { private Node root = new Node(); class Node { private int[] filteredIndexes = null; private Object[] filteredElements = null; private Map<Object, Node> children = null; // only set for parent nodes, // indexed by child Node() { } boolean addFilter(TreePath path, int childIndex, int pathIndex, Object filtered) { if (pathIndex == path.getSegmentCount()) { if (filteredIndexes == null) { filteredIndexes = new int[]{childIndex}; filteredElements = new Object[]{filtered}; return true; } int location = Arrays.binarySearch(filteredIndexes, childIndex); if (location >= 0) { return false; } location = 0 - (location + 1); int[] next = new int[filteredIndexes.length + 1]; Object[] filt = new Object[next.length]; if (location == 0) { next[0] = childIndex; filt[0] = filtered; System.arraycopy(filteredIndexes, 0, next, 1, filteredIndexes.length); System.arraycopy(filteredElements, 0, filt, 1, filteredElements.length); } else if (location == filteredIndexes.length) { next[filteredIndexes.length] = childIndex; filt[filteredElements.length] = filtered; System.arraycopy(filteredIndexes, 0, next, 0, filteredIndexes.length); System.arraycopy(filteredElements, 0, filt, 0, filteredElements.length); } else { System.arraycopy(filteredIndexes, 0, next, 0, location); System.arraycopy(filteredElements, 0, filt, 0, location); next[location] = childIndex; filt[location] = filtered; System.arraycopy(filteredIndexes, location, next, location + 1, filteredIndexes.length - location); System.arraycopy(filteredElements, location, filt, location + 1, filteredElements.length - location); } filteredIndexes = next; filteredElements = filt; return true; } if (children == null) { children = new HashMap<Object, Node>(); } Object element = path.getSegment(pathIndex); Node node = children.get(element); if (node == null) { node = new Node(); children.put(element, node); } return node.addFilter(path, childIndex, pathIndex + 1, filtered); } boolean clear(TreePath path, int pathIndex) { if (pathIndex == path.getSegmentCount()) { return true; } if (children == null) { return false; } Object child = path.getSegment(pathIndex); Node node = children.get(child); if (node != null) { if (node.clear(path, pathIndex + 1)) { children.remove(child); } } return children.isEmpty() && (filteredIndexes == null || filteredIndexes.length == 0); } boolean clear(TreePath path, int childIndex, int pathIndex) { if (pathIndex == path.getSegmentCount()) { if (filteredIndexes != null) { int location = Arrays.binarySearch(filteredIndexes, childIndex); if (location >= 0) { // remove it if (location == 0 && filteredIndexes.length == 1) { filteredIndexes = null; filteredElements = null; return true; } int[] next = new int[filteredIndexes.length - 1]; Object[] filt = new Object[next.length]; if (location == 0) { System.arraycopy(filteredIndexes, 1, next, 0, next.length); System.arraycopy(filteredElements, 1, filt, 0, filt.length); } else if (location == (filteredIndexes.length - 1)) { System.arraycopy(filteredIndexes, 0, next, 0, location); System.arraycopy(filteredElements, 0, filt, 0, location); } else { System.arraycopy(filteredIndexes, 0, next, 0, location); System.arraycopy(filteredElements, 0, filt, 0, location); System.arraycopy(filteredIndexes, location + 1, next, location, next.length - location); System.arraycopy(filteredElements, location + 1, filt, location, filt.length - location); } filteredIndexes = next; filteredElements = filt; return false; } } else { return false; } } if (children == null) { return false; } Object element = path.getSegment(pathIndex); Node node = children.get(element); if (node == null) { return false; } boolean remove = node.clear(path, childIndex, pathIndex + 1); if (remove) { children.remove(element); return filteredIndexes == null && children.isEmpty(); } else { return false; } } Node find(TreePath path, int pathIndex) { if (pathIndex == path.getSegmentCount()) { return this; } if (children == null) { return null; } Object child = path.getSegment(pathIndex); Node node = children.get(child); if (node != null) { return node.find(path, pathIndex + 1); } return null; } int viewToModel(int childIndex) { if (filteredIndexes == null) { return childIndex; } // If there are filtered children, then we want to find the // (n+1)th missing number in the list of filtered indexes (missing // entries are visible in the view). For example, if the request // has asked for the model index corresponding to the 4th viewer // index, then we want to find the 5th missing number in the // filtered index sequence. int count = -1; // count from 0, 1, 2... int missingNumbers = 0; // how many numbers missing from the filtered index int offset = 0; // offset into the filtered index while (missingNumbers < (childIndex + 1)) { count++; if (offset < filteredIndexes.length) { if (filteredIndexes[offset] == count) { // not missing offset++; } else { // missing missingNumbers++; } } else { missingNumbers++; } } return count; } int modelToView(int childIndex) { if (filteredIndexes == null) { return childIndex; } int offset = 0; for (int i = 0; i < filteredIndexes.length; i++) { if (childIndex == filteredIndexes[i] ) { return -1; } else if (childIndex > filteredIndexes[i]) { offset++; } else { break; } } return childIndex - offset; } int modelToViewCount(int childCount) { if (filteredIndexes == null) { return childCount; } return childCount - filteredIndexes.length; } boolean isFiltered(int index) { if (filteredIndexes != null) { int location = Arrays.binarySearch(filteredIndexes, index); return location >= 0; } return false; } int indexOfFilteredElement(Object element) { if (filteredElements != null) { for (int i = 0; i < filteredElements.length; i++) { if (element.equals(filteredElements[i])) { return filteredIndexes[i]; } } } return -1; } /** * Sets the child count for this element, trimming any filtered elements * that were above this count. * * @param childCount new child count */ void setModelChildCount(int childCount) { if (filteredIndexes != null) { for (int i = 0; i < filteredIndexes.length; i++) { if (filteredIndexes[i] >= childCount) { // trim if (i == 0) { filteredIndexes = null; // bug 200325 - filteredElements should have the same length // as filteredIndexes filteredElements = null; return; } else { int[] temp = new int[i + 1]; System.arraycopy(filteredIndexes, 0, temp, 0, temp.length); filteredIndexes = temp; // bug 200325 - filteredElements should have the same length // as filteredIndexes Object[] temp2 = new Object[i + 1]; System.arraycopy(filteredElements, 0, temp2, 0, temp2.length); filteredElements = temp2; } } } } } /** * Updates filter index for a removed element at the given index * * @param index index at which an element was removed */ void removeElementFromFilters(int index) { if (filteredIndexes != null) { int location = Arrays.binarySearch(filteredIndexes, index); if (location >= 0) { // remove a filtered item if (filteredIndexes.length == 1) { // only filtered item filteredIndexes = null; filteredElements = null; } else { int[] next = new int[filteredIndexes.length - 1]; Object[] filt = new Object[next.length]; if (location == 0) { // first System.arraycopy(filteredIndexes, 1, next, 0, next.length); System.arraycopy(filteredElements, 1, filt, 0, filt.length); } else if (location == (filteredIndexes.length - 1)) { // last System.arraycopy(filteredIndexes, 0, next, 0, next.length); System.arraycopy(filteredElements, 0, filt, 0, filt.length); } else { // middle System.arraycopy(filteredIndexes, 0, next, 0, location); System.arraycopy(filteredElements, 0, filt, 0, location); System.arraycopy(filteredIndexes, location + 1, next, location, next.length - location); System.arraycopy(filteredElements, location + 1, filt, location, filt.length - location); } filteredIndexes = next; filteredElements = filt; } } else { location = 0 - (location + 1); } if (filteredIndexes != null) { // decrement remaining indexes for (int i = location; i < filteredIndexes.length; i ++) { filteredIndexes[i]--; } } } } } /** * Filters the specified child of the given parent and returns * whether the filter was added. * * @param parentPath path to parent element * @param childIndex index of filtered child relative to parent (in model coordinates) * @param element the filtered element * @return whether the filter was added - returns <code>true</code> if the filter is * added, and <code>false</code> if the index was already filtered */ public boolean addFilteredIndex(TreePath parentPath, int childIndex, Object element) { return root.addFilter(parentPath, childIndex, 0, element); } /** * Clears all filtered elements. */ public void clear() { root = new Node(); } /** * Clears all filters in the subtree of the given element. * * @param path element path */ public void clear(TreePath path) { root.clear(path, 0); } /** * Clears the given filtered index of the specified parent. I.e. * the child still exists, but is no longer filtered. * * @param parentPath parent path * @param index index to clear */ public void clear(TreePath parentPath, int index) { root.clear(parentPath, index, 0); } public int indexOfFilteredElement(TreePath parentPath, Object element) { Node parentNode = root.find(parentPath, 0); if (parentNode == null) { return -1; } return parentNode.indexOfFilteredElement(element); } /** * Translates and returns the given model index (raw index) into * a view index (filtered index), or -1 if filtered. * * @param parentPath path to parent element * @param childIndex index of child element in model space * @return the given index in view coordinates, or -1 if filtered. */ public int modelToViewIndex(TreePath parentPath, int childIndex) { Node parentNode = root.find(parentPath, 0); if (parentNode == null) { return childIndex; } return parentNode.modelToView(childIndex); } /** * Translates and returns the given view index (filtered) into * a model index (raw index). * * @param parentPath path to parent element * @param childIndex index of child element in view space * @return the given index in model coordinates */ public int viewToModelIndex(TreePath parentPath, int childIndex) { Node parentNode = root.find(parentPath, 0); if (parentNode == null) { return childIndex; } return parentNode.viewToModel(childIndex); } /** * Returns the number of children for the given parent, in the model. * * @param parentPath path to parent element * @param viewCount number of children in the view * @return number of children in the model */ public int viewToModelCount(TreePath parentPath, int viewCount) { Node parentNode = root.find(parentPath, 0); if (parentNode != null) { if (parentNode.filteredIndexes != null) { return viewCount + parentNode.filteredIndexes.length; } } return viewCount; } /** * Translates and returns the given model child count (raw) into * a view count (filtered). * * @param parentPath path to parent element * @param count child count in model space * @return the given count in view coordinates */ public int modelToViewCount(TreePath parentPath, int count) { Node parentNode = root.find(parentPath, 0); if (parentNode == null) { return count; } return parentNode.modelToViewCount(count); } /** * Returns whether the given index of the specified parent is currently filtered. * * @param parentPath path to parent element * @param index index of child element * @return whether the child is currently filtered */ public boolean isFiltered(TreePath parentPath, int index) { Node parentNode = root.find(parentPath, 0); if (parentNode == null) { return false; } return parentNode.isFiltered(index); } /** * Returns filtered children of the given parent, or <code>null</code> if none. * * @param parentPath Path of parent element * @return filtered children or <code>null</code> */ public int[] getFilteredChildren(TreePath parentPath) { Node parentNode = root.find(parentPath, 0); if (parentNode == null) { return null; } return parentNode.filteredIndexes; } /** * Clears any filters for the given parent above the given count. * * @param parentPath path to parent element * @param childCount child count */ public void setModelChildCount(TreePath parentPath, int childCount) { Node parentNode = root.find(parentPath, 0); if (parentNode != null) { parentNode.setModelChildCount(childCount); } } /** * The element at the given index has been removed from the parent. Update * indexes. * * @param parentPath path to parent element * @param index index of child element in model coordinates */ public void removeElementFromFilters(TreePath parentPath, int index) { Node parentNode = root.find(parentPath, 0); if (parentNode != null) { parentNode.removeElementFromFilters(index); } } /** * The element has been removed from the parent. Update * filtered indexes, in case it was a filtered object. * * @param parentPath path to parent element * @param element removed element * @return true if element was removed */ public boolean removeElementFromFilters(TreePath parentPath, Object element) { Node parentNode = root.find(parentPath, 0); if (parentNode != null) { int index = parentNode.indexOfFilteredElement(element); if (index >= 0) { parentNode.removeElementFromFilters(index); return true; } } return false; } }