Add substantial test coverage to the existing Facelet library location mechanism.  Also makes major additions to the mock workspace context framework.
diff --git a/jsf/tests/org.eclipse.jst.jsf.core.tests/src/org/eclipse/jst/jsf/core/tests/resource/LifecycleStressTest.java b/jsf/tests/org.eclipse.jst.jsf.core.tests/src/org/eclipse/jst/jsf/core/tests/resource/LifecycleStressTest.java
index b720786..4f647dc 100644
--- a/jsf/tests/org.eclipse.jst.jsf.core.tests/src/org/eclipse/jst/jsf/core/tests/resource/LifecycleStressTest.java
+++ b/jsf/tests/org.eclipse.jst.jsf.core.tests/src/org/eclipse/jst/jsf/core/tests/resource/LifecycleStressTest.java
@@ -48,7 +48,7 @@
         JSFTestUtil.setValidationEnabled(false);
 
         _resources = new HashMap<IProject, List<IFile>>();
-        _listener = new LifecycleListener();
+        _listener = new LifecycleListener(ResourcesPlugin.getWorkspace());
 
         for (int i = 0; i < NUM_PROJECTS; i++)
         {
diff --git a/jsf/tests/org.eclipse.jst.jsf.core.tests/src/org/eclipse/jst/jsf/core/tests/resource/TestLifecycleListener.java b/jsf/tests/org.eclipse.jst.jsf.core.tests/src/org/eclipse/jst/jsf/core/tests/resource/TestLifecycleListener.java
index c98ea4f..95dd2fb 100644
--- a/jsf/tests/org.eclipse.jst.jsf.core.tests/src/org/eclipse/jst/jsf/core/tests/resource/TestLifecycleListener.java
+++ b/jsf/tests/org.eclipse.jst.jsf.core.tests/src/org/eclipse/jst/jsf/core/tests/resource/TestLifecycleListener.java
@@ -16,6 +16,7 @@
 import junit.framework.TestCase;
 
 import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.jst.jsf.common.internal.resource.IResourceLifecycleListener;
 import org.eclipse.jst.jsf.common.internal.resource.LifecycleListener;
@@ -56,7 +57,7 @@
     {
         assertTrue(res.isAccessible());
 
-        LifecycleListener testListener = new LifecycleListener(res);
+        LifecycleListener testListener = new LifecycleListener(res, ResourcesPlugin.getWorkspace());
         MockListener mockListener = new MockListener();
         testListener.addListener(mockListener);
 
@@ -78,7 +79,7 @@
             assertTrue(res.isAccessible());
         }
 
-        LifecycleListener testListener = new LifecycleListener(resources);
+        LifecycleListener testListener = new LifecycleListener(resources, ResourcesPlugin.getWorkspace());
         MockListener mockListener = new MockListener();
         testListener.addListener(mockListener);
 
@@ -238,7 +239,7 @@
         assertTrue(_res1.isAccessible());
         assertTrue(_res2.isAccessible());
 
-        LifecycleListener testListener = new LifecycleListener(_res1);
+        LifecycleListener testListener = new LifecycleListener(_res1, ResourcesPlugin.getWorkspace());
         MockListener mockListener = new MockListener();
         testListener.addListener(mockListener);
         testListener.addResource(_res2);
@@ -259,7 +260,7 @@
         assertTrue(_res1.isAccessible());
         assertTrue(_res2.isAccessible());
 
-        LifecycleListener testListener = new LifecycleListener(_res1);
+        LifecycleListener testListener = new LifecycleListener(_res1, ResourcesPlugin.getWorkspace());
         MockListener mockListener = new MockListener();
         testListener.addListener(mockListener);
         testListener.addResource(_res2);
@@ -282,7 +283,7 @@
         assertTrue(_res1.isAccessible());
         assertTrue(_res2.isAccessible());
 
-        LifecycleListener testListener = new LifecycleListener(_res1);
+        LifecycleListener testListener = new LifecycleListener(_res1, ResourcesPlugin.getWorkspace());
         testListener.addResource(_res2);
         MockListener mockListener = new MockListener();
         MockListener mockListener2 = new MockListener();
@@ -317,7 +318,7 @@
         assertTrue(_res1.isAccessible());
         assertTrue(_res2.isAccessible());
 
-        LifecycleListener testListener = new LifecycleListener(_res1);
+        LifecycleListener testListener = new LifecycleListener(_res1, ResourcesPlugin.getWorkspace());
         testListener.addResource(_res2);
         MockListener mockListener = new MockListener();
         testListener.addListener(mockListener);
@@ -333,7 +334,7 @@
     
     public void testDisposeAfterEvent() throws Exception
     {
-        LifecycleListener testListener = new LifecycleListener(_res1);
+        LifecycleListener testListener = new LifecycleListener(_res1, ResourcesPlugin.getWorkspace());
         MockListenerThatDoesDispose mockListener = new MockListenerThatDoesDispose();
         testListener.addListener(mockListener);
 
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/META-INF/MANIFEST.MF b/jsf/tests/org.eclipse.jst.jsf.test.util/META-INF/MANIFEST.MF
index 038a331..da0ba03 100644
--- a/jsf/tests/org.eclipse.jst.jsf.test.util/META-INF/MANIFEST.MF
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/META-INF/MANIFEST.MF
@@ -6,13 +6,12 @@
 Bundle-Activator: org.eclipse.jst.jsf.test.util.Activator
 Bundle-Localization: plugin
 Require-Bundle: org.eclipse.ui;bundle-version="[3.2.0,4.0.0)",
- org.eclipse.core.runtime;bundle-version="[3.2.0,4.0.0)",
  org.eclipse.core.resources;bundle-version="[3.2.0,4.0.0)";visibility:=reexport,
- org.eclipse.jst.j2ee;bundle-version="[1.1.0,1.2.0)",
+ org.eclipse.jst.j2ee;bundle-version="[1.1.0,1.2.0)";visibility:=reexport,
  org.eclipse.jst.j2ee.core;bundle-version="[1.1.0,1.3.0)",
  org.eclipse.jst.j2ee.web;bundle-version="[1.1.0,1.2.0)",
  org.eclipse.wst.common.frameworks;bundle-version="[1.1.0,2.0.0)",
- org.eclipse.jdt.core;bundle-version="[3.2.0,4.0.0)",
+ org.eclipse.jdt.core;bundle-version="[3.2.0,4.0.0)";visibility:=reexport,
  org.junit;bundle-version="3.8.1",
  org.eclipse.wst.common.core;bundle-version="[1.1.0,2.0.0)",
  org.eclipse.wst.validation;bundle-version="[1.1.0,2.0.0)",
@@ -26,7 +25,11 @@
  org.eclipse.wst.xml.core,
  org.eclipse.ui.ide;bundle-version="3.4.0",
  org.eclipse.jst.common.project.facet.core,
- org.eclipse.core.filesystem;visibility:=reexport
+ org.eclipse.core.filesystem;visibility:=reexport,
+ org.eclipse.equinox.common;bundle-version="3.6.0";visibility:=reexport,
+ org.eclipse.core.jobs;bundle-version="3.5.0";visibility:=reexport,
+ org.eclipse.core.contenttype;bundle-version="3.4.100";visibility:=reexport,
+ org.eclipse.core.runtime;bundle-version="3.6.0";visibility:=reexport
 Export-Package: org.eclipse.jst.jsf.test.util,
  org.eclipse.jst.jsf.test.util.junit4,
  org.eclipse.jst.jsf.test.util.mock
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/JSFTestUtil.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/JSFTestUtil.java
index adca74d..1e96551 100644
--- a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/JSFTestUtil.java
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/JSFTestUtil.java
@@ -196,6 +196,12 @@
 
     }
 
+    public static void saveToFileSystem(final InputStream inStream, final URI absPath) throws IOException
+    {
+        ByteArrayOutputStream loadFromInputStream = loadFromInputStream(inStream);
+        saveToFileSystem(loadFromInputStream.toByteArray(), absPath);
+    }
+    
     /**
      * @param testFile
      * @param absPath
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/TestUtil.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/TestUtil.java
new file mode 100644
index 0000000..72d5922
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/TestUtil.java
@@ -0,0 +1,471 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2006 Sybase, Inc. 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:
+ *     Sybase, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jst.jsf.test.util;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import junit.framework.Assert;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.wst.validation.ValidationFramework;
+import org.osgi.framework.Bundle;
+
+/**
+ * Test utility to create project and its files.
+ * 
+ * @author Yang Liu, Xiao-guang Zhang
+ * 
+ * @version
+ */
+public class TestUtil
+{
+
+
+    /**
+     * 
+     * @param prjname
+     * @param path
+     *            relative to this plugin's root folder.
+     * @return
+     * @throws Exception
+     */
+    public static IProject createProjectFromZip(Bundle bundle, String prjname,
+            String path) throws Exception
+    {
+
+        URL url = FileLocator.find(bundle, new Path(path), null);
+        // if this fails, it most likely that path is wrong.
+        Assert.assertNotNull(url);
+        return createProjectFromZip(prjname, url);
+    }
+
+    /**
+     * build a project
+     * 
+     * @param project
+     * @param monitor
+     */
+    public static void buildProject(IProject project, IProgressMonitor monitor)
+    {
+        try
+        {
+            project.build(IncrementalProjectBuilder.FULL_BUILD, monitor);
+        } catch (CoreException e)
+        {
+            e.printStackTrace(System.err);
+        }
+    }
+
+    /**
+     * expand the zip stream into the specified folder.
+     * 
+     * @param url
+     * @param dir
+     * @throws Exception
+     */
+    private static void expandZip(URL url, IContainer dir)
+            throws Exception
+    {
+        ZipInputStream zis = null;
+
+        try
+        {
+            // first, count number of items in zip file
+            zis = new ZipInputStream(url.openStream());
+            String prefix = getPrefix(zis);
+            zis = new ZipInputStream(url.openStream());
+            ZipEntry ze = zis.getNextEntry();
+            while (ze != null)
+            {
+                String name = ze.getName();
+                if (!name.startsWith(prefix))
+                {
+                    ze = zis.getNextEntry();
+                    continue;
+                }
+                name = name.substring(prefix.length());
+                if (ze.isDirectory())
+                {
+                    IFolder folder = dir.getFolder(new Path(name));
+                    if (!folder.exists())
+                    {
+                        ensurePath(folder);
+                        folder.create(true, true, null);
+                    }
+                } 
+                else
+                {
+                    IFile file = dir.getFile(new Path(name));
+                    ensurePath(file);
+                    // use ZipStreamWrapper to prevent zis being closed
+                    if (file.exists())
+                    {
+                        file.setContents(new ZipStreamWrapper(zis),
+                                IResource.NONE, null);
+                    } else
+                    {
+                        file.create(new ZipStreamWrapper(zis), true, null);
+                    }
+                }
+
+                ze = zis.getNextEntry();
+            }
+        } finally
+        {
+            try
+            {
+                if (zis != null)
+                    zis.close();
+            } catch (Exception ex)
+            {
+                // ignore
+            }
+        }
+    }
+
+    private static String getPrefix(ZipInputStream zipStream) throws IOException
+    {
+        ZipEntry ze = zipStream.getNextEntry();
+        while (ze != null)
+        {
+            String name = ze.getName();
+            if (name != null && name.endsWith(".project")
+                    && !ze.isDirectory())
+            {
+                int index = name.lastIndexOf(".project");
+                return name.substring(0, index);
+            }
+            ze = zipStream.getNextEntry();
+        }
+        // if we get to here, then nothing to prepend
+        return "";
+    }
+
+    /**
+     * @param file
+     * @throws CoreException
+     */
+    private static void ensurePath(IResource file) throws CoreException
+    {
+        IContainer container = file.getParent();
+        if (!container.exists())
+        {
+            ensurePath(container);
+            ((IFolder) container).create(true, true, null);
+        }
+    }
+
+    /**
+     * this method will copy file from the sourcePath folder of this plugin into
+     * the target path related with the destination project.
+     * 
+     * @param project
+     * @param sourcePath
+     *            - Source path, must be a relative path to test plugin
+     * @param targetPath
+     *            - Target path, must be relative path to eclispe project.
+     * 
+     * @return
+     */
+    public static IFile copyFile(Bundle bundle, IProject project, String targetPath,
+            String sourcePath) throws Exception
+    {
+
+        URL url = FileLocator.find(bundle,
+                new Path(sourcePath), null);
+        InputStream stream = url.openStream();
+
+        IFile file = null;
+        IPath path = new Path(sourcePath);
+        if (targetPath != null && targetPath.length() > 0)
+        {
+            IFolder folder = project.getFolder(targetPath);
+            file = folder.getFile(path.lastSegment());
+        } else
+        {
+            file = project.getFile(path.lastSegment());
+        }
+
+        if (!file.exists())
+        {
+            ensurePath(file);
+            file.create(stream, true, null);
+        } else
+        {
+            file.setContents(stream, IFile.FORCE, null);
+        }
+
+        return file;
+    }
+
+    /**
+     * this method will create page file from the "pages" folder of this plugin
+     * into the webroot folder of the destination project.
+     * 
+     * @param filePath
+     *            - file path, must be relative path to destination project.
+     * 
+     * @return
+     */
+    public static IFile createFile(IProject project, String filePath,
+            String content) throws Exception
+    {
+        IFile file = project.getFile(filePath);
+        ensurePath(file);
+        ByteArrayInputStream stream = new ByteArrayInputStream(content
+                .getBytes());
+        file.create(stream, true, null);
+        return file;
+    }
+
+    /**
+     * this method will get the page file from the "pages" folder of this plugin
+     * as a string.
+     * 
+     * @param path
+     *            - related with plugin
+     * @return
+     * @throws Exception
+     */
+    public static String getFileAsString(String path) throws Exception
+    {
+
+        URL url = FileLocator.find(Activator.getDefault().getBundle(),
+                new Path(path), null);
+        InputStream stream = url.openStream();
+        BufferedReader reader = new BufferedReader(
+                new InputStreamReader(stream));
+        StringBuffer buffer = new StringBuffer();
+        char[] temp = new char[256];
+        int count;
+        while ((count = reader.read(temp)) > 0)
+        {
+            buffer.append(temp, 0, count);
+        }
+        reader.close();
+        stream.close();
+        return buffer.toString();
+    }
+
+    /**
+	 * create a project from a zip file.
+	 * 
+	 * @param prjname
+	 * @param zipStream
+	 * @throws Exception
+	 */
+	private static IProject createProjectFromZip(final String prjname,
+			final URL url) throws Exception {
+		final IProject[] holder = new IProject[1];
+		IWorkspaceRunnable r = new IWorkspaceRunnable() {
+			public void run(IProgressMonitor monitor) throws CoreException {
+				IProject prj = ResourcesPlugin.getWorkspace().getRoot()
+						.getProject(prjname);
+				if (!prj.exists()) {
+					prj.create(null);
+				}
+				prj.open(null);
+				ValidationFramework.getDefault().suspendValidation(prj, true);
+				try {
+					expandZip(url, prj);
+				} catch (Exception ex) {
+					throw new CoreException(new Status(0,
+							Activator.PLUGIN_ID, 0, ex
+									.getMessage(), ex));
+				}
+				holder[0] = prj;
+			}
+		};
+        ResourcesPlugin.getWorkspace().run(r, null);
+		return holder[0];
+	}
+	
+    public static String removeAllWhitespace(String s)
+    {
+        StringBuffer buffer = new StringBuffer(s.length());
+        for (int i = 0, length = s.length(); i < length; i++)
+        {
+            if (!Character.isWhitespace(s.charAt(i)))
+            {
+                buffer.append(s.charAt(i));
+            }
+        }
+        return buffer.toString();
+    }
+    /**
+     * remove resource, following schedule rule.
+     * 
+     * @param prj
+     * @throws CoreException
+     */
+    public static void removeResource(final IResource prj) throws CoreException
+    {
+        if (prj instanceof IFile)
+        {
+            ((IFile) prj).delete(true, false, null);
+            return;
+        }
+        Job job = new Job("DeleteProject")
+        {
+
+            protected IStatus run(IProgressMonitor monitor)
+            {
+                try
+                {
+                    prj.delete(true, monitor);
+                } catch (CoreException e)
+                {
+                    return Status.CANCEL_STATUS;
+                }
+                return Status.OK_STATUS;
+            }
+
+        };
+        job.setPriority(Job.SHORT);
+        job.setRule(ResourcesPlugin.getWorkspace().getRoot());
+        job.schedule();
+
+        // wait
+        try
+        {
+            job.join();
+        } catch (InterruptedException e)
+        {
+        }
+    }
+
+    private final static boolean PROJECT_IS_OPEN = true;
+
+    public static boolean verifyProjectStatus(final IProject project,
+            final boolean isOpenCondition, final int waitTimeInMs)
+    {
+        return TestUtil.waitForCondition(createProjectStatusCondition(project,
+                isOpenCondition), waitTimeInMs, 20);
+    }
+
+    public static TestCondition createProjectStatusCondition(
+            final IProject project, final boolean isOpenCondition)
+    {
+        return new TestCondition()
+        {
+            @Override
+            public boolean test()
+            {
+                return project.isOpen() == isOpenCondition;
+            }
+        };
+    }
+
+    public static boolean openProject(final IProject project,
+            final int waitTimeInMs) throws CoreException
+    {
+        project.open(null);
+        return verifyProjectStatus(project, PROJECT_IS_OPEN, waitTimeInMs);
+    }
+
+    public static boolean closeProject(final IProject project,
+            final int waitTimeInMs) throws CoreException
+    {
+        project.close(null);
+        return verifyProjectStatus(project, !PROJECT_IS_OPEN, waitTimeInMs);
+    }
+
+    public static boolean waitForCondition(final TestCondition condition,
+            final int maxTime, final int numIntervals)
+    {
+        int curIteration = 0;
+        int waitPerInterval = maxTime / numIntervals;
+        if (waitPerInterval < 1)
+        {
+            Assert.fail("Your wait interval is less than 1");
+        }
+
+        do
+        {
+            if (condition.test())
+            {
+                return true;
+            }
+
+            try
+            {
+                Thread.sleep(waitPerInterval);
+            } catch (InterruptedException e)
+            {
+                // ignore.
+            }
+        } while (curIteration++ < numIntervals);
+
+        // if we get to here, then the condition was not satisified in the
+        // time alloted
+        return false;
+    }
+
+    public static abstract class TestCondition
+    {
+        public abstract boolean test();
+    }
+
+    public static class CompositeTestCondition extends TestCondition
+    {
+        protected final List<? extends TestCondition> _conditions;
+
+        public CompositeTestCondition(
+                final List<? extends TestCondition> conditions)
+        {
+            super();
+            _conditions = Collections
+                    .unmodifiableList(new ArrayList<TestCondition>(conditions));
+        }
+
+        public void dispose()
+        {
+            // do nothing by default
+        }
+
+        @Override
+        public boolean test()
+        {
+            boolean isSatisfied = true;
+            for (final TestCondition condition : _conditions)
+            {
+                isSatisfied &= condition.test();
+            }
+            return isSatisfied;
+        }
+    }
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/ZipStreamWrapper.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/ZipStreamWrapper.java
new file mode 100644
index 0000000..48b4929
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/ZipStreamWrapper.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2006 Sybase, Inc. 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:
+ *     Sybase, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jst.jsf.test.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipInputStream;
+
+/**
+ * @author Yang Liu
+ * @version 
+ */
+public class ZipStreamWrapper extends InputStream
+{
+
+    private ZipInputStream zipStream;
+
+    /**
+     * 
+     */
+    public ZipStreamWrapper(ZipInputStream stream)
+    {
+        super();
+        this.zipStream = stream;
+    }
+
+    /* (non-Javadoc)
+     * @see java.io.InputStream#read()
+     */
+    public int read() throws IOException
+    {
+        return zipStream.read();
+    }
+
+    /* (non-Javadoc)
+     * @see java.io.InputStream#read(byte[])
+     */
+    public int read(byte[] b) throws IOException
+    {
+        return zipStream.read(b);
+    }
+    
+    /* (non-Javadoc)
+     * @see java.io.InputStream#read(byte[], int, int)
+     */
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        return zipStream.read(b, off, len);
+    }
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/junit4/RequiresPluginEnvironment.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/junit4/RequiresPluginEnvironment.java
new file mode 100644
index 0000000..6a23981
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/junit4/RequiresPluginEnvironment.java
@@ -0,0 +1,14 @@
+package org.eclipse.jst.jsf.test.util.junit4;
+
+/**
+ * Marks a JUnit4 test category that must be run using the PDE Plugin Test JUnit
+ * Runner and the plugin env that it bootstraps.  These tests cannot accept
+ * Workspace mocks.
+ * 
+ * @author cbateman
+ *
+ */
+public interface RequiresPluginEnvironment extends SlowTest
+{
+
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/junit4/SlowTest.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/junit4/SlowTest.java
new file mode 100644
index 0000000..c442ed2
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/junit4/SlowTest.java
@@ -0,0 +1,12 @@
+package org.eclipse.jst.jsf.test.util.junit4;
+
+/**
+ * Marks a JUnit 4 test category that runs "slowly".
+ * @author cbateman
+ * 
+ *
+ */
+public interface SlowTest
+{
+
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/IMockResourceFactory.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/IMockResourceFactory.java
new file mode 100644
index 0000000..e0eab39
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/IMockResourceFactory.java
@@ -0,0 +1,41 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import java.util.List;
+
+import org.eclipse.core.runtime.IPath;
+
+public interface IMockResourceFactory
+{
+    /**
+     * @param path the path, relative to container where the file should
+     * be created.
+     * @return the mock file for the path.  If the file already exists
+     * for this path, then this will be returned. If a resource already exists
+     * for this path but it is not a file then a ClassCastException is thrown.
+     */
+    MockResource createFile(final MockContainer container, final IPath path) throws Exception;
+    
+    /**
+     * @param container
+     * @return all of the resources this factory currently knows about for the 
+     * container.  In other words, all of the paths within container for 
+     * which create* methods have been successfully called.
+     */
+    List<MockResource>  getCurrentMembers(final MockContainer container);
+
+    /**
+     * Cause any "existent" resources that have not yet be created from test
+     * source (i.e. from a project zip) to be loaded so that they will be
+     * returned by getCurrentMembers().
+     * 
+     * @throws Exception
+     */
+    void forceLoad(final MockProject project) throws Exception;
+    /**
+     * Signal that the factory should release any resources it is holding.
+     * @throws Exception 
+     */
+    void dispose() throws Exception;
+
+    public abstract MockFolder createFolder(final MockContainer container, final IPath path);
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockContainer.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockContainer.java
new file mode 100644
index 0000000..3dfd169
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockContainer.java
@@ -0,0 +1,162 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import junit.framework.AssertionFailedError;
+
+import org.eclipse.core.resources.FileInfoMatcherDescription;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceFilterDescription;
+import org.eclipse.core.resources.IResourceVisitor;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+
+public class MockContainer extends MockResource implements IContainer
+{
+    private final IMockResourceFactory _resFactory;
+
+    public MockContainer(int type, IPath path, IMockResourceFactory resFactory)
+    {
+        super(type, path);
+        _resFactory = resFactory;
+    }
+
+    public void loadAllMembers() throws Exception 
+    {
+        _resFactory.forceLoad((MockProject) this.getProject());
+    }
+    
+    @Override
+    public void dispose() throws Exception
+    {
+        try
+        {
+            _resFactory.dispose();
+        } finally
+        {
+            super.dispose();
+        }
+    }
+
+
+    public boolean exists(IPath path)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IResource findMember(String name)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IResource findMember(String name, boolean includePhantoms)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IResource findMember(IPath path)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IResource findMember(IPath path, boolean includePhantoms)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getDefaultCharset() throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getDefaultCharset(boolean checkImplicit) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IFile getFile(IPath path)
+    {
+        try
+        {
+            return (IFile) _resFactory.createFile(this, path);
+        } catch (Exception e)
+        {
+            throw new AssertionFailedError(e.getLocalizedMessage());
+        }
+    }
+
+    public IFile getFile(String path)
+    {
+        return getFile(new Path(path));
+    }
+
+    public IFolder getFolder(IPath path)
+    {
+        try
+        {
+            return _resFactory.createFolder(this, path);
+        } catch (Exception e)
+        {
+            throw new AssertionFailedError(e.getLocalizedMessage());
+        }
+    }
+
+    
+    @Override
+    protected void visitMembers(IResourceVisitor visitor, int depth,
+            int memberFlags) throws CoreException
+    {
+         for (final IResource res : members(memberFlags))
+         {
+             res.accept(visitor, depth, memberFlags);
+         }
+    }
+
+    public IResource[] members() throws CoreException
+    {
+        return members(IResource.NONE);
+    }
+
+    public IResource[] members(boolean includePhantoms) throws CoreException
+    {
+        return members(includePhantoms ? INCLUDE_PHANTOMS : IResource.NONE);
+    }
+
+    public IResource[] members(int memberFlags) throws CoreException
+    {
+        // ignore member flags for now
+        return _resFactory.getCurrentMembers(this).toArray(new IResource[0]);
+    }
+
+    public IFile[] findDeletedMembersWithHistory(int depth,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setDefaultCharset(String charset) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setDefaultCharset(String charset, IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IResourceFilterDescription createFilter(int type,
+            FileInfoMatcherDescription matcherDescription, int updateFlags,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IResourceFilterDescription[] getFilters() throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockExtensionRegistryProvider.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockExtensionRegistryProvider.java
new file mode 100644
index 0000000..dbdeedb
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockExtensionRegistryProvider.java
@@ -0,0 +1,18 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import java.io.File;
+
+import org.eclipse.core.internal.registry.ExtensionRegistry;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.spi.IRegistryProvider;
+import org.eclipse.core.runtime.spi.RegistryStrategy;
+
+public class MockExtensionRegistryProvider implements IRegistryProvider
+{
+
+    public IExtensionRegistry getRegistry()
+    {
+        return new ExtensionRegistry(new RegistryStrategy(new File[0], new boolean[0]), new Object(), new Object());
+    }
+
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockFile.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockFile.java
new file mode 100644
index 0000000..1cb307c
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockFile.java
@@ -0,0 +1,184 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URI;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFileState;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.content.IContentDescription;
+import org.eclipse.jst.jsf.test.util.Activator;
+import org.eclipse.jst.jsf.test.util.JSFTestUtil;
+
+public class MockFile extends MockResource implements IFile
+{
+
+    private byte[]  _contents;
+
+    public MockFile(final IPath path)
+    {
+        super(IResource.FILE, path);
+    }
+
+    public void appendContents(final InputStream source, final boolean force,
+            final boolean keepHistory, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void appendContents(final InputStream source, final int updateFlags,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void create(final InputStream source, final boolean force,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void create(final InputStream source, final int updateFlags,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void createLink(final IPath localLocation, final int updateFlags,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void createLink(final URI location, final int updateFlags,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void delete(final boolean force, final boolean keepHistory,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String getCharset() throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String getCharset(final boolean checkImplicit) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String getCharsetFor(final Reader reader) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IContentDescription getContentDescription() throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public InputStream getContents() throws CoreException
+    {
+        return getContents(false);
+
+    }
+
+    public InputStream getContents(final boolean force) throws CoreException
+    {
+        return new ByteArrayInputStream(_contents);
+    }
+
+    public int getEncoding() throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IFileState[] getHistory(final IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void move(final IPath destination, final boolean force,
+            final boolean keepHistory, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void setCharset(final String newCharset) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void setCharset(final String newCharset,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void setContents(final InputStream source, final boolean force,
+            final boolean keepHistory, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        if (_contents != null && !force)
+        {
+            throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Attempt to reset contents without force"));
+        }
+        ByteArrayOutputStream captureBytes;
+        try
+        {
+            captureBytes = JSFTestUtil.loadFromInputStream(source);
+        } catch (IOException e)
+        {
+            throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed loading mock contents from stream"));
+        }
+        _contents = captureBytes.toByteArray();
+    }
+
+    public void setContents(final IFileState source, final boolean force,
+            final boolean keepHistory, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        setContents(source.getContents(), force, keepHistory, monitor);
+    }
+
+    public void setContents(final InputStream source, final int updateFlags,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        setContents(source, (updateFlags | IResource.FORCE)!= 0, (updateFlags | IResource.KEEP_HISTORY) != 0, monitor);
+    }
+
+    public void setContents(final IFileState source, final int updateFlags,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        setContents(source, (updateFlags | IResource.FORCE)!= 0, (updateFlags | IResource.KEEP_HISTORY) != 0, monitor);
+    }
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockFolder.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockFolder.java
new file mode 100644
index 0000000..a58e2e4
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockFolder.java
@@ -0,0 +1,66 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import java.net.URI;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+public class MockFolder extends MockContainer implements IFolder
+{
+
+    public MockFolder(IPath path, IMockResourceFactory resFactory)
+    {
+        super(IResource.FOLDER, path, resFactory);
+    }
+
+    public void create(boolean force, boolean local, IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void create(int updateFlags, boolean local, IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void createLink(IPath localLocation, int updateFlags,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void createLink(URI location, int updateFlags,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void delete(boolean force, boolean keepHistory,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IFolder getFolder(String name)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void move(IPath destination, boolean force, boolean keepHistory,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockProject.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockProject.java
new file mode 100644
index 0000000..4cc2f94
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockProject.java
@@ -0,0 +1,178 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IProjectNature;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IPluginDescriptor;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.content.IContentTypeMatcher;
+
+@SuppressWarnings("deprecation")
+public class MockProject extends MockContainer implements IProject
+{
+
+    private Set<String> _natures = new HashSet<String>();
+
+    public MockProject(final IPath path, final IMockResourceFactory resFactory)
+    {
+        super(IResource.PROJECT, new Path("MockProject_"+path), resFactory);
+        setProject(this);
+    }
+
+    @SuppressWarnings("rawtypes")
+    public void build(int kind, String builderName, Map args,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void build(int kind, IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void close(IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void create(IProjectDescription description, IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void create(IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void create(IProjectDescription description, int updateFlags,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void delete(boolean deleteContent, boolean force,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IContentTypeMatcher getContentTypeMatcher() throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IProjectDescription getDescription() throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IFolder getFolder(String name)
+    {
+        return getFolder(new Path(name));
+    }
+
+    public IProjectNature getNature(String natureId) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IPath getPluginWorkingLocation(IPluginDescriptor plugin)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IPath getWorkingLocation(String id)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IProject[] getReferencedProjects() throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IProject[] getReferencingProjects()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addNature(String natureId)
+    {
+        _natures.add(natureId);
+    }
+    public boolean hasNature(String natureId) throws CoreException
+    {
+        return _natures.contains(natureId);
+    }
+
+    public boolean isNatureEnabled(String natureId) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isOpen()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void loadSnapshot(int options, URI snapshotLocation,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void move(IProjectDescription description, boolean force,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void open(int updateFlags, IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void open(IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void saveSnapshot(int options, URI snapshotLocation,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setDescription(IProjectDescription description,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setDescription(IProjectDescription description,
+            int updateFlags, IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public IContainer getParent()
+    {
+        return getWorkspace().getRoot();
+    }
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResource.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResource.java
index f19f11c..ada834e 100644
--- a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResource.java
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResource.java
@@ -17,11 +17,11 @@
 import org.eclipse.core.resources.IResourceProxy;
 import org.eclipse.core.resources.IResourceProxyVisitor;
 import org.eclipse.core.resources.IResourceVisitor;
-import org.eclipse.core.resources.IWorkspace;
 import org.eclipse.core.resources.ResourceAttributes;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.QualifiedName;
 import org.eclipse.core.runtime.jobs.ISchedulingRule;
 
@@ -29,6 +29,10 @@
 {
     private final int _type;
     private final IPath _path;
+    private boolean _exists = true; // always exist by default
+    private MockWorkspace _workspace;
+    private IProject _project;
+    private long _modificationStamp;
     public final static List<Integer> VALID_TYPES;
 
     static
@@ -71,8 +75,8 @@
 
     }
 
-    public void accept(final IResourceProxyVisitor visitor, final int memberFlags)
-    throws CoreException
+    public void accept(final IResourceProxyVisitor visitor,
+            final int memberFlags) throws CoreException
     {
         throw new UnsupportedOperationException();
 
@@ -80,29 +84,42 @@
 
     public void accept(final IResourceVisitor visitor) throws CoreException
     {
-        throw new UnsupportedOperationException();
+        accept(visitor, IResource.DEPTH_INFINITE, IResource.NONE);
     }
 
     public void accept(final IResourceVisitor visitor, final int depth,
             final boolean includePhantoms) throws CoreException
-            {
-        throw new UnsupportedOperationException();
-            }
-
-    public void accept(final IResourceVisitor visitor, final int depth, final int memberFlags)
-    throws CoreException
     {
-        throw new UnsupportedOperationException();
+        accept(visitor, depth, includePhantoms ? IContainer.INCLUDE_PHANTOMS
+                : IResource.NONE);
     }
 
-    public void clearHistory(final IProgressMonitor monitor) throws CoreException
+    public void accept(final IResourceVisitor visitor, final int depth,
+            final int memberFlags) throws CoreException
+    {
+        boolean visit = visitor.visit(this);
+
+        if (visit)
+        {
+            visitMembers(visitor, depth, memberFlags);
+        }
+    }
+
+    protected void visitMembers(IResourceVisitor visitor, int depth,
+            int memberFlags) throws CoreException
+    {
+        // do nothing by default. Container must to override to visit members.
+    }
+
+    public void clearHistory(final IProgressMonitor monitor)
+            throws CoreException
     {
         throw new UnsupportedOperationException();
 
     }
 
-    public void copy(final IPath destination, final boolean force, final IProgressMonitor monitor)
-    throws CoreException
+    public void copy(final IPath destination, final boolean force,
+            final IProgressMonitor monitor) throws CoreException
     {
         throw new UnsupportedOperationException();
 
@@ -110,24 +127,26 @@
 
     public void copy(final IPath destination, final int updateFlags,
             final IProgressMonitor monitor) throws CoreException
-            {
+    {
         throw new UnsupportedOperationException();
 
-            }
+    }
 
-    public void copy(final IProjectDescription description, final boolean force,
-            final IProgressMonitor monitor) throws CoreException
-            {
+    public void copy(final IProjectDescription description,
+            final boolean force, final IProgressMonitor monitor)
+            throws CoreException
+    {
         throw new UnsupportedOperationException();
 
-            }
+    }
 
-    public void copy(final IProjectDescription description, final int updateFlags,
-            final IProgressMonitor monitor) throws CoreException
-            {
+    public void copy(final IProjectDescription description,
+            final int updateFlags, final IProgressMonitor monitor)
+            throws CoreException
+    {
         throw new UnsupportedOperationException();
 
-            }
+    }
 
     public IMarker createMarker(final String type) throws CoreException
     {
@@ -140,21 +159,21 @@
     }
 
     public void delete(final boolean force, final IProgressMonitor monitor)
-    throws CoreException
+            throws CoreException
     {
         throw new UnsupportedOperationException();
 
     }
 
     public void delete(final int updateFlags, final IProgressMonitor monitor)
-    throws CoreException
+            throws CoreException
     {
         throw new UnsupportedOperationException();
 
     }
 
-    public void deleteMarkers(final String type, final boolean includeSubtypes, final int depth)
-    throws CoreException
+    public void deleteMarkers(final String type, final boolean includeSubtypes,
+            final int depth) throws CoreException
     {
         throw new UnsupportedOperationException();
 
@@ -162,7 +181,12 @@
 
     public boolean exists()
     {
-        throw new UnsupportedOperationException();
+        return _exists;
+    }
+
+    public void setExists(final boolean exists)
+    {
+        _exists = exists;
     }
 
     public IMarker findMarker(final long id) throws CoreException
@@ -170,17 +194,19 @@
         throw new UnsupportedOperationException();
     }
 
-    public IMarker[] findMarkers(final String type, final boolean includeSubtypes, final int depth)
-    throws CoreException
+    public IMarker[] findMarkers(final String type,
+            final boolean includeSubtypes, final int depth)
+            throws CoreException
     {
         throw new UnsupportedOperationException();
     }
 
-    public int findMaxProblemSeverity(final String type, final boolean includeSubtypes,
-            final int depth) throws CoreException
-            {
+    public int findMaxProblemSeverity(final String type,
+            final boolean includeSubtypes, final int depth)
+            throws CoreException
+    {
         throw new UnsupportedOperationException();
-            }
+    }
 
     public String getFileExtension()
     {
@@ -195,7 +221,7 @@
 
     public IPath getFullPath()
     {
-        throw new UnsupportedOperationException();
+        return _path;
     }
 
     public long getLocalTimeStamp()
@@ -220,7 +246,7 @@
 
     public long getModificationStamp()
     {
-        throw new UnsupportedOperationException();
+        return _modificationStamp;
     }
 
     public String getName()
@@ -230,27 +256,46 @@
 
     public IContainer getParent()
     {
-        throw new UnsupportedOperationException();
+        final IPath myParent = getProjectRelativePath().removeLastSegments(1);
+
+        // if the parent is the empty path, then our parent is the root.
+        if (myParent.segmentCount() == 0)
+        {
+            return getProject();
+        }
+        return _project.getFolder(getProjectRelativePath().removeLastSegments(1));
     }
 
-    public Map<?,?> getPersistentProperties() throws CoreException
+    public Map<?, ?> getPersistentProperties() throws CoreException
     {
         throw new UnsupportedOperationException();
     }
 
-    public String getPersistentProperty(final QualifiedName key) throws CoreException
+    public String getPersistentProperty(final QualifiedName key)
+            throws CoreException
     {
         throw new UnsupportedOperationException();
     }
 
     public IProject getProject()
     {
-        throw new UnsupportedOperationException();
+        return _project;
+    }
+
+    public void setProject(final IProject project)
+    {
+        _project = project;
     }
 
     public IPath getProjectRelativePath()
     {
-        throw new UnsupportedOperationException();
+        if (getType() == IResource.ROOT || getType() == IResource.PROJECT)
+        {
+            return new Path("");
+        }
+        IPath projectPath = getProject().getFullPath();
+        Assert.assertTrue(projectPath.isPrefixOf(getFullPath()));
+        return getFullPath().removeFirstSegments(projectPath.segmentCount());
     }
 
     public IPath getRawLocation()
@@ -268,24 +313,30 @@
         throw new UnsupportedOperationException();
     }
 
-    public Map<?,?> getSessionProperties() throws CoreException
+    public Map<?, ?> getSessionProperties() throws CoreException
     {
         throw new UnsupportedOperationException();
     }
 
-    public Object getSessionProperty(final QualifiedName key) throws CoreException
+    public Object getSessionProperty(final QualifiedName key)
+            throws CoreException
     {
         throw new UnsupportedOperationException();
     }
 
-    public IWorkspace getWorkspace()
+    public MockWorkspace getWorkspace()
     {
-        throw new UnsupportedOperationException();
+        return _workspace;
+    }
+
+    public void setWorkspace(final MockWorkspace workspace)
+    {
+        _workspace = workspace;
     }
 
     public boolean isAccessible()
     {
-        throw new UnsupportedOperationException();
+        return exists();
     }
 
     public boolean isDerived()
@@ -358,8 +409,8 @@
         throw new UnsupportedOperationException();
     }
 
-    public void move(final IPath destination, final boolean force, final IProgressMonitor monitor)
-    throws CoreException
+    public void move(final IPath destination, final boolean force,
+            final IProgressMonitor monitor) throws CoreException
     {
         throw new UnsupportedOperationException();
 
@@ -367,27 +418,29 @@
 
     public void move(final IPath destination, final int updateFlags,
             final IProgressMonitor monitor) throws CoreException
-            {
+    {
         throw new UnsupportedOperationException();
 
-            }
+    }
 
-    public void move(final IProjectDescription description, final boolean force,
-            final boolean keepHistory, final IProgressMonitor monitor) throws CoreException
-            {
-        throw new UnsupportedOperationException();
-
-            }
-
-    public void move(final IProjectDescription description, final int updateFlags,
+    public void move(final IProjectDescription description,
+            final boolean force, final boolean keepHistory,
             final IProgressMonitor monitor) throws CoreException
-            {
+    {
         throw new UnsupportedOperationException();
 
-            }
+    }
+
+    public void move(final IProjectDescription description,
+            final int updateFlags, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
 
     public void refreshLocal(final int depth, final IProgressMonitor monitor)
-    throws CoreException
+            throws CoreException
     {
         throw new UnsupportedOperationException();
 
@@ -405,8 +458,8 @@
 
     }
 
-    public void setDerived(final boolean isDerived, final IProgressMonitor monitor)
-    throws CoreException
+    public void setDerived(final boolean isDerived,
+            final IProgressMonitor monitor) throws CoreException
     {
         throw new UnsupportedOperationException();
 
@@ -418,8 +471,8 @@
 
     }
 
-    public void setLocal(final boolean flag, final int depth, final IProgressMonitor monitor)
-    throws CoreException
+    public void setLocal(final boolean flag, final int depth,
+            final IProgressMonitor monitor) throws CoreException
     {
         throw new UnsupportedOperationException();
 
@@ -430,8 +483,8 @@
         throw new UnsupportedOperationException();
     }
 
-    public void setPersistentProperty(final QualifiedName key, final String value)
-    throws CoreException
+    public void setPersistentProperty(final QualifiedName key,
+            final String value) throws CoreException
     {
         throw new UnsupportedOperationException();
 
@@ -444,21 +497,21 @@
     }
 
     public void setResourceAttributes(final ResourceAttributes attributes)
-    throws CoreException
+            throws CoreException
     {
         throw new UnsupportedOperationException();
 
     }
 
     public void setSessionProperty(final QualifiedName key, final Object value)
-    throws CoreException
+            throws CoreException
     {
         throw new UnsupportedOperationException();
 
     }
 
     public void setTeamPrivateMember(final boolean isTeamPrivate)
-    throws CoreException
+            throws CoreException
     {
         throw new UnsupportedOperationException();
 
@@ -479,4 +532,20 @@
     {
         throw new UnsupportedOperationException();
     }
+
+    public void dispose() throws Exception
+    {
+        _project = null;
+        _workspace = null;
+    }
+    
+    public String toString()
+    {
+        return _path.toString();
+    }
+
+    public void incrementModStamp()
+    {
+        _modificationStamp++;
+    }
 }
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceChangeEvent.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceChangeEvent.java
new file mode 100644
index 0000000..5936223
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceChangeEvent.java
@@ -0,0 +1,60 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import org.eclipse.core.resources.IMarkerDelta;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceDelta;
+
+public class MockResourceChangeEvent implements IResourceChangeEvent
+{
+    private final MockResourceDelta _delta;
+    private final IResource _resource;
+    private final int _type;
+
+    /**
+     * @param resource
+     * @param type
+     * @param delta
+     */
+    public MockResourceChangeEvent(final IResource resource, final int type, final MockResourceDelta delta)
+    {
+        _delta = delta;
+        _resource = resource;
+        _type = type;
+    }
+    public MockResourceChangeEvent(final int type, final MockResourceDelta delta)
+    {
+        this(null, type, delta);
+    }
+
+    public IMarkerDelta[] findMarkerDeltas(final String type, final boolean includeSubtypes)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public int getBuildKind()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IResourceDelta getDelta()
+    {
+        return _delta;
+    }
+
+    public IResource getResource()
+    {
+        return _resource;
+    }
+
+    public Object getSource()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public int getType()
+    {
+        return _type;
+    }
+
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceChangeEventFactory.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceChangeEventFactory.java
new file mode 100644
index 0000000..9f0ab43
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceChangeEventFactory.java
@@ -0,0 +1,46 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import org.eclipse.core.resources.IResourceChangeEvent;
+
+public class MockResourceChangeEventFactory
+{
+    private final MockResourceDeltaFactory _deltaFactory;
+
+    public MockResourceChangeEventFactory(
+            final MockResourceDeltaFactory deltaFactory)
+    {
+        _deltaFactory = deltaFactory;
+    }
+
+    public MockResourceChangeEventFactory()
+    {
+        this(new MockResourceDeltaFactory());
+    }
+
+    public IResourceChangeEvent createSimpleFileChange(MockFile file,
+            boolean incrementModStamp)
+    {
+        MockResourceDelta delta = _deltaFactory.createSimpleFileChange(file);
+        if (incrementModStamp)
+        {
+            file.incrementModStamp();
+        }
+        return new MockResourceChangeEvent(IResourceChangeEvent.POST_CHANGE,
+                delta);
+    }
+    
+    public IResourceChangeEvent createSimpleFileRemove(MockFile file)
+    {
+        final MockResourceDelta delta = _deltaFactory.createSimpleFileRemoved(file);
+        return new MockResourceChangeEvent(IResourceChangeEvent.POST_CHANGE,
+                delta);
+    }
+
+    public IResourceChangeEvent createSimpleFileAdded(final MockFile file)
+    {
+        final MockResourceDelta delta = _deltaFactory.createSimpleFileAdded(file);
+        return new MockResourceChangeEvent(IResourceChangeEvent.POST_CHANGE,
+                delta);
+    }
+
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceDelta.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceDelta.java
new file mode 100644
index 0000000..34810b1
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceDelta.java
@@ -0,0 +1,327 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.internal.runtime.AdapterManager;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IMarkerDelta;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+
+public class MockResourceDelta implements IResourceDelta
+{
+    public enum StateType
+    {
+        BEFORE_EVENT, AFTER_EVENT
+    }
+
+    private final IResource _afterEventResource;
+    private final IResource _beforeEventResource;
+    private final List<MockResourceDelta> _childDeltas;
+    private final int _status;
+
+    protected static int KIND_MASK = 0xFF;
+
+    public MockResourceDelta(final IResource afterEventResource,
+            final IResource beforeEventResource, final int status,
+            final List<MockResourceDelta> childDeltas)
+    {
+        super();
+        _afterEventResource = afterEventResource;
+        _beforeEventResource = beforeEventResource;
+        _status = status;
+        _childDeltas = childDeltas;
+    }
+
+    /*
+     * @see IResourceDelta#accept(IResourceDeltaVisitor)
+     */
+    public void accept(IResourceDeltaVisitor visitor) throws CoreException
+    {
+        accept(visitor, 0);
+    }
+
+    /*
+     * @see IResourceDelta#accept(IResourceDeltaVisitor, boolean)
+     */
+    public void accept(IResourceDeltaVisitor visitor, boolean includePhantoms)
+            throws CoreException
+    {
+        accept(visitor, includePhantoms ? IContainer.INCLUDE_PHANTOMS : 0);
+    }
+
+    /*
+     * @see IResourceDelta#accept(IResourceDeltaVisitor, int)
+     */
+    public void accept(IResourceDeltaVisitor visitor, int memberFlags)
+            throws CoreException
+    {
+        final boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0;
+        final boolean includeTeamPrivate = (memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) != 0;
+        final boolean includeHidden = (memberFlags & IContainer.INCLUDE_HIDDEN) != 0;
+        int mask = includePhantoms ? ALL_WITH_PHANTOMS : REMOVED | ADDED
+                | CHANGED;
+        if ((getKind() & mask) == 0)
+            return;
+        if (!visitor.visit(this))
+            return;
+        for (final MockResourceDelta childDelta : _childDeltas)
+        {
+            // quietly exclude team-private, hidden and phantom members unless
+            // explicitly included
+            if (!includeTeamPrivate && childDelta.isTeamPrivate())
+                continue;
+            if (!includePhantoms && childDelta.isPhantom())
+                continue;
+            if (!includeHidden && childDelta.isHidden())
+                continue;
+            childDelta.accept(visitor, memberFlags);
+        }
+    }
+
+    /**
+     * @see IResourceDelta#findMember(IPath)
+     */
+    public IResourceDelta findMember(IPath path)
+    {
+        int segmentCount = path.segmentCount();
+        if (segmentCount == 0)
+            return this;
+
+        // iterate over the path and find matching child delta
+        MockResourceDelta current = this;
+        segments: for (int i = 0; i < segmentCount; i++)
+        {
+            List<MockResourceDelta> currentChildren = current._childDeltas;
+            for (int j = 0, jmax = currentChildren.size(); j < jmax; j++)
+            {
+                if (currentChildren.get(j).getFullPath().lastSegment().equals(
+                        path.segment(i)))
+                {
+                    current = (MockResourceDelta) currentChildren.get(j);
+                    continue segments;
+                }
+            }
+            // matching child not found, return
+            return null;
+        }
+        return current;
+    }
+
+    /**
+     * @see IResourceDelta#getAffectedChildren()
+     */
+    public IResourceDelta[] getAffectedChildren()
+    {
+        return getAffectedChildren(ADDED | REMOVED | CHANGED, IResource.NONE);
+    }
+
+    /**
+     * @see IResourceDelta#getAffectedChildren(int)
+     */
+    public IResourceDelta[] getAffectedChildren(int kindMask)
+    {
+        return getAffectedChildren(kindMask, IResource.NONE);
+    }
+
+    /*
+     * @see IResourceDelta#getAffectedChildren(int, int)
+     */
+    public IResourceDelta[] getAffectedChildren(int kindMask, int memberFlags)
+    {
+        final int numChildren = _childDeltas.size();
+        // if there are no children, they all match
+        if (numChildren == 0)
+        {
+            return _childDeltas.toArray(new IResourceDelta[0]);
+        }
+        boolean includePhantoms = (memberFlags & IContainer.INCLUDE_PHANTOMS) != 0;
+        boolean includeTeamPrivate = (memberFlags & IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS) != 0;
+        boolean includeHidden = (memberFlags & IContainer.INCLUDE_HIDDEN) != 0;
+        // reduce INCLUDE_PHANTOMS member flag to kind mask
+        if (includePhantoms)
+            kindMask |= ADDED_PHANTOM | REMOVED_PHANTOM;
+
+        // first count the number of matches so we can allocate the exact array
+        // size
+        int matching = 0;
+        for (int i = 0; i < numChildren; i++)
+        {
+            if ((_childDeltas.get(i).getKind() & kindMask) == 0)
+                continue;// child has wrong kind
+            if (!includePhantoms && _childDeltas.get(i).isPhantom())
+                continue;
+            if (!includeTeamPrivate && _childDeltas.get(i).isTeamPrivate())
+                continue; // child has is a team-private member which are not
+            // included
+            if (!includeHidden && _childDeltas.get(i).isHidden())
+                continue;
+            matching++;
+        }
+        // use arraycopy if all match
+        if (matching == numChildren)
+        {
+            final List<IResourceDelta> copy = new ArrayList<IResourceDelta>(
+                    _childDeltas);
+            return copy.toArray(new IResourceDelta[0]);
+        }
+        // create the appropriate sized array and fill it
+        IResourceDelta[] result = new IResourceDelta[matching];
+        int nextPosition = 0;
+        for (int i = 0; i < numChildren; i++)
+        {
+            if ((_childDeltas.get(i).getKind() & kindMask) == 0)
+                continue; // child has wrong kind
+            if (!includePhantoms && _childDeltas.get(i).isPhantom())
+                continue;
+            if (!includeTeamPrivate && _childDeltas.get(i).isTeamPrivate())
+                continue; // child has is a team-private member which are not
+            // included
+            if (!includeHidden && _childDeltas.get(i).isHidden())
+                continue;
+            result[nextPosition++] = _childDeltas.get(i);
+        }
+        return result;
+    }
+
+    /**
+     * @see IResourceDelta#getFlags()
+     */
+    public int getFlags()
+    {
+        return _status & ~KIND_MASK;
+    }
+
+    private boolean isSet(final int flag)
+    {
+        return (getFlags() & flag) != 0;
+    }
+
+    /**
+     * @see IResourceDelta#getFullPath()
+     */
+    public IPath getFullPath()
+    {
+
+        return getActiveResource().getFullPath();
+    }
+
+    /**
+     * @see IResourceDelta#getKind()
+     */
+    public int getKind()
+    {
+        return _status & KIND_MASK;
+    }
+
+    public IMarkerDelta[] getMarkerDeltas()
+    {
+        throw new UnsupportedOperationException(
+                "getMarkerDeltas not supported by the mock");
+    }
+
+    public IPath getMovedFromPath()
+    {
+        if (isSet(MOVED_FROM))
+        {
+            return _beforeEventResource.getFullPath();
+        }
+        return null;
+    }
+
+    public IPath getMovedToPath()
+    {
+        if (isSet(MOVED_TO))
+        {
+            return _afterEventResource.getFullPath();
+        }
+        return null;
+    }
+
+    public IPath getProjectRelativePath()
+    {
+        return _beforeEventResource.getProjectRelativePath();
+    }
+
+    public IResource getResource()
+    {
+        return getActiveResource();
+    }
+
+    @SuppressWarnings("rawtypes")
+    public Object getAdapter(Class adapter)
+    {
+        return AdapterManager.getDefault().getAdapter(this, adapter);
+    }
+
+    private IResource getActiveResource()
+    {
+        switch (getStateType())
+        {
+        case AFTER_EVENT:
+            return _afterEventResource;
+        case BEFORE_EVENT:
+            return _beforeEventResource;
+        default:
+            throw new IllegalStateException("Should not get here");
+        }
+    }
+
+    private StateType getStateType()
+    {
+        if (getKind() == ADDED || getKind() == ADDED_PHANTOM || isSet(MOVED_TO) || isSet(MOVED_FROM))
+        {
+            return StateType.AFTER_EVENT;
+        }
+        return StateType.BEFORE_EVENT;
+    }
+
+    /**
+     * Returns true if this delta represents a hidden member, and false
+     * otherwise.
+     */
+    protected boolean isHidden()
+    {
+        // // use old info for removals, and new info for added or changed
+        // if ((_status & (REMOVED | REMOVED_PHANTOM)) != 0)
+        // return ResourceInfo.isSet(oldInfo.getFlags(),
+        // ICoreConstants.M_HIDDEN);
+        // return ResourceInfo.isSet(newInfo.getFlags(),
+        // ICoreConstants.M_HIDDEN);
+        return false;
+    }
+
+    /**
+     * Returns true if this delta represents a phantom member, and false
+     * otherwise.
+     */
+    protected boolean isPhantom()
+    {
+        // // use old info for removals, and new info for added or changed
+        // if ((_status & (REMOVED | REMOVED_PHANTOM)) != 0)
+        // return ResourceInfo.isSet(oldInfo.getFlags(),
+        // ICoreConstants.M_PHANTOM);
+        // return ResourceInfo.isSet(newInfo.getFlags(),
+        // ICoreConstants.M_PHANTOM);
+        return false;
+    }
+
+    /**
+     * Returns true if this delta represents a team private member, and false
+     * otherwise.
+     */
+    protected boolean isTeamPrivate()
+    {
+        // // use old info for removals, and new info for added or changed
+        // if ((_status & (REMOVED | REMOVED_PHANTOM)) != 0)
+        // return ResourceInfo.isSet(oldInfo.getFlags(),
+        // ICoreConstants.M_TEAM_PRIVATE_MEMBER);
+        // return ResourceInfo.isSet(newInfo.getFlags(),
+        // ICoreConstants.M_TEAM_PRIVATE_MEMBER);
+        return false;
+    }
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceDeltaFactory.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceDeltaFactory.java
new file mode 100644
index 0000000..dcc76ab
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockResourceDeltaFactory.java
@@ -0,0 +1,102 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+
+public class MockResourceDeltaFactory
+{
+    public MockResourceDelta createSimpleFileChange(final IFile file)
+    {
+        return newChangeDelta(file, IResourceDelta.CHANGED |IResourceDelta.CONTENT);
+    }
+
+    public MockResourceDelta createSimpleFileAdded(final IFile file)
+    {
+        return newAddDelta(file, IResourceDelta.ADDED);
+    }
+
+    public MockResourceDelta createSimpleFolderAdded(final IFolder folder)
+    {
+        return newAddDelta(folder, IResourceDelta.ADDED);
+    }
+
+    public MockResourceDelta createSimpleFileRemoved(final IFile file)
+    {
+        return newRemoveDelta(file, IResourceDelta.REMOVED);
+    }
+
+    public MockResourceDelta createSimpleFolderRemoved(final IFolder folder)
+    {
+        return newRemoveDelta(folder, IResourceDelta.REMOVED);
+    }
+
+    /**
+     * @param startLocation
+     * @param endLocation
+     * @param movedType Either IResourceDelta.MOVED_FROM or IResourceDelta.MOVED_TO
+     * @return the mock delta
+     */
+    public MockResourceDelta createFileMoved(final IFile startLocation,
+            final IFile endLocation, final int movedType)
+    {
+        //TODO: need to implement multiple deltas
+        throw new UnsupportedOperationException();
+//        if (movedType != IResourceDelta.MOVED_FROM && movedType != IResourceDelta.MOVED_TO)
+//        {
+//            throw new IllegalArgumentException("movedType must be MOVED_FROM or MOVED_TO");
+//        }
+//        return new MockResourceDelta(endLocation, startLocation,
+//                IResourceDelta.CHANGED | movedType, Collections.EMPTY_LIST);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected MockResourceDelta newChangeDelta(final IResource resource, final int kind)
+    {
+        final MockResourceDelta delta = new MockResourceDelta(resource, resource, kind,
+                Collections.EMPTY_LIST);
+        return createWorkspaceRootedDeltaTo(resource, delta);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected MockResourceDelta newAddDelta(final IResource resource, final int kind)
+    {
+        final MockResourceDelta delta = new MockResourceDelta(resource, null, kind,
+                Collections.EMPTY_LIST);
+        return createWorkspaceRootedDeltaTo(resource, delta);
+    }
+    
+    @SuppressWarnings("unchecked")
+    protected MockResourceDelta newRemoveDelta(final IResource resource, final int kind)
+    {
+        final MockResourceDelta delta = new MockResourceDelta(null, resource, kind,
+                Collections.EMPTY_LIST);
+        return createWorkspaceRootedDeltaTo(resource, delta);
+    }
+
+    /**
+     * @return a delta that doesn't represent a change itself but has descendants
+     * that do.
+     */
+    public MockResourceDelta createPathToChangeDelta(final IResource res, final List<MockResourceDelta> descendants)
+    {
+        return new MockResourceDelta(res,res,0, descendants);
+    }
+    
+    protected MockResourceDelta createWorkspaceRootedDeltaTo(final IResource res, final MockResourceDelta leafRes)
+    {
+        MockResourceDelta curDelta = leafRes;
+        IContainer parent = res.getParent();
+        while (parent != null)
+        {
+            curDelta = createPathToChangeDelta(parent, Collections.singletonList(curDelta));
+            parent = parent.getParent();
+        }
+        return curDelta;
+    }
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockVirtualFile.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockVirtualFile.java
new file mode 100644
index 0000000..1406059
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockVirtualFile.java
@@ -0,0 +1,162 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
+import org.eclipse.wst.common.componentcore.resources.IVirtualContainer;
+import org.eclipse.wst.common.componentcore.resources.IVirtualFile;
+
+public class MockVirtualFile implements IVirtualFile
+{
+    private IFile _realFile;
+
+    public MockVirtualFile(IFile realFile)
+    {
+        _realFile = realFile;
+    }
+
+    public void createLink(IPath aProjectRelativeLocation, int updateFlags,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void removeLink(IPath aProjectRelativeLocation, int updateFlags,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void delete(int updateFlags, IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean exists()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String getFileExtension()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IPath getWorkspaceRelativePath()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IPath getProjectRelativePath()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IPath getRuntimePath()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String getName()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualComponent getComponent()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualContainer getParent()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IProject getProject()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public int getType()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IResource getUnderlyingResource()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IResource[] getUnderlyingResources()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean isAccessible()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String getResourceType()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void setResourceType(String aResourceType)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean contains(ISchedulingRule rule)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean isConflicting(ISchedulingRule rule)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    @SuppressWarnings("rawtypes")
+    public Object getAdapter(Class adapter)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IFile getUnderlyingFile()
+    {
+        return _realFile;
+    }
+
+    public IFile[] getUnderlyingFiles()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockVirtualFolder.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockVirtualFolder.java
new file mode 100644
index 0000000..89e4bed
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockVirtualFolder.java
@@ -0,0 +1,253 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
+import org.eclipse.wst.common.componentcore.resources.IVirtualContainer;
+import org.eclipse.wst.common.componentcore.resources.IVirtualFile;
+import org.eclipse.wst.common.componentcore.resources.IVirtualFolder;
+import org.eclipse.wst.common.componentcore.resources.IVirtualResource;
+
+public class MockVirtualFolder implements IVirtualFolder
+{
+    private final IContainer _realFolder;
+
+    public MockVirtualFolder(final IContainer realFolder)
+    {
+        _realFolder = realFolder;
+    }
+
+    public void create(final int updateFlags, final IProgressMonitor aMonitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean exists(final IPath path)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualResource findMember(final String name)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualResource findMember(final String name, final int searchFlags)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualResource findMember(final IPath path)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualResource findMember(final IPath path, final int searchFlags)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualFile getFile(final IPath path)
+    {
+        IFile file2 = _realFolder.getFile(path);
+        if (file2 != null)
+        {
+            return new MockVirtualFile(file2);
+        }
+        return null;
+    }
+
+    public IVirtualFolder getFolder(final IPath path)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualFile getFile(final String name)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualFolder getFolder(final String name)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualResource[] members() throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualResource[] members(final int memberFlags)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualResource[] getResources(final String aResourceType)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void createLink(final IPath aProjectRelativeLocation,
+            final int updateFlags, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void removeLink(final IPath aProjectRelativeLocation,
+            final int updateFlags, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void delete(final int updateFlags, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean exists()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String getFileExtension()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IPath getWorkspaceRelativePath()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IPath getProjectRelativePath()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IPath getRuntimePath()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String getName()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualComponent getComponent()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IVirtualContainer getParent()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IProject getProject()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public int getType()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IResource getUnderlyingResource()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IResource[] getUnderlyingResources()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean isAccessible()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String getResourceType()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void setResourceType(final String aResourceType)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean contains(final ISchedulingRule rule)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean isConflicting(final ISchedulingRule rule)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    @SuppressWarnings("rawtypes")
+    public Object getAdapter(final Class adapter)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IContainer getUnderlyingFolder()
+    {
+        return _realFolder;
+    }
+
+    public IContainer[] getUnderlyingFolders()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspace.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspace.java
index 578691c..da850c9 100644
--- a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspace.java
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspace.java
@@ -1,8 +1,374 @@
 package org.eclipse.jst.jsf.test.util.mock;
 
-import org.eclipse.core.internal.resources.Workspace;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 
-public class MockWorkspace extends Workspace
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFilterMatcherDescriptor;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IPathVariableManager;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IProjectNatureDescriptor;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceRuleFactory;
+import org.eclipse.core.resources.ISaveParticipant;
+import org.eclipse.core.resources.ISavedState;
+import org.eclipse.core.resources.ISynchronizer;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceDescription;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.resources.WorkspaceLock;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+
+@SuppressWarnings("deprecation")
+public class MockWorkspace implements IWorkspace
 {
+    private final CopyOnWriteArrayList<IResourceChangeListener> _changeListeners = new CopyOnWriteArrayList<IResourceChangeListener>();
+    private final MockWorkspaceRoot _root;
+
+    public MockWorkspace(final MockWorkspaceRoot root)
+    {
+        _root = root;
+    }
+    public void dispose()
+    {
+        _changeListeners.clear();
+    }
+
+    @SuppressWarnings("rawtypes")
+    public Object getAdapter(final Class adapter)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void addResourceChangeListener(final IResourceChangeListener listener)
+    {
+        _changeListeners.addIfAbsent(listener);
+    }
+
+    public void addResourceChangeListener(final IResourceChangeListener listener,
+            final int eventMask)
+    {
+        // TODO: ignore masks for now
+        _changeListeners.addIfAbsent(listener);
+    }
+
+    protected void fireResourceChangeEvent(final IResourceChangeEvent event)
+    {
+        for (final IResourceChangeListener listener : _changeListeners)
+        {
+            listener.resourceChanged(event);
+        }
+    }
+
+    public List<IResourceChangeListener> getListeners()
+    {
+        return Collections.unmodifiableList(_changeListeners);
+    }
+    
+    public ISavedState addSaveParticipant(final Plugin plugin,
+            final ISaveParticipant participant) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public ISavedState addSaveParticipant(final String pluginId,
+            final ISaveParticipant participant) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void build(final int kind, final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void checkpoint(final boolean build)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IProject[][] computePrerequisiteOrder(final IProject[] projects)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public ProjectOrder computeProjectOrder(final IProject[] projects)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus copy(final IResource[] resources, final IPath destination,
+            final boolean force, final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus copy(final IResource[] resources, final IPath destination,
+            final int updateFlags, final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus delete(final IResource[] resources, final boolean force,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus delete(final IResource[] resources, final int updateFlags,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void deleteMarkers(final IMarker[] markers) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void forgetSavedTree(final String pluginId)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IFilterMatcherDescriptor[] getFilterMatcherDescriptors()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IFilterMatcherDescriptor getFilterMatcherDescriptor(
+            final String filterMatcherId)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IProjectNatureDescriptor[] getNatureDescriptors()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IProjectNatureDescriptor getNatureDescriptor(final String natureId)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    @SuppressWarnings("rawtypes")
+    public Map getDanglingReferences()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IWorkspaceDescription getDescription()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IWorkspaceRoot getRoot()
+    {
+        return _root;
+    }
+
+    public IResourceRuleFactory getRuleFactory()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public ISynchronizer getSynchronizer()
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public boolean isAutoBuilding()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isTreeLocked()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public IProjectDescription loadProjectDescription(
+            final IPath projectDescriptionFile) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IProjectDescription loadProjectDescription(
+            final InputStream projectDescriptionFile) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus move(final IResource[] resources, final IPath destination,
+            final boolean force, final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus move(final IResource[] resources, final IPath destination,
+            final int updateFlags, final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IProjectDescription newProjectDescription(final String projectName)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void removeResourceChangeListener(final IResourceChangeListener listener)
+    {
+        _changeListeners.remove(listener);
+    }
+
+    public void removeSaveParticipant(final Plugin plugin)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void removeSaveParticipant(final String pluginId)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void run(final IWorkspaceRunnable action, final ISchedulingRule rule, final int flags,
+            final IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void run(final IWorkspaceRunnable action, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus save(final boolean full, final IProgressMonitor monitor)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void setDescription(final IWorkspaceDescription description)
+            throws CoreException
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public void setWorkspaceLock(final WorkspaceLock lock)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public String[] sortNatureSet(final String[] natureIds)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus validateEdit(final IFile[] files, final Object context)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus validateFiltered(final IResource resource)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus validateLinkLocation(final IResource resource, final IPath location)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus validateLinkLocationURI(final IResource resource, final URI location)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus validateName(final String segment, final int typeMask)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus validateNatureSet(final String[] natureIds)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus validatePath(final String path, final int typeMask)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus validateProjectLocation(final IProject project, final IPath location)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IStatus validateProjectLocationURI(final IProject project, final URI location)
+    {
+        throw new UnsupportedOperationException();
+
+    }
+
+    public IPathVariableManager getPathVariableManager()
+    {
+        throw new UnsupportedOperationException();
+
+    }
 
 }
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspaceContext.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspaceContext.java
new file mode 100644
index 0000000..5ef6f4b
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspaceContext.java
@@ -0,0 +1,291 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+
+/**
+ * A test context that can be used to construct a number of MockResources that
+ * are associated with a mocked workspace/project hierarchy
+ * 
+ * @author cbateman
+ * 
+ */
+public class MockWorkspaceContext
+{
+    private final MockWorkspace _ws;
+
+    private final Map<IPath, MockResource> _ownedResources;
+
+    public MockWorkspaceContext(final MockWorkspace ws)
+    {
+        _ws = ws;
+        _ownedResources = new HashMap<IPath, MockResource>();
+    }
+
+    public MockWorkspaceContext()
+    {
+        this(new MockWorkspace(new MockWorkspaceRoot()));
+    }
+
+    public void fireWorkspaceEvent(final IResourceChangeEvent event)
+    {
+        _ws.fireResourceChangeEvent(event);
+    }
+
+    public void dispose() throws Exception
+    {
+        for (final Map.Entry<IPath, MockResource> entry : _ownedResources
+                .entrySet())
+        {
+            entry.getValue().dispose();
+        }
+        _ownedResources.clear();
+        _ws.dispose();
+    }
+
+    public MockWorkspace getWorkspace()
+    {
+        return _ws;
+    }
+
+    public MockResource getResource(final IPath path)
+    {
+        return _ownedResources.get(path);
+    }
+
+    public MockProject getProject(final IPath path)
+    {
+        return (MockProject) _ownedResources.get(path);
+    }
+
+    public MockProject createProject(final IPath path, final boolean replace)
+    {
+        final MockProject project = new MockProject(path,
+                new MyMockResourceFactory());
+        attachProject(project, replace);
+        return project;
+    }
+
+    /**
+     * @return a mock project with a generated name that is guaranteed not to
+     *         conflict with any that already exist in this context.
+     */
+    public MockProject createProject(final String baseId)
+    {
+        int i = 0;
+
+        while (_ownedResources.get(generateName(baseId, i)) != null)
+        {
+            // keep looping until we get TestProject_i that doesn't exist
+            i++;
+        }
+
+        return createProject(generateName(baseId, i), false);
+    }
+
+    private Path generateName(final String baseId, final int i)
+    {
+        return new Path(baseId + "_TestProject_" + i);
+    }
+
+    public MockProject createProject(final IPath path)
+    {
+        return createProject(path, false);
+    }
+
+    public void attachProject(final MockProject project, final boolean replace)
+    {
+        checkExists(project.getFullPath(), replace);
+        project.setWorkspace(_ws);
+        _ownedResources.put(project.getFullPath(), project);
+    }
+
+    public MockProject loadProject(final IPath path, final ZipFile zip)
+            throws Exception
+    {
+        return loadProject(path, zip, "");
+    }
+
+    public MockProject loadProject(final IPath path, final ZipFile zip,
+            final String pathIntoZip) throws Exception
+    {
+        checkExists(path, false);
+        final MockProject project = new MockProject(path,
+                new MyMockResourceFactory(zip, pathIntoZip));
+        attachProject(project, false);
+        return project;
+    }
+
+    private MockResource checkExists(final IPath path, final boolean replace)
+    {
+        final MockResource resource = _ownedResources.get(path);
+
+        if (resource != null && !replace)
+        {
+            throw new IllegalArgumentException(path.toString()
+                    + " already exists");
+        }
+        return resource;
+    }
+
+    private class MyMockResourceFactory implements IMockResourceFactory
+    {
+        private ZipFile _zip;
+        private final String _pathIntoZip;
+
+        public MyMockResourceFactory()
+        {
+            // do nothing.
+            _pathIntoZip = "";
+        }
+
+        public MyMockResourceFactory(final ZipFile zip, final String pathIntoZip)
+                throws Exception
+        {
+            _zip = zip;
+            _pathIntoZip = pathIntoZip;
+        }
+
+        public MockFile createFile(final MockContainer container,
+                final IPath path) throws CoreException, IOException
+        {
+            final IPath newFileFullPath = container.getFullPath().append(path);
+            MockResource resource = checkExists(newFileFullPath, true);
+            if (resource == null)
+            {
+
+                resource = new MockFile(newFileFullPath);
+                ((MockResource) resource).setWorkspace(_ws);
+                ((MockResource) resource).setProject(container.getProject());
+                if (_zip != null)
+                {
+                    final ZipEntry entry = _zip.getEntry(_pathIntoZip
+                            + path.toString());
+
+                    if (entry != null)
+                    {
+                        final InputStream inputStream = _zip
+                                .getInputStream(entry);
+                        if (inputStream != null)
+                        {
+                            ((MockFile) resource).setContents(inputStream,
+                                    false, true, new NullProgressMonitor());
+                        }
+                    }
+
+                    ensurePathToNewResource(container, path);
+                }
+                _ownedResources.put(newFileFullPath, resource);
+            }
+            return (MockFile) resource;
+        }
+
+        public MockFolder createFolder(final MockContainer container,
+                final IPath path)
+        {
+            final IPath newFileFullPath = container.getFullPath().append(path);
+            MockResource resource = checkExists(newFileFullPath, true);
+            if (resource == null)
+            {
+
+                resource = new MockFolder(newFileFullPath, this);
+                ((MockResource) resource).setWorkspace(_ws);
+                ((MockResource) resource).setProject(container.getProject());
+                ensurePathToNewResource(container, path);
+                _ownedResources.put(newFileFullPath, resource);
+            }
+            return (MockFolder) resource;
+        }
+
+        protected void ensurePathToNewResource(final MockContainer container,
+                final IPath path)
+        {
+            // add any intervening MockContainers for the folders
+            // under container where the file lives
+            IPath leadingPath = path.removeLastSegments(1);
+            IPath curPath = container.getFullPath();
+            while (leadingPath.segmentCount() > 0)
+            {
+                final String nextSegment = leadingPath.segments()[0];
+                curPath = curPath.append(nextSegment);
+                leadingPath = leadingPath.removeFirstSegments(1);
+                MockResource newContainer = checkExists(curPath, true);
+                if (newContainer == null)
+                {
+                    newFolder(container, curPath);
+                }
+            }
+        }
+
+        protected void newFolder(MockContainer container, IPath curPath)
+        {
+            MockResource newContainer;
+            newContainer = new MockFolder(curPath, this);
+            newContainer.setWorkspace(_ws);
+            newContainer.setProject(container.getProject());
+            _ownedResources.put(curPath, newContainer);
+        }
+
+        public void dispose() throws Exception
+        {
+            if (_zip != null)
+            {
+                _zip.close();
+            }
+        }
+
+        public List<MockResource> getCurrentMembers(
+                final MockContainer container)
+        {
+            final List<MockResource> members = new ArrayList<MockResource>();
+            final IPath containerPath = container.getFullPath();
+            for (final IPath path : _ownedResources.keySet())
+            {
+                // path is a member of container if container's path is
+                // a prefix of path and path has only one extra segment
+                if (containerPath.isPrefixOf(path)
+                        && path.segmentCount() == containerPath.segmentCount() + 1)
+                {
+                    members.add(_ownedResources.get(path));
+                }
+            }
+            return members;
+        }
+
+        public void forceLoad(final MockProject project) throws Exception
+        {
+            final Enumeration<? extends ZipEntry> entries = _zip.entries();
+            while (entries.hasMoreElements())
+            {
+                final ZipEntry entry = entries.nextElement();
+                String name = entry.getName();
+                final int removeIdx = name.indexOf(_pathIntoZip);
+                if (removeIdx > -1)
+                {
+                    name = name.substring(_pathIntoZip.length());
+                    if (entry.isDirectory())
+                    {
+                        newFolder(project, new Path(name));
+                    } else
+                    {
+                        createFile(project, new Path(name));
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspaceRoot.java b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspaceRoot.java
new file mode 100644
index 0000000..95c55a3
--- /dev/null
+++ b/jsf/tests/org.eclipse.jst.jsf.test.util/src/org/eclipse/jst/jsf/test/util/mock/MockWorkspaceRoot.java
@@ -0,0 +1,108 @@
+package org.eclipse.jst.jsf.test.util.mock;
+
+import java.net.URI;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+
+public class MockWorkspaceRoot extends MockContainer implements IWorkspaceRoot
+{
+
+    public MockWorkspaceRoot()
+    {
+        super(IResource.ROOT, new Path(""), null);
+    }
+
+    public void delete(boolean deleteContent, boolean force,
+            IProgressMonitor monitor) throws CoreException
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IContainer[] findContainersForLocation(IPath location)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IContainer[] findContainersForLocationURI(URI location)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IContainer[] findContainersForLocationURI(URI location,
+            int memberFlags)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IFile[] findFilesForLocation(IPath location)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IFile[] findFilesForLocationURI(URI location)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IFile[] findFilesForLocationURI(URI location, int memberFlags)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IContainer getContainerForLocation(IPath location)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IFile getFileForLocation(IPath location)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IProject getProject(String name)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IProject[] getProjects()
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    public IProject[] getProjects(int memberFlags)
+    {
+        throw new UnsupportedOperationException();
+        
+    }
+
+    @Override
+    public IProject getProject()
+    {
+        return null;
+    }
+
+    @Override
+    public IContainer getParent()
+    {
+        return null;
+    }
+}