[204567] Dependency Graph Manager rewrite
diff --git a/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraph.java b/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraph.java
index e811f34..74341f2 100644
--- a/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraph.java
+++ b/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraph.java
@@ -10,45 +10,39 @@
  *******************************************************************************/
 package org.eclipse.wst.common.componentcore.internal.builder;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
+import java.util.Collections;
 import java.util.Set;
 
 import org.eclipse.core.resources.IProject;
 
+/**
+ * @deprecated use {@link IDependencyGraph}
+ */
 public class DependencyGraph {
-	
+
 	private static final DependencyGraph INSTANCE = new DependencyGraph();
-	
-	private final Map dependencies = new HashMap();
-	
+
 	public static DependencyGraph getInstance() {
 		return INSTANCE;
 	}
 
 	public IProject[] getReferencingComponents(IProject target) {
-		Set referencingComponents = internalGetReferencingComponents(target);
+		Set<IProject> referencingComponents = IDependencyGraph.INSTANCE.getReferencingComponents(target);
 		return (IProject[]) referencingComponents.toArray(new IProject[referencingComponents.size()]);
 	}
-	
+
 	public void addReference(IProject target, IProject referencingComponent) {
-		internalGetReferencingComponents(target).add(referencingComponent);
+		// do nothing
 	}
-	
+
 	public void removeReference(IProject target, IProject referencingComponent) {
-		internalGetReferencingComponents(target).remove(referencingComponent);
-		
+		// do nothing
 	}
-	
+
 	protected Set internalGetReferencingComponents(IProject target) {
-		Set referencingComponents = (Set) dependencies.get(target);
-		if(referencingComponents == null) 
-			dependencies.put(target, (referencingComponents = new HashSet()));
-		return referencingComponents;
+		return Collections.EMPTY_SET;
 	}
-	
+
 	public void clear() {
-		dependencies.clear();
 	}
 }
diff --git a/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraphImpl.java b/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraphImpl.java
new file mode 100644
index 0000000..075591e
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraphImpl.java
@@ -0,0 +1,447 @@
+package org.eclipse.wst.common.componentcore.internal.builder;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.wst.common.componentcore.ComponentCore;
+import org.eclipse.wst.common.componentcore.internal.ModulecorePlugin;
+import org.eclipse.wst.common.componentcore.internal.impl.WTPModulesResourceFactory;
+import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
+import org.eclipse.wst.common.componentcore.resources.IVirtualReference;
+
+public class DependencyGraphImpl implements IDependencyGraph {
+
+	/**
+	 * Don't read or write the graph without first obtaining the graphLock.
+	 */
+	private Object graphLock = new Object();
+
+	/**
+	 * If projects and and B both depend on C an entry in this graph would be {C ->
+	 * {A, B} }
+	 */
+	private Map<IProject, Set<IProject>> graph = null;
+
+	private long modStamp = 0;
+
+	/**
+	 * This is not public; only {@link IDependencyGraph#INSTANCE} should be
+	 * used.
+	 * 
+	 * @return
+	 */
+	static IDependencyGraph getInstance() {
+		if (instance == null) {
+			instance = new DependencyGraphImpl();
+			instance.initGraph();
+		}
+		return instance;
+	}
+
+	private static DependencyGraphImpl instance = null;
+
+	private DependencyGraphImpl() {
+	}
+
+	public long getModStamp() {
+		synchronized (graphLock) {
+			return modStamp;
+		}
+	}
+
+	public Set<IProject> getReferencingComponents(IProject targetProject) {
+		waitForAllUpdates(null);
+		synchronized (graphLock) {
+			Set<IProject> set = graph.get(targetProject);
+			if (set == null) {
+				return Collections.EMPTY_SET;
+			} else {
+				for (Iterator<IProject> iterator = set.iterator(); iterator.hasNext();) {
+					IProject project = iterator.next();
+					if (!project.isAccessible()) {
+						iterator.remove();
+					}
+				}
+				Set<IProject> copy = new HashSet<IProject>();
+				copy.addAll(set);
+				return copy;
+			}
+		}
+	}
+
+	private class DependencyGraphResourceChangedListener implements IResourceChangeListener, IResourceDeltaVisitor {
+		// only registered for post change events
+		public void resourceChanged(IResourceChangeEvent event) {
+			try {
+				preUpdate();
+				event.getDelta().accept(this);
+			} catch (CoreException e) {
+				ModulecorePlugin.logError(e);
+			} finally {
+				postUpdate();
+			}
+		}
+
+		public boolean visit(IResourceDelta delta) throws CoreException {
+			IResource resource = delta.getResource();
+			switch (resource.getType()) {
+			case IResource.ROOT:
+				return true;
+			case IResource.PROJECT: {
+				int kind = delta.getKind();
+				if ((IResourceDelta.ADDED & kind) != 0) {
+					queueProjectAdded((IProject) resource);
+					return false;
+				} else if ((IResourceDelta.REMOVED & kind) != 0) {
+					queueProjectDeleted((IProject) resource);
+					return false;
+				} else if ((IResourceDelta.CHANGED & kind) != 0) {
+					int flags = delta.getFlags();
+					if ((IResourceDelta.OPEN & flags) != 0) {
+						boolean isOpen = ((IProject) resource).isOpen();
+						if (isOpen) {
+							queueProjectAdded((IProject) resource);
+						} else {
+							queueProjectDeleted((IProject) resource);
+						}
+						return false;
+					}
+					return true;
+				}
+				return false;
+			}
+			case IResource.FOLDER:
+				if (resource.getName().equals(".settings")) {
+					return true;
+				}
+				return false;
+			case IResource.FILE:
+				String name = resource.getName();
+				if (name.equals(WTPModulesResourceFactory.WTP_MODULES_SHORT_NAME)) {
+					update(resource.getProject());
+				}
+			default:
+				return false;
+			}
+		}
+	};
+
+	private DependencyGraphResourceChangedListener listener = null;
+
+	/**
+	 * The graph is built lazily once. Afterwards, the graph is updated as
+	 * necessary.
+	 */
+	private void initGraph() {
+		synchronized (graphLock) {
+			try {
+				preUpdate();
+				graph = new HashMap<IProject, Set<IProject>>();
+				listener = new DependencyGraphResourceChangedListener();
+				ResourcesPlugin.getWorkspace().addResourceChangeListener(listener, IResourceChangeEvent.POST_CHANGE);
+				IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+				for (IProject sourceProject : allProjects) {
+					queueProjectAdded(sourceProject);
+				}
+			} finally {
+				postUpdate();
+			}
+		}
+	}
+
+	private void removeAllReferences(IProject project) {
+		synchronized (graphLock) {
+			graph.remove(project);
+			for (Iterator<Set<IProject>> iterator = graph.values().iterator(); iterator.hasNext();) {
+				iterator.next().remove(project);
+			}
+			modStamp++;
+		}
+	}
+
+	private void removeReference(IProject sourceProject, IProject targetProject) {
+		synchronized (graphLock) {
+			Set<IProject> referencingProjects = graph.get(targetProject);
+			if (referencingProjects != null) {
+				referencingProjects.remove(sourceProject);
+			}
+			modStamp++;
+		}
+	}
+
+	private void addReference(IProject sourceProject, IProject targetProject) {
+		synchronized (graphLock) {
+			Set<IProject> referencingProjects = graph.get(targetProject);
+			if (referencingProjects == null) {
+				referencingProjects = new HashSet<IProject>();
+				graph.put(targetProject, referencingProjects);
+			}
+			referencingProjects.add(sourceProject);
+			modStamp++;
+		}
+	}
+
+	public static final Object GRAPH_UPDATE_JOB_FAMILY = new Object();
+
+	private static final int JOB_DELAY = 100;
+
+	private final GraphUpdateJob graphUpdateJob = new GraphUpdateJob();
+	private final Object jobLock = new Object();
+
+	private class GraphUpdateJob extends Job {
+
+		public GraphUpdateJob() {
+			super("Graph Update Job");
+			setSystem(true);
+			setRule(null);
+		}
+
+		public boolean belongsTo(Object family) {
+			if (family == GRAPH_UPDATE_JOB_FAMILY) {
+				return true;
+			}
+			return super.belongsTo(family);
+		}
+
+		// We use the listener list as a thread safe queue.
+		private class Queue extends ListenerList {
+			public synchronized Object[] getListeners() {
+				Object[] data = super.getListeners();
+				clear();
+				return data;
+			}
+
+			public synchronized boolean isEmpty() {
+				return super.isEmpty();
+			}
+		};
+
+		private Queue projectsAdded = new Queue();
+
+		private Queue projectsRemoved = new Queue();
+
+		private Queue projectsUpdated = new Queue();
+
+		public void queueProjectAdded(IProject project) {
+			synchronized (graphLock) {
+				modStamp++;
+			}
+			projectsAdded.add(project);
+		}
+
+		public void queueProjectDeleted(IProject project) {
+			synchronized (graphLock) {
+				modStamp++;
+			}
+			projectsRemoved.add(project);
+		}
+
+		public void queueProjectUpdated(IProject project) {
+			synchronized (graphLock) {
+				modStamp++;
+			}
+			projectsUpdated.add(project);
+		}
+
+		@Override
+		public boolean shouldSchedule() {
+			boolean isEmpty = projectsAdded.isEmpty() && projectsRemoved.isEmpty() && projectsUpdated.isEmpty();
+			return !isEmpty;
+		}
+
+		protected IStatus run(IProgressMonitor monitor) {
+			final Object[] removed = projectsRemoved.getListeners();
+			final Object[] updated = projectsUpdated.getListeners();
+			final Object[] added = projectsAdded.getListeners();
+			if (removed.length == 0 && updated.length == 0 && added.length == 0) {
+				return Status.OK_STATUS;
+			}
+			synchronized (graphLock) {
+				modStamp++;
+			}
+			SafeRunner.run(new ISafeRunnable() {
+				public void handleException(Throwable e) {
+					ModulecorePlugin.logError(e);
+				}
+
+				public void run() throws Exception {
+					// this is the simple case; just remove them all
+					synchronized (graphLock) {
+						for (Object o : removed) {
+							IProject project = (IProject) o;
+							removeAllReferences(project);
+						}
+					}
+					// get the updated queue in case there are any adds
+					// if there are any added projects, then unfortunately the
+					// entire workspace needs to be processed
+					if (added.length > 0) {
+						IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+						for (IProject sourceProject : allProjects) {
+							IVirtualComponent component = ComponentCore.createComponent(sourceProject);
+							if (component != null) {
+								IVirtualReference[] references = component.getReferences();
+								for (IVirtualReference ref : references) {
+									IVirtualComponent targetComponent = ref.getReferencedComponent();
+									if (targetComponent != null) {
+										IProject targetProject = targetComponent.getProject();
+										if (targetProject != null && !targetProject.equals(sourceProject)) {
+											addReference(sourceProject, targetProject);
+										}
+									}
+								}
+							}
+						}
+					} else if (updated.length > 0) {
+						IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+						Set<IProject> validRefs = new HashSet<IProject>();
+						for (Object o : updated) {
+							IProject sourceProject = (IProject) o;
+							IVirtualComponent component = ComponentCore.createComponent(sourceProject);
+							if (component != null) {
+								validRefs.clear();
+								IVirtualReference[] references = component.getReferences();
+								for (IVirtualReference ref : references) {
+									IVirtualComponent targetComponent = ref.getReferencedComponent();
+									if (targetComponent != null) {
+										IProject targetProject = targetComponent.getProject();
+										if (targetProject != null && !targetProject.equals(sourceProject)) {
+											validRefs.add(targetProject);
+										}
+									}
+								}
+								synchronized (graphLock) {
+									for (IProject targetProject : allProjects) {
+										// if the reference was identified
+										// above, be sure to add it
+										// otherwise, remove it
+										if (validRefs.remove(targetProject)) {
+											addReference(sourceProject, targetProject);
+										} else {
+											removeReference(sourceProject, targetProject);
+										}
+									}
+								}
+							} else {
+								// if this project is not a component, then it
+								// should be completely removed.
+								removeAllReferences(sourceProject);
+							}
+						}
+					}
+				}
+			});
+			// System.err.println(IDependencyGraph.INSTANCE);
+			return Status.OK_STATUS;
+		}
+	};
+
+	public void queueProjectAdded(IProject project) {
+		graphUpdateJob.queueProjectAdded(project);
+		synchronized (jobLock) {
+			if (pauseCount > 0) {
+				return;
+			}
+		}
+		graphUpdateJob.schedule(JOB_DELAY);
+	}
+
+	public void queueProjectDeleted(IProject project) {
+		graphUpdateJob.queueProjectDeleted(project);
+		synchronized (jobLock) {
+			if (pauseCount > 0) {
+				return;
+			}
+		}
+		graphUpdateJob.schedule(JOB_DELAY);
+	}
+
+	public void update(IProject project) {
+		graphUpdateJob.queueProjectUpdated(project);
+		synchronized (jobLock) {
+			if (pauseCount > 0) {
+				return;
+			}
+		}
+		graphUpdateJob.schedule(JOB_DELAY);
+	}
+
+	private int pauseCount = 0;
+
+	/**
+	 * Pauses updates; any caller of this method must ensure through a
+	 * try/finally block that resumeUpdates is subsequently called.
+	 */
+	public void preUpdate() {
+		synchronized (jobLock) {
+			pauseCount++;
+		}
+	}
+
+	public void postUpdate() {
+		synchronized (jobLock) {
+			if (pauseCount > 0) {
+				pauseCount--;
+			}
+			if (pauseCount > 0) {
+				return;
+			}
+		}
+		graphUpdateJob.schedule(JOB_DELAY);
+	}
+
+	/**
+	 * Blocks until the graph is finished updating
+	 */
+	public void waitForAllUpdates(IProgressMonitor monitor) {
+		try {
+			graphUpdateJob.schedule(0);
+			Job.getJobManager().join(GRAPH_UPDATE_JOB_FAMILY, monitor);
+		} catch (OperationCanceledException e) {
+			ModulecorePlugin.logError(e);
+		} catch (InterruptedException e) {
+			ModulecorePlugin.logError(e);
+		}
+	}
+
+	public String toString() {
+		synchronized (graphLock) {
+			StringBuffer buff = new StringBuffer("Dependency Graph:\n{\n");
+			for (Iterator<Map.Entry<IProject, Set<IProject>>> iterator = graph.entrySet().iterator(); iterator.hasNext();) {
+				Map.Entry<IProject, Set<IProject>> entry = iterator.next();
+				buff.append("  " + entry.getKey().getName() + " -> {");
+				for (Iterator<IProject> mappedProjects = entry.getValue().iterator(); mappedProjects.hasNext();) {
+					buff.append(mappedProjects.next().getName());
+					if (mappedProjects.hasNext()) {
+						buff.append(", ");
+					}
+				}
+				buff.append("}\n");
+			}
+			buff.append("}");
+			return buff.toString();
+		}
+
+	}
+
+}
diff --git a/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraphManager.java b/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraphManager.java
index 3d95164..0b275c8 100644
--- a/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraphManager.java
+++ b/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/DependencyGraphManager.java
@@ -10,264 +10,46 @@
  *******************************************************************************/
 package org.eclipse.wst.common.componentcore.internal.builder;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
-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.Path;
-import org.eclipse.jem.util.emf.workbench.ProjectUtilities;
-import org.eclipse.jem.util.logger.proxy.Logger;
-import org.eclipse.wst.common.componentcore.ComponentCore;
-import org.eclipse.wst.common.componentcore.internal.resources.VirtualArchiveComponent;
-import org.eclipse.wst.common.componentcore.internal.util.ComponentUtilities;
-import org.eclipse.wst.common.componentcore.internal.util.IModuleConstants;
-import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
-import org.eclipse.wst.common.componentcore.resources.IVirtualReference;
 
+/**
+ * @deprecated use {@link IDependencyGraph}
+ */
 public class DependencyGraphManager {
-	
-	private class DependencyReference {
-		
-		public IProject componentProject;
-		public IProject targetProject;
-		
-		public DependencyReference(IProject target, IProject component) {
-			super();
-			componentProject=component;
-			targetProject=target;
-		}
-	}
 
 	private static DependencyGraphManager INSTANCE = null;
-	private static final String MANIFEST_URI = "META-INF/MANIFEST.MF";
-	private HashMap wtpModuleTimeStamps = null;
-	private HashMap manifestTimeStamps = null;
-	private long modStamp = System.currentTimeMillis();
-	
+
 	private DependencyGraphManager() {
 		super();
 	}
-	
+
 	public synchronized static final DependencyGraphManager getInstance() {
 		if (INSTANCE == null)
 			INSTANCE = new DependencyGraphManager();
 		return INSTANCE;
 	}
-	
+
 	public void construct(IProject project) {
-		if (project!=null && project.isAccessible() && getComponentFile(project) !=null) //$NON-NLS-1$
-			constructIfNecessary();
-	}
-
-	private IResource getComponentFile(IProject project) {
-		IResource componentFile = project.findMember(IModuleConstants.COMPONENT_FILE_PATH);
-		if (componentFile == null)
-			componentFile = project.findMember(IModuleConstants.R1_MODULE_META_FILE_PATH);
-		return componentFile;
-		
-	}
-	
-	private void constructIfNecessary() {
-		if (metadataChanged()) {
-			buildDependencyGraph();
-		}
-	}
-
-	private boolean metadataChanged() {
-		return moduleTimeStampsChanged() || manifestTimeStampsChanged();
-	}
-	
-	private boolean manifestTimeStampsChanged() {
-		HashMap workspaceTimeStamps = collectManifestTimeStamps();
-		if (timestampsMatch(getManifestTimeStamps(),workspaceTimeStamps))
-			return false;
-		return true;
-	}
-
-	private HashMap getManifestTimeStamps() {
-		if (manifestTimeStamps == null)
-			manifestTimeStamps = new HashMap();
-		return manifestTimeStamps;
-	}
-
-	private HashMap collectManifestTimeStamps() {
-		HashMap timeStamps = new HashMap();
-		IProject[] projects = ProjectUtilities.getAllProjects();
-		for (int i=0; i<projects.length; i++) {
-			IFile manifestFile = null;
-			if (projects[i]==null || !projects[i].isAccessible())
-				continue;
-			manifestFile = getTimeStampFile(projects[i]);
-			if (manifestFile != null && manifestFile.exists() && ComponentCore.createComponent(projects[i]) != null) {
-				Long currentTimeStamp = new Long(manifestFile.getModificationStamp());
-				timeStamps.put(projects[i],currentTimeStamp);
-			}
-		}
-		return timeStamps;
-	}
-	
-	/**
-	 * This returns the file used for time stamp purposes.  Typically this will be the manifest file.
-	 * @param p
-	 * @return
-	 */
-	private IFile getTimeStampFile(IProject p) {
-		IVirtualComponent component = ComponentCore.createComponent(p);
-		if(null == component){
-			return null;
-		}
-		if(component.isBinary()){
-			return ((VirtualArchiveComponent)component).getUnderlyingWorkbenchFile();
-		} else {
-			try {
-				IFile file = ComponentUtilities.findFile(component, new Path(MANIFEST_URI));
-				if (file != null)
-					return file;
-			} catch (CoreException ce) {
-				Logger.getLogger().log(ce);
-			}
-		}
-		return null;
-	}
-
-	private boolean moduleTimeStampsChanged() {
-		HashMap workspaceTimeStamps = collectModuleTimeStamps();
-		if (timestampsMatch(getWtpModuleTimeStamps(),workspaceTimeStamps))
-			return false;
-		return true;
-	}
-
-	private boolean timestampsMatch(HashMap savedTimeStamps, HashMap workspaceTimeStamps) {
-		return savedTimeStamps.equals(workspaceTimeStamps);
-		
-	}
-	
-	private HashMap collectModuleTimeStamps() {
-		HashMap timeStamps = new HashMap();
-		IProject[] projects = ProjectUtilities.getAllProjects();
-		for (int i=0; i<projects.length; i++) {
-			if (projects[i]==null || !projects[i].isAccessible())
-				continue;
-			IResource wtpModulesFile = getComponentFile(projects[i]); //$NON-NLS-1$
-			if (wtpModulesFile != null && wtpModulesFile.exists() && ComponentCore.createComponent(projects[i]) != null) {
-				Long currentTimeStamp = new Long(wtpModulesFile.getModificationStamp());
-				timeStamps.put(projects[i],currentTimeStamp);
-			}
-		}
-		return timeStamps;
-	}
-	
-	private void buildDependencyGraph() {
-		// Process and collect dependency references to add
-		List referencesToAdd = new ArrayList();
-		List componentProjects = new ArrayList();
-		IProject[] projects = ProjectUtilities.getAllProjects();
-		
-		for (int k=0; k<projects.length; k++) {
-			if (!projects[k].isAccessible() || getComponentFile(projects[k])==null) 
-				continue;
-			IVirtualComponent component= ComponentCore.createComponent(projects[k]);
-			if (component == null) continue;
-			referencesToAdd.addAll(getDependencyReferences(component));
-			componentProjects.add(projects[k]);
-		}
-		
-		//Update the actual graph/timestamps and block other threads here
-		synchronized (this) {
-			cleanDependencyGraph();
-			for (Iterator iter = componentProjects.iterator(); iter.hasNext();) {
-				IProject proj = (IProject) iter.next();
-				//For All projects (regardless if involved in references), update timestamps
-				addTimeStamp(proj);
-			}
-			for (int i=0; i<referencesToAdd.size(); i++) {
-				DependencyReference ref = (DependencyReference) referencesToAdd.get(i);
-				if (ref.targetProject == null || ref.componentProject == null || ref.targetProject.equals(ref.componentProject))
-					continue;
-				DependencyGraph.getInstance().addReference(ref.targetProject,ref.componentProject);
-			}
-		}
-	}
-	
-	private List getDependencyReferences(IVirtualComponent component) {
-		List refs = new ArrayList();
-		IProject componentProject = component.getProject();
-		IVirtualReference[] depRefs = component.getReferences();
-		for(int i = 0; i<depRefs.length; i++){
-			IVirtualComponent targetComponent = depRefs[i].getReferencedComponent();
-			if (targetComponent!=null) {
-				IProject targetProject = targetComponent.getProject();
-				refs.add(new DependencyReference(targetProject,componentProject));
-			}	
-		}
-		return refs;
-	}
-	
-	private boolean addTimeStamp(IProject project) {
-		// Get the .component file for the given project
-		IResource wtpModulesFile = getComponentFile(project);
-		if (wtpModulesFile==null)
-			return false;
-		Long currentTimeStamp = new Long(wtpModulesFile.getModificationStamp());
-		getWtpModuleTimeStamps().put(project,currentTimeStamp);
-		//		 Get the MANIFEST file for the given project
-		IResource manifestFile = getTimeStampFile(project);
-
-		if (manifestFile==null)
-			return false;
-		currentTimeStamp = new Long(manifestFile.getModificationStamp());
-		getManifestTimeStamps().put(project,currentTimeStamp);
-		return true;
-	}
-	
-	private void cleanDependencyGraph() {
-		DependencyGraph.getInstance().clear();
-		getWtpModuleTimeStamps().clear();
-		getManifestTimeStamps().clear();
-		setModStamp(System.currentTimeMillis());
+		// do nothing
 	}
 
 	/**
-	 * Lazy initialization and return of the key valued pair of projects and wtp modules file
-	 * timestamps.
-	 * 
-	 * @return HashMap of projects to .component file stamps
-	 */
-	private HashMap getWtpModuleTimeStamps() {
-		if (wtpModuleTimeStamps == null)
-			wtpModuleTimeStamps = new HashMap();
-		return wtpModuleTimeStamps;
-	}
-	
-	/**
-	 * Return the dependency graph which was initialized if need be in the 
+	 * Return the dependency graph which was initialized if need be in the
 	 * singleton manager method.
-	 */ 
+	 */
 	public DependencyGraph getDependencyGraph() {
-		constructIfNecessary();
 		return DependencyGraph.getInstance();
 	}
-	
+
 	public void forceRefresh() {
-		buildDependencyGraph();
+		// do nothing
 	}
 
-	
 	public long getModStamp() {
-		return modStamp;
-	}
-
-	
-	private void setModStamp(long modStamp) {
-		this.modStamp = modStamp;
+		return IDependencyGraph.INSTANCE.getModStamp();
 	}
 
 	public boolean checkIfStillValid(long timeStamp) {
-		return (getModStamp() == timeStamp && !metadataChanged());
+		return IDependencyGraph.INSTANCE.getModStamp() == timeStamp;
 	}
 }
diff --git a/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/IDependencyGraph.java b/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/IDependencyGraph.java
new file mode 100644
index 0000000..1396949
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.modulecore/modulecore-src/org/eclipse/wst/common/componentcore/internal/builder/IDependencyGraph.java
@@ -0,0 +1,106 @@
+package org.eclipse.wst.common.componentcore.internal.builder;
+
+import java.util.Set;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
+
+/**
+ * This graph provides a backward mapping of project component dependencies. It
+ * provides a project limited inverse of
+ * {@link IVirtualComponent#getReferences()}.
+ * 
+ * For example:
+ * <ul>
+ * <li>if the IVirtualComponent for project A has a dependency on the
+ * IVirtualComponent for project B, then calling
+ * {@link #getReferencingComponents(IProject)} on project B will return project
+ * A. </li>
+ * <li>if the IVirtualComponent for project A has a dependency on on the
+ * IVirtualComponent for a jar in project B, then calling
+ * {@link #getReferencingComponents(IProject)} for project B will return project
+ * A. This is true even if project B is not defined as an IVirtualComponent.
+ * </li>
+ * </ul>
+ * 
+ * Any call to {@link #getReferencingComponents(IProject)} is always expected to
+ * be up to date. The only case where a client may need to force an update is if
+ * that client is also defining dynamic IVirtualComponent dependencies, i.e. the
+ * client is using the org.eclipse.wst.common.modulecore.componentimpl extension
+ * point. Only in this case should a client be calling any of
+ * {@link #preUpdate()}, {@link #postUpdate()}, or {@link #update(IProject)}
+ * 
+ */
+public interface IDependencyGraph {
+
+	/**
+	 * The static instance of this graph
+	 */
+	public static IDependencyGraph INSTANCE = DependencyGraphImpl.getInstance();
+
+	/**
+	 * Returns the set of component projects referencing the specified target
+	 * project.
+	 * 
+	 * @param targetProject
+	 * @return
+	 */
+	public Set<IProject> getReferencingComponents(IProject targetProject);
+	
+	/**
+	 * Returns a modification stamp.  This modification stamp will be different
+	 * if the project dependencies ever change.
+	 */
+	public long getModStamp();
+	
+	/**
+	 * WARNING: this should only be called by implementors of the
+	 * org.eclipse.wst.common.modulecore.componentimpl extension point.
+	 * 
+	 * This method is part of the update API.
+	 * 
+	 * @see {@link #update(IProject)}
+	 */
+	public void preUpdate();
+
+	/**
+	 * WARNING: this should only be called by implementors of the
+	 * org.eclipse.wst.common.modulecore.componentimpl extension point.
+	 * 
+	 * This method is part of the update API.
+	 * 
+	 * @see {@link #update(IProject)}
+	 */
+	public void postUpdate();
+
+	/**
+	 * WARNING: this should only be called by implementors of the
+	 * org.eclipse.wst.common.modulecore.componentimpl extension point.
+	 * 
+	 * This method must be called when a resource change is detected which will
+	 * affect how dependencies behave. For example, the core IVirtualComponent
+	 * framework updates when changes are made to the
+	 * .settings/org.eclipse.wst.common.component file changes, and also when
+	 * IProjects are added or removed from the workspace. In the case for J2EE,
+	 * this occurs when changes are made to the META-INF/MANIFEST.MF file. In
+	 * general a call to update should only be made from a fast
+	 * {@link IResourceDeltaVisitor}.
+	 * 
+	 * In order to improve efficiency and avoid unnecessary update processing,
+	 * it is necessary to always proceed calls to update() with a call to
+	 * preUpdate() and follow with a call to postUpdate() using a try finally
+	 * block as follows: <code>
+	 * try {
+	 *     preUpdate();
+	 *     // perform 0 or more update() calls here
+	 * } finally {
+	 *     IDependencyGraph.INSTANCE.postUpdate();
+	 * }    
+	 * </code>
+	 * 
+	 * 
+	 */
+	public void update(IProject sourceProject);
+
+}