Added TreeModelViewerFilter to make filtering more efficient.
diff --git a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/FilterTests.java b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/FilterTests.java
index f7401f9..46eb8e7 100644
--- a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/FilterTests.java
+++ b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/FilterTests.java
@@ -21,7 +21,10 @@
 import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
 import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
 import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewerFilter;
+import org.eclipse.jface.viewers.ITreeSelection;
 import org.eclipse.jface.viewers.TreePath;
+import org.eclipse.jface.viewers.TreeSelection;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerFilter;
 import org.eclipse.swt.layout.FillLayout;
@@ -84,6 +87,11 @@
         }
     }
     
+    protected IInternalTreeModelViewer getInternalViewer() {
+        return (IInternalTreeModelViewer)fViewer;
+    }
+    
+
     class TestViewerFilter extends ViewerFilter {
     	
     	Pattern fPattern;
@@ -101,21 +109,70 @@
     		return true;
     	}
     }
+
+    class TestTMVFilter extends TreeModelViewerFilter {
+        Pattern fPattern;
+        Object fParentElement;
+        TestTMVFilter(String pattern, Object parentElement) {
+            fPattern = Pattern.compile(pattern);
+            fParentElement = parentElement;
+        }
+        
+        public boolean isApplicable(ITreeModelViewer viewer, Object parentElement) {
+            if (fParentElement != null) {
+                return fParentElement.equals(parentElement);
+            }
+
+            return true;
+        }
+        
+         public boolean select(Viewer viewer, Object parentElement, Object element) {
+             if (element instanceof TestElement) {
+                 TestElement te = (TestElement)element;
+                 return !fPattern.matcher(te.getLabel()).find();
+             }
+            
+            return true;
+        }
+    }
     
     public void testSimpleSingleLevel() throws InterruptedException {
-        // Create the model with test data
         TestModel model = TestModel.simpleSingleLevel();
+        doTestSimpleLevel(model, new ViewerFilter[] { new TestViewerFilter("2") });        
+    }
+    
+    public void testSimpleSingleLevelWithTMVFilter() throws InterruptedException {
+        TestModel model = TestModel.simpleSingleLevel();
+        doTestSimpleLevel(model, new ViewerFilter[] { new TestTMVFilter("2", model.getRootElement()) });        
+    }
+    
+    public void testSimpleSingleLevelWithMixedFilters() throws InterruptedException {
+        TestModel model = TestModel.simpleSingleLevel();
+        doTestSimpleLevel(model, new ViewerFilter[] { new TestTMVFilter("2", model.getRootElement()), new TestViewerFilter("1") });        
+    }    
 
+    public void testSimpleMultiLevel() throws InterruptedException {
+        TestModel model = TestModel.simpleMultiLevel();
+        doTestSimpleLevel(model, new ViewerFilter[] { new TestViewerFilter(".1"), new TestViewerFilter(".2") });        
+    }
+    
+    public void testSimpleMultiLevelWithTMVFilter() throws InterruptedException {
+        TestModel model = TestModel.simpleMultiLevel();
+        doTestSimpleLevel(model, new ViewerFilter[] { new TestTMVFilter(".1", null), new TestTMVFilter(".2", null) });        
+    }
+
+    public void testSimpleMultiLevelWithMixedFilters() throws InterruptedException {
+        TestModel model = TestModel.simpleMultiLevel();
+        doTestSimpleLevel(model, new ViewerFilter[] { new TestViewerFilter(".1"), new TestTMVFilter(".2", null) });        
+    }
+
+    private void doTestSimpleLevel(TestModel model, ViewerFilter[] filters) throws InterruptedException {
+        
         // Make sure that all elements are expanded
         fViewer.setAutoExpandLevel(-1);
         
-        // Create filter for element 2
-        ViewerFilter[] filters = new ViewerFilter[] { new TestViewerFilter("2") };
         fViewer.setFilters(filters);
         
-        // Create the agent which forces the tree to populate
-        //TreeModelViewerAutopopulateAgent autopopulateAgent = new TreeModelViewerAutopopulateAgent(fViewer);
-        
         // Create the listener which determines when the view is finished updating.
         // fListener.reset(TreePath.EMPTY, model.getRootElement(), filters, -1, false, false);
         fListener.reset(TreePath.EMPTY, model.getRootElement(), filters, -1, true, true);
@@ -128,32 +185,21 @@
         
         model.validateData(fViewer, TreePath.EMPTY, false, filters);
     }
-
-    public void testSimpleMultiLevel() throws InterruptedException {
-        //TreeModelViewerAutopopulateAgent autopopulateAgent = new TreeModelViewerAutopopulateAgent(fViewer);
-        
-        TestModel model = TestModel.simpleMultiLevel();
-        fViewer.setAutoExpandLevel(-1);
-        
-        // Create filter for elements ending with numbers 0-4.
-        ViewerFilter[] filters = new ViewerFilter[] { new TestViewerFilter(".2") };
-        fViewer.setFilters(filters);
-        
-        //fListener.reset(TreePath.EMPTY, model.getRootElement(), filters, -1, false, false);
-        fListener.reset(TreePath.EMPTY, model.getRootElement(), filters, -1, true, true);
-        fViewer.setInput(model.getRootElement());
-        while (!fListener.isFinished()) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
-
-        model.validateData(fViewer, TreePath.EMPTY, false, filters);
+    
+    public void testLargeSingleLevel() throws InterruptedException {
+        doTestLargeSingleLevel(new ViewerFilter[] { new TestViewerFilter("2") });
     }
 
-    public void testLargeSingleLevel() throws InterruptedException {
+    public void testLargeSingleLevelWithTMVFilter() throws InterruptedException {
+        doTestLargeSingleLevel(new ViewerFilter[] { new TestTMVFilter("2", null) });
+    }
+    
+    private void doTestLargeSingleLevel(ViewerFilter[] filters) throws InterruptedException {
         TestModel model = new TestModel();
         model.setRoot( new TestElement(model, "root", new TestElement[0] ) ); 
-        model.setElementChildren(TreePath.EMPTY, TestModel.makeSingleLevelModelElements(model, 300, "model."));
+        model.setElementChildren(TreePath.EMPTY, TestModel.makeSingleLevelModelElements(model, 3000, "model."));
 
-        // Create filter for element 2
-        ViewerFilter[] filters = new ViewerFilter[] { new TestViewerFilter("2") };
+        // Set filters
         fViewer.setFilters(filters);
 
         fListener.setFailOnRedundantUpdates(false);
@@ -165,18 +211,31 @@
         while (!fListener.isFinished(ALL_UPDATES_COMPLETE)) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
     }
     
+    
     /**
      * Replace an element that is not visible but filtered out.  With an element that is NOT filtered out.
      * Fire REPLACE delta.
      */
     public void testReplacedUnrealizedFilteredElement() throws InterruptedException {
+        doTestReplacedUnrealizedFilteredElement(new ViewerFilter[] { new TestViewerFilter("2") });
+    }
+    
+
+    /**
+     * Replace an element that is not visible but filtered out.  With an element that is NOT filtered out.
+     * Fire REPLACE delta.
+     */
+    public void testReplacedUnrealizedFilteredElementWithTMVFilter() throws InterruptedException {
+        doTestReplacedUnrealizedFilteredElement(new ViewerFilter[] { new TestTMVFilter("2", null) });
+    }
+    
+    private void doTestReplacedUnrealizedFilteredElement(ViewerFilter[] filters) throws InterruptedException {
+        
         // Populate a view with a large model (only first 100 elements will be visible in virtual viewer).
         TestModel model = new TestModel();
         model.setRoot( new TestElement(model, "root", new TestElement[0] ) ); 
         model.setElementChildren(TreePath.EMPTY, TestModel.makeSingleLevelModelElements(model, 300, "model."));
 
-        // Create filter for element 2
-        ViewerFilter[] filters = new ViewerFilter[] { new TestViewerFilter("2") };
         fViewer.setFilters(filters);
 
         fListener.setFailOnRedundantUpdates(false);
@@ -197,7 +256,7 @@
 
         // Reposition the viewer to make element 100 the top element, making the replaced element visible.
         fListener.reset();        
-        ((IInternalTreeModelViewer) fViewer).reveal(TreePath.EMPTY, 100);
+        ((IInternalTreeModelViewer) fViewer).reveal(TreePath.EMPTY, 150);
         while (!fListener.isFinished(ALL_UPDATES_COMPLETE)) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
 
         // Verify that the replaced element is in viewer now (i.e. it's not filtered out.
@@ -205,19 +264,25 @@
         Assert.assertTrue(replacedElementPaths.length != 0);
     }
 
-    
+
+    public void testRefreshUnrealizedFilteredElement() throws InterruptedException {
+        doTestRefreshUnrealizedFilteredElement(new ViewerFilter[] { new TestViewerFilter("2") });
+    }
+
+    public void testRefreshUnrealizedFilteredElementWithTMVFilter() throws InterruptedException {
+        doTestRefreshUnrealizedFilteredElement(new ViewerFilter[] { new TestTMVFilter("2", null) });
+    }
+
     /**
      * Replace an element that is not visible but filtered out.  With an element that is NOT filtered out.
      * Fire CONTENT delta on parent.
      */
-    public void testRefreshUnrealizedFilteredElement() throws InterruptedException {
+    private void doTestRefreshUnrealizedFilteredElement(ViewerFilter[] filters) throws InterruptedException {
         // Populate a view with a large model (only first 100 elements will be visible in virtual viewer).
         TestModel model = new TestModel();
         model.setRoot( new TestElement(model, "root", new TestElement[0] ) ); 
         model.setElementChildren(TreePath.EMPTY, TestModel.makeSingleLevelModelElements(model, 300, "model."));
 
-        // Create filter for element 2
-        ViewerFilter[] filters = new ViewerFilter[] { new TestViewerFilter("2") };
         fViewer.setFilters(filters);
 
         fListener.setFailOnRedundantUpdates(false);
@@ -238,11 +303,130 @@
 
         // Reposition the viewer to make element 100 the top element, making the replaced element visible.
         fListener.reset();        
-        ((IInternalTreeModelViewer) fViewer).reveal(TreePath.EMPTY, 100);
+        ((IInternalTreeModelViewer) fViewer).reveal(TreePath.EMPTY, 150);
         while (!fListener.isFinished(ALL_UPDATES_COMPLETE)) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
 
         // Verify that the replaced element is in viewer now (i.e. it's not filtered out.
         TreePath[] replacedElementPaths = fViewer.getElementPaths(replacedElement);
         Assert.assertTrue(replacedElementPaths.length != 0);
     }    
+
+    public void testRefreshToUnfilterElements() throws InterruptedException {
+        doTestRefreshToUnfilterElements(new ViewerFilter[] { new TestViewerFilter(".1"), new TestViewerFilter(".2") });        
+    }
+    
+    public void testRefreshToUnfilterElementsWithTMVFilter() throws InterruptedException {
+        doTestRefreshToUnfilterElements(new ViewerFilter[] { new TestTMVFilter(".1", null), new TestTMVFilter(".2", null) });        
+    }
+
+    public void testRefreshToUnfilterElementsWithMixedFilters() throws InterruptedException {
+        doTestRefreshToUnfilterElements(new ViewerFilter[] { new TestViewerFilter(".1"), new TestTMVFilter(".2", null) });        
+    }
+    
+    /**
+     * Replace an element that is not visible but filtered out.  With an element that is NOT filtered out.
+     * Fire CONTENT delta on parent.
+     */
+    private void doTestRefreshToUnfilterElements(ViewerFilter[] filters) throws InterruptedException {
+        // Populate a view with a large model (only first 100 elements will be visible in virtual viewer).
+        TestModel model = TestModel.simpleMultiLevel();
+
+        fViewer.setFilters(filters);
+
+        fListener.setFailOnRedundantUpdates(false);
+        fListener.reset();
+
+        // Make sure that all elements are expanded
+        fViewer.setAutoExpandLevel(-1);
+        
+        // Populate the view (all elements containing a "2" will be filtered out.
+        fViewer.setInput(model.getRootElement());
+        
+        while (!fListener.isFinished(ALL_UPDATES_COMPLETE)) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
+
+        // Turn off filters and refresh.
+        filters = new ViewerFilter[0];
+        fViewer.setFilters(filters);
+        fListener.reset();
+        model.postDelta(new ModelDelta(model.getRootElement(), IModelDelta.CONTENT));
+        while (!fListener.isFinished(ALL_UPDATES_COMPLETE)) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
+
+        model.validateData(fViewer, TreePath.EMPTY, false, filters);
+    }    
+
+    public void testPreserveExpandedOnMultLevelContent() throws InterruptedException {
+        //TreeModelViewerAutopopulateAgent autopopulateAgent = new TreeModelViewerAutopopulateAgent(fViewer);
+        TestModel model = StateTests.alternatingSubsreesModel(6);
+
+        // NOTE: WE ARE NOT EXPANDING ANY CHILDREN
+        
+        // Create the listener, only check the first level
+        fListener.reset(TreePath.EMPTY, model.getRootElement(), 1, true, false); 
+
+        // Set the input into the view and update the view.
+        fViewer.setInput(model.getRootElement());
+        while (!fListener.isFinished()) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
+        model.validateData(fViewer, TreePath.EMPTY, true);
+
+        StateTests.expandAlternateElements(fListener, model, true);
+        
+        // Set a selection in view
+        // Set a selection in view
+        TreeSelection originalSelection = new TreeSelection(
+            new TreePath[] { model.findElement("5"), model.findElement("5.1"), model.findElement("6") });
+        fViewer.setSelection(originalSelection);
+        Assert.assertTrue( StateTests.areTreeSelectionsEqual(originalSelection, (ITreeSelection)fViewer.getSelection()) );
+
+        // Set a filter to remove element "1"
+        ViewerFilter[] filters = new ViewerFilter[] { new TestViewerFilter("^1$") };
+        fViewer.setFilters(filters);
+        
+        // Note: Re-expanding nodes causes redundant updates.
+        fListener.reset(false, false);
+        fListener.addUpdates(getInternalViewer(), TreePath.EMPTY, model.getRootElement(), filters, -1, ALL_UPDATES_COMPLETE);
+        
+        // Post the refresh delta
+        model.postDelta(new ModelDelta(model.getRootElement(), IModelDelta.CONTENT));
+        while (!fListener.isFinished(ALL_UPDATES_COMPLETE | STATE_RESTORE_COMPLETE)) 
+            if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
+
+        // Validate data
+        model.validateData(fViewer, TreePath.EMPTY, true, filters);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("2")) == false);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("3")) == true);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("3.1")) == true);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("4")) == false);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("5")) == true);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("5.1")) == true);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("6")) == false);
+        Assert.assertTrue( StateTests.areTreeSelectionsEqual(originalSelection, (ITreeSelection)fViewer.getSelection()) );
+        
+        // Note: in past it was observed sub-optimal coalescing in this test due 
+        // to scattered update requests from viewer.
+        Assert.assertTrue( fListener.checkCoalesced(TreePath.EMPTY, 0, 6) );
+        
+        // Clear the filter, to re-add the element
+        filters = new ViewerFilter[0];
+        fViewer.setFilters(filters);
+
+        // Refresh again to get the filtered element back
+        fListener.reset();
+        fListener.addUpdates(getInternalViewer(), TreePath.EMPTY, model.getRootElement(), filters, -1, ALL_UPDATES_COMPLETE);
+        model.postDelta(new ModelDelta(model.getRootElement(), IModelDelta.CONTENT));
+        while (!fListener.isFinished(ALL_UPDATES_COMPLETE | STATE_RESTORE_COMPLETE)) 
+            if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
+
+        // Validate data
+        model.validateData(fViewer, TreePath.EMPTY, true, filters);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("2")) == false);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("3")) == true);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("3.1")) == true);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("4")) == false);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("5")) == true);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("5.1")) == true);
+        Assert.assertTrue(getInternalViewer().getExpandedState(model.findElement("6")) == false);
+        Assert.assertTrue( StateTests.areTreeSelectionsEqual(originalSelection, (ITreeSelection)fViewer.getSelection()) );
+        
+    }
+    
 }
diff --git a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/JFaceViewerFilterTests.java b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/JFaceViewerFilterTests.java
index 13ffecd..853d2e7 100644
--- a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/JFaceViewerFilterTests.java
+++ b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/JFaceViewerFilterTests.java
@@ -27,6 +27,6 @@
     }
 
     protected IInternalTreeModelViewer createViewer(Display display, Shell shell) {
-        return new TreeModelViewer(fShell, SWT.VIRTUAL, new PresentationContext("TestViewer"));
+        return new TreeModelViewer(fShell, SWT.VIRTUAL | SWT.MULTI, new PresentationContext("TestViewer"));
     }
 }
diff --git a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/PerformanceTests.java b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/PerformanceTests.java
index 90d9126..2a5b067 100644
--- a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/PerformanceTests.java
+++ b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/PerformanceTests.java
@@ -20,6 +20,8 @@
 import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
 import org.eclipse.jface.viewers.TreePath;
 import org.eclipse.jface.viewers.TreeSelection;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Shell;
@@ -260,5 +262,61 @@
         }
 
     }
+
+    public void testRefreshListFiltered() throws InterruptedException {
+        TestModel model = new TestModel();
+        model.setRoot( new TestElement(model, "root", new TestElement[0] ) ); 
+        int numElements = (int)Math.pow(2, getTestModelDepth());
+        model.setElementChildren(TreePath.EMPTY, TestModel.makeSingleLevelModelElements(model, 1000, "model."));
+        
+        fViewer.setAutoExpandLevel(-1);
+
+        // Create the listener
+        fListener.reset(TreePath.EMPTY, model.getRootElement(), -1, true, false); 
+
+        fViewer.addFilter(new ViewerFilter() {
+            public boolean select(Viewer viewer, Object parentElement, Object element) {
+                if (element instanceof TestElement) {
+                    String id = ((TestElement)element).getID();
+                    if (id.startsWith("model.")) {
+                        id = id.substring("model.".length());
+                    }
+                    if (id.length() >= 2 && (id.charAt(1) == '1' || id.charAt(1) == '3' || id.charAt(1) == '5' || id.charAt(1) == '7' || id.charAt(1) == '9')) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        });
+        
+        // Set the input into the view and update the view.
+        fViewer.setInput(model.getRootElement());
+        while (!fListener.isFinished()) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
+        model.validateData(fViewer, TreePath.EMPTY);
+
+        Performance perf = Performance.getDefault();
+        PerformanceMeter meter = perf.createPerformanceMeter(perf.getDefaultScenarioId(this));
+        try {
+            for (int i = 0; i < 100; i++) {
+                // Update the model
+                model.setAllAppendix(" - pass " + i);
+                
+                TestElement element = model.getRootElement();
+                fListener.reset(TreePath.EMPTY, element, -1, false, false);
+                
+                meter.start();
+                model.postDelta(new ModelDelta(element, IModelDelta.CONTENT));
+                while (!fListener.isFinished(ALL_UPDATES_COMPLETE | MODEL_CHANGED_COMPLETE)) 
+                    if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
+                meter.stop();
+                System.gc();
+            }
+            
+            meter.commit();
+            perf.assertPerformance(meter);
+        } finally {
+            meter.dispose();
+        }
+    }
     
 }
diff --git a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/StateTests.java b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/StateTests.java
index 77d0a12..3ad16f9 100644
--- a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/StateTests.java
+++ b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/StateTests.java
@@ -189,7 +189,7 @@
      *     (size).1
      *       (size).1.1
      */
-    private TestModel alternatingSubsreesModel(int size) {
+    static TestModel alternatingSubsreesModel(int size) {
         TestModel model = new TestModel();
         
         TestElement[] elements = new TestElement[size];
@@ -207,7 +207,7 @@
         return model;
     }
 
-    private boolean areTreeSelectionsEqual(ITreeSelection sel1, ITreeSelection sel2) {
+    static boolean areTreeSelectionsEqual(ITreeSelection sel1, ITreeSelection sel2) {
         Set sel1Set = new HashSet();
         sel1Set.addAll( Arrays.asList(sel1.getPaths()) );
         
@@ -217,9 +217,9 @@
         return sel1Set.equals(sel2Set);
     }
     
-    private void expandAlternateElements(TestModel model, boolean waitForAllUpdates) throws InterruptedException {
-        fListener.reset(); 
-        fListener.setFailOnRedundantUpdates(false);
+    static void expandAlternateElements(TestModelUpdatesListener listener, TestModel model, boolean waitForAllUpdates) throws InterruptedException {
+        listener.reset(); 
+        listener.setFailOnRedundantUpdates(false);
         
         TestElement rootElement = model.getRootElement();
         TestElement[] children = rootElement.getChildren();
@@ -237,7 +237,7 @@
             int index = i;
             while (element.getChildren().length != 0) {
                 TreePath elementPath = model.findElement(element.getLabel());
-                fListener.addUpdates(
+                listener.addUpdates(
                     elementPath, element, 1, 
                     CHILD_COUNT_UPDATES | (waitForAllUpdates ? CHILDREN_UPDATES : 0) );
                 delta = delta.addNode(element, index, IModelDelta.EXPAND, element.getChildren().length);
@@ -247,8 +247,8 @@
         }
         model.postDelta(rootDelta);
 
-        while (!fListener.isFinished(CONTENT_SEQUENCE_COMPLETE | MODEL_CHANGED_COMPLETE)) 
-            if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
+        while (!listener.isFinished(CONTENT_SEQUENCE_COMPLETE | MODEL_CHANGED_COMPLETE)) 
+            if (!Display.getDefault().readAndDispatch ()) Thread.sleep(0);
     }
     
     public void testPreserveExpandedOnRemove() throws InterruptedException {
@@ -265,7 +265,7 @@
         while (!fListener.isFinished()) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
         model.validateData(fViewer, TreePath.EMPTY, true);
 
-        expandAlternateElements(model, true);
+        expandAlternateElements(fListener, model, true);
         
         // Set a selection in view
         TreeSelection originalSelection = new TreeSelection(model.findElement("5.1"));
@@ -306,7 +306,7 @@
         while (!fListener.isFinished()) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
         model.validateData(fViewer, TreePath.EMPTY, true);
 
-        expandAlternateElements(model, true);
+        expandAlternateElements(fListener, model, true);
         
         // Set a selection in view
         TreeSelection originalSelection = new TreeSelection(model.findElement("5.1"));
@@ -352,7 +352,7 @@
         while (!fListener.isFinished()) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
         model.validateData(fViewer, TreePath.EMPTY, true);
 
-        expandAlternateElements(model, true);
+        expandAlternateElements(fListener, model, true);
         
         // Set a selection in view
         // Set a selection in view
@@ -460,7 +460,7 @@
         while (!fListener.isFinished()) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
         model.validateData(fViewer, TreePath.EMPTY, true);
 
-        expandAlternateElements(model, true);
+        expandAlternateElements(fListener, model, true);
         
         // Set a selection in view
 //        TreeSelection originalSelection = new TreeSelection(
@@ -530,7 +530,7 @@
         while (!fListener.isFinished(CONTENT_SEQUENCE_COMPLETE)) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
 //        model.validateData(fViewer, TreePath.EMPTY, true);
 
-        expandAlternateElements(model, false);
+        expandAlternateElements(fListener, model, false);
         
         // Set a selection in view
         TreeSelection originalSelection = new TreeSelection(model.findElement("5.1.1"));
@@ -739,7 +739,7 @@
         model.validateData(fViewer, TreePath.EMPTY, true);
 
         // Expand some, but not all elements
-        expandAlternateElements(model, true);
+        expandAlternateElements(fListener, model, true);
 
         // Set a selection in view
         fViewer.setSelection(new TreeSelection(new TreePath[] { model.findElement("5.1"), model.findElement("5.1.1"), model.findElement("6.1.1") } ));
@@ -838,7 +838,7 @@
         model.validateData(fViewer, TreePath.EMPTY, true);
 
         // Expand some, but not all elements
-        expandAlternateElements(model, true);
+        expandAlternateElements(fListener, model, true);
 
         // Set a selection in view
         fViewer.setSelection(new TreeSelection(new TreePath[] { model.findElement("5.1"), model.findElement("5.1.1"), model.findElement("6.1.1") } ));
@@ -880,7 +880,7 @@
         model.validateData(fViewer, TreePath.EMPTY, true);
 
         // Expand some, but not all elements
-        expandAlternateElements(model, true);
+        expandAlternateElements(fListener, model, true);
 
         // Set a selection in view
         fViewer.setSelection(new TreeSelection(new TreePath[] { model.findElement("5.1"), model.findElement("5.1.1"), model.findElement("6.1.1") } ));
@@ -924,7 +924,7 @@
         fViewer.setInput(model.getRootElement());
         while (!fListener.isFinished(CONTENT_SEQUENCE_COMPLETE)) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
 
-        expandAlternateElements(model, false);
+        expandAlternateElements(fListener, model, false);
         
         // Set a selection in view
         TreeSelection originalSelection = new TreeSelection(model.findElement("5.1.1"));
@@ -981,7 +981,7 @@
         fViewer.setInput(model.getRootElement());
         while (!fListener.isFinished(CONTENT_SEQUENCE_COMPLETE)) if (!fDisplay.readAndDispatch ()) Thread.sleep(0);
 
-        expandAlternateElements(model, false);
+        expandAlternateElements(fListener, model, false);
         
         // Set a selection in view
         TreeSelection originalSelection = new TreeSelection(model.findElement("5.1.1"));
diff --git a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/TestModelUpdatesListener.java b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/TestModelUpdatesListener.java
index 8b540b1..40dde84 100644
--- a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/TestModelUpdatesListener.java
+++ b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/viewer/model/TestModelUpdatesListener.java
@@ -431,9 +431,9 @@
     }
     
     public boolean isFinished(int flags) {
-        if (isTimedOut()) {
-            throw new RuntimeException("Timed Out: " + toString(flags));
-        }
+//        if (isTimedOut()) {
+//            throw new RuntimeException("Timed Out: " + toString(flags));
+//        }
         
         if (fFailExpectation != null) {
         	throw fFailExpectation;
diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java
index 82ba80b..a3cba9b 100644
--- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java
@@ -25,6 +25,7 @@
 import org.eclipe.debug.tests.viewer.model.PresentationContextTests;
 import org.eclipe.debug.tests.viewer.model.VirtualViewerContentTests;
 import org.eclipe.debug.tests.viewer.model.VirtualViewerDeltaTests;
+import org.eclipe.debug.tests.viewer.model.VirtualViewerFilterTests;
 import org.eclipe.debug.tests.viewer.model.VirtualViewerLazyModeTests;
 import org.eclipe.debug.tests.viewer.model.VirtualViewerSelectionTests;
 import org.eclipe.debug.tests.viewer.model.VirtualViewerStateTests;
@@ -62,6 +63,7 @@
 		addTest(new TestSuite(VirtualViewerSelectionTests.class));
 		addTest(new TestSuite(VirtualViewerStateTests.class));
 		addTest(new TestSuite(VirtualViewerUpdateTests.class));
+        addTest(new TestSuite(VirtualViewerFilterTests.class));
 		
 		// Viewer neutral tests
 		addTest(new TestSuite(FilterTransformTests.class));
diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/LocalSuite.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/LocalSuite.java
index 55acd57..6098eb8 100644
--- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/LocalSuite.java
+++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/LocalSuite.java
@@ -17,6 +17,7 @@
 import org.eclipe.debug.tests.viewer.model.JFaceViewerCheckTests;
 import org.eclipe.debug.tests.viewer.model.JFaceViewerContentTests;
 import org.eclipe.debug.tests.viewer.model.JFaceViewerDeltaTests;
+import org.eclipe.debug.tests.viewer.model.JFaceViewerFilterTests;
 import org.eclipe.debug.tests.viewer.model.JFaceViewerLazyTests;
 import org.eclipe.debug.tests.viewer.model.JFaceViewerSelectionTests;
 import org.eclipe.debug.tests.viewer.model.JFaceViewerStateTests;
@@ -53,6 +54,7 @@
 		addTest(new TestSuite(JFaceViewerUpdateTests.class));
         addTest(new TestSuite(JFaceViewerLazyTests.class));
         addTest(new TestSuite(JFaceViewerTopIndexTests.class));
+        addTest(new TestSuite(JFaceViewerFilterTests.class));
         addTest(new TestSuite(ColumnPresentationTests.class));
 	}
 }
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ChildrenCountUpdate.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ChildrenCountUpdate.java
index 1c2a0e6..5616975 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ChildrenCountUpdate.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ChildrenCountUpdate.java
@@ -40,7 +40,13 @@
      * children update is performed on child elements to filter them as part of the
      * child count calculation.
      */
-	private boolean fShouldFilter;
+	private boolean fShouldFilter = false;
+	
+	/**
+	 * Children indexes which are currently filtered.  When updating child count, also need 
+	 * to verify that currently filtered children are still filtered.
+	 */
+    private int[] fFilteredChildren = null;
     
 	/**
 	 * Children update used to filter children.
@@ -57,7 +63,8 @@
 	 */
 	public ChildrenCountUpdate(TreeModelContentProvider provider, Object viewerInput, TreePath elementPath, Object element, IElementContentProvider elementContentProvider) {
 		super(provider, viewerInput, elementPath, element, elementContentProvider, provider.getPresentationContext());
-		fShouldFilter = provider.getViewer().getFilters().length != 0;
+		fShouldFilter = provider.areTreeModelViewerFiltersApplicable(element);
+		fFilteredChildren = provider.getFilteredChildren(elementPath);
 	}
 
 	public synchronized void cancel() {
@@ -66,13 +73,24 @@
 		}
 		super.cancel();
 	}
-	
+
 	protected synchronized void scheduleViewerUpdate() {
-		if (fShouldFilter) {
-		    // If filtering is enabled perform child update on all children in order to update
-		    // viewer filters.
+        // If filtering is enabled perform child update on all children in order to update
+        // viewer filters.
+		if (fShouldFilter || fFilteredChildren != null) {
 		    if (fChildrenUpdate == null) {
-     		    fChildrenUpdate = new ChildrenUpdate(getContentProvider(), getViewerInput(), getElementPath(), getElement(), 0, getCount(), getElementContentProvider()) {
+		        int startIdx;
+		        int count;
+		        if (fShouldFilter) {
+		            startIdx = 0;
+		            count = getCount();
+		        } else {
+		            startIdx =  fFilteredChildren[0];
+		            int endIdx = fFilteredChildren[fFilteredChildren.length - 1];
+		            count = endIdx - startIdx + 1;
+		        }
+		        
+     		    fChildrenUpdate = new ChildrenUpdate(getContentProvider(), getViewerInput(), getElementPath(), getElement(), startIdx, count, getElementContentProvider()) {
      		    	protected void performUpdate() {
      		    		performUpdate(true);
      		    		ChildrenCountUpdate.super.scheduleViewerUpdate();
@@ -116,7 +134,13 @@
 		if (TreeModelContentProvider.DEBUG_CONTENT_PROVIDER && TreeModelContentProvider.DEBUG_TEST_PRESENTATION_ID(getPresentationContext())) {
 			System.out.println("setChildCount(" + getElement() + ", modelCount: " + fCount + " viewCount: " + viewCount + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 		}
-		getContentProvider().getViewer().setChildCount(elementPath, viewCount);
+		// Special case for element 0 in a set of filtered elements:  
+		// Child 0 is automatically updated by the tree at the same time that the child count is requested. Therefore, 
+		// If this child count update filtered out this element, it needs to be updated again.
+		if (fShouldFilter && getContentProvider().isFiltered(elementPath, 0)) {
+		    getContentProvider().updateElement(elementPath, 0);
+		}
+        getContentProvider().getViewer().setChildCount(elementPath, viewCount);
 		getContentProvider().getStateTracker().restorePendingStateOnUpdate(getElementPath(), -1, true, true, false);
 	}
 
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ITreeModelContentProvider.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ITreeModelContentProvider.java
index 090cf96..d929349 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ITreeModelContentProvider.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ITreeModelContentProvider.java
@@ -15,6 +15,7 @@
 import org.eclipse.debug.internal.ui.viewers.model.provisional.IStateUpdateListener;
 import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
 import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewerFilter;
 import org.eclipse.jface.viewers.IContentProvider;
 import org.eclipse.jface.viewers.ILazyTreePathContentProvider;
 import org.eclipse.jface.viewers.TreePath;
@@ -86,6 +87,20 @@
     public int modelToViewIndex(TreePath parentPath, int index);
 
     /**
+     * Returns whether the children of given element should be filtered.
+     * <p>This method is used to determine whether any of the registered filters
+     * that extend {@link TreeModelViewerFilter} are applicable to the given 
+     * element.  If so, then children of given element should be filtered 
+     * prior to populating them in the viewer.  
+     * 
+     * @param parentElement
+     *            the parent element 
+     * @return whether there are any {@link TreeModelViewerFilter} filters 
+     * applicable to given parent
+     */
+    public boolean areTreeModelViewerFiltersApplicable(Object parentElement);        
+    
+    /**
      * Returns whether the given element is filtered.
      * 
      * @param parentElementOrTreePath
@@ -160,6 +175,13 @@
      */
     public void removeModelChangedListener(IModelChangedListener listener);
     
+    /**
+     * Causes the content provider to save the expansion and selection state
+     * of given element.  The state is then restored as the tree is lazily
+     * re-populated.
+     * @param path Path of the element to save.
+     */
+    public void preserveState(TreePath path);
 
     /**
      * Registers the specified listener for state update notifications.
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/InternalTreeModelViewer.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/InternalTreeModelViewer.java
index d3cea20..cd0a693 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/InternalTreeModelViewer.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/InternalTreeModelViewer.java
@@ -625,6 +625,27 @@
 			refreshColumns();
 		}
 	}	
+
+	protected void internalRefresh(Object element, boolean updateLabels) {
+	    ITreeModelContentProvider contentProvider = (ITreeModelContentProvider)getContentProvider(); 
+	    
+        if (element == null) {
+            internalRefresh(getControl(), getRoot(), true, updateLabels);
+            contentProvider.preserveState(TreePath.EMPTY);
+        } else {
+            Widget[] items = findItems(element);
+            if (items.length != 0) {
+                for (int i = 0; i < items.length; i++) {
+                    if (items[i] instanceof TreeItem) {
+                        contentProvider.preserveState(getTreePathFromItem((TreeItem)items[i]));
+                    } else {
+                        contentProvider.preserveState(TreePath.EMPTY);
+                    }
+                }
+            }
+        }
+	    super.internalRefresh(element, updateLabels);
+	}
 	
     /**
      * Refreshes the columns in the view, based on the viewer input.
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/InternalVirtualTreeModelViewer.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/InternalVirtualTreeModelViewer.java
index d3da1cf..046f3dd 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/InternalVirtualTreeModelViewer.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/InternalVirtualTreeModelViewer.java
@@ -493,6 +493,8 @@
     }
 
     private void refresh(VirtualItem item) {
+        getContentProvider().preserveState(getTreePathFromItem(item));
+        
         if (!item.needsDataUpdate()) {
             if (item.getParent() != null) {
                 item.setNeedsLabelUpdate();
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/SubTreeModelViewer.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/SubTreeModelViewer.java
index 3ef3dd7..b045aba 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/SubTreeModelViewer.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/SubTreeModelViewer.java
@@ -426,6 +426,10 @@
             fBaseProvider.addModelChangedListener(listener);
         }
 
+        public void preserveState(TreePath subPath) {
+            fBaseProvider.preserveState(createFullPath(subPath));
+        }
+
         public void addStateUpdateListener(IStateUpdateListener listener) {
             fBaseProvider.addStateUpdateListener(listener);
         }
@@ -462,6 +466,10 @@
             fBaseProvider.setModelDeltaMask(mask);
         }
 
+        public boolean areTreeModelViewerFiltersApplicable(Object parentElement) {
+            return fBaseProvider.areTreeModelViewerFiltersApplicable(parentElement);
+        }
+        
         public boolean shouldFilter(Object parentElementOrTreePath, Object element) {
             if (parentElementOrTreePath instanceof TreePath) {
                 TreePath path = (TreePath)parentElementOrTreePath;
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/TreeModelContentProvider.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/TreeModelContentProvider.java
index f38fd78..18b37ec 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/TreeModelContentProvider.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/TreeModelContentProvider.java
@@ -46,6 +46,7 @@
 import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
 import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
 import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
+import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewerFilter;
 import org.eclipse.jface.viewers.IContentProvider;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.TreePath;
@@ -234,6 +235,10 @@
         fStateTracker.addStateUpdateListener(listener);
     }
 
+    public void preserveState(TreePath path) {
+        fStateTracker.appendToPendingStateDelta(path);        
+    }
+    
     public void removeStateUpdateListener(IStateUpdateListener listener) {
         fStateTracker.removeStateUpdateListener(listener);
     }
@@ -509,10 +514,34 @@
         return fTransform.modelToViewCount(parentPath, count);
     }
 
+    public boolean areTreeModelViewerFiltersApplicable(Object parentElement) {
+        ViewerFilter[] filters = fViewer.getFilters();
+        if (filters.length > 0) {
+            for (int j = 0; j < filters.length; j++) {
+                if (filters[j] instanceof TreeModelViewerFilter &&
+                    ((TreeModelViewerFilter)filters[j]).isApplicable(fViewer, parentElement)) 
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     public boolean shouldFilter(Object parentElementOrTreePath, Object element) {
         ViewerFilter[] filters = fViewer.getFilters();
         if (filters.length > 0) {
             for (int j = 0; j < filters.length; j++) {
+                if (filters[j] instanceof TreeModelViewerFilter) {
+                    // Skip the filter if not applicable to parent element
+                    Object parentElement = parentElementOrTreePath instanceof TreePath 
+                        ? ((TreePath)parentElementOrTreePath).getLastSegment() : parentElementOrTreePath;
+                    if (parentElement == null) parentElement = fViewer.getInput();
+                    if (!((TreeModelViewerFilter)filters[j]).isApplicable(fViewer, parentElement)) {
+                        continue;
+                    }
+                }
+                
                 if (!(filters[j].select((Viewer) fViewer, parentElementOrTreePath, element))) {
                     return true;
                 }
@@ -1219,7 +1248,6 @@
 		}
 		TreePath treePath = getViewerTreePath(delta);
 		cancelSubtreeUpdates(treePath);
-		fStateTracker.appendToPendingStateDelta(treePath);
 		getViewer().refresh(getElement(treePath));
 	}
 	
@@ -1414,9 +1442,31 @@
 		    index = fTransform.indexOfFilteredElement(parentPath, delta.getElement());
 		}
 		if (index >= 0) {
-	        clearFilteredChild(parentPath, index);		    
+		    boolean filtered = isFiltered(parentPath, index);
+		    boolean shouldFilter = shouldFilter(parentPath, delta.getReplacementElement());
+		    
+		    // Update the filter transform
+		    if (filtered) {
+                clearFilteredChild(parentPath, index);
+		    }
+		    if (shouldFilter) {
+		        addFilteredIndex(parentPath, index, delta.getElement());
+		    }
+		    
+		    // Update the viewer
+		    if (filtered) {
+		        if (!shouldFilter) {
+		            getViewer().insert(parentPath, delta.getReplacementElement(), modelToViewIndex(parentPath, index));
+		        } 
+		        //else do nothing
+		    } else {
+		        if (shouldFilter) {
+		            getViewer().remove(parentPath, modelToViewIndex(parentPath, index));
+		        } else {
+		            getViewer().replace(parentPath, delta.getIndex(), delta.getReplacementElement());
+		        }
+		    }
 		}
-		getViewer().replace(parentPath, delta.getIndex(), delta.getElement());
 	}
 
 	protected void handleSelect(IModelDelta delta) {
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ViewerStateTracker.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ViewerStateTracker.java
index fa71299..a116ca7 100644
--- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ViewerStateTracker.java
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/ViewerStateTracker.java
@@ -367,6 +367,8 @@
      * @param path Path to subtree to restore.
      */
     void appendToPendingStateDelta(final TreePath path) {
+        if (fContentProvider.getViewer() == null) return; // Not initialized yet.
+        
         if (DEBUG_STATE_SAVE_RESTORE && TreeModelContentProvider.DEBUG_TEST_PRESENTATION_ID(fContentProvider.getPresentationContext()))  {
             System.out.println("STATE APPEND BEGIN: " + path.getLastSegment()); //$NON-NLS-1$
         }
diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/provisional/TreeModelViewerFilter.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/provisional/TreeModelViewerFilter.java
new file mode 100644
index 0000000..0779386
--- /dev/null
+++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/viewers/model/provisional/TreeModelViewerFilter.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Wind River Systems 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:
+ *     Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.debug.internal.ui.viewers.model.provisional;
+
+import org.eclipse.jface.viewers.ViewerFilter;
+
+/**
+ * Viewer filter for the Tree Model Viewer which allows more efficient filtering
+ * in the lazy viewer.
+ * <p>
+ * The standard {@link ViewerFilter} class must be applied to all elements in the 
+ * tree, thus forcing the lazy viewer to retrieve all children of all elements and
+ * defeating the lazy loading behavior.  This class adds an {@link #isApplicable(ITreeModelViewer, Object)}
+ * method, which can be used by the filter to discern which parent elements the 
+ * filter should apply to. 
+ * </p> 
+ * 
+ * @since 3.8
+ */
+abstract public class TreeModelViewerFilter extends ViewerFilter {
+
+    /**
+     * Determines whether the filter applies to the given parent element.
+     * @return Returns true if the viewer should use the given filter on the 
+     * given element.
+     */
+    abstract public boolean isApplicable(ITreeModelViewer viewer, Object parentElement);
+}