blob: c57070790eb2802ac23941f18123ae3a97a33b2b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 University of Illinois at Urbana-Champaign 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:
* UIUC - Initial API and implementation
*******************************************************************************/
package org.eclipse.rephraserengine.core.vpg.eclipse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IFile;
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.IResourceVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.rephraserengine.core.vpg.IVPGNode;
import org.eclipse.rephraserengine.core.vpg.VPG;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchListener;
import org.eclipse.ui.internal.Workbench;
/**
* A virtual program graph for use in an Eclipse environment.
* <a href="../../../../overview-summary.html#Eclipse">More Information</a>
* <p>
* This class is intended to be subclassed directly.
*
* @author Jeff Overbey
*/
@SuppressWarnings("restriction")
public abstract class EclipseVPG<A, T, R extends IVPGNode<T>>
extends VPG<A, T, R>
{
private String syncMessage;
public EclipseVPG(IEclipseVPGComponentFactory<A, T, R> locator, String syncMessage, int transientASTCacheSize)
{
super(locator, transientASTCacheSize);
this.syncMessage = syncMessage;
}
public EclipseVPG(IEclipseVPGComponentFactory<A, T, R> locator, String syncMessage)
{
super(locator);
this.syncMessage = syncMessage;
}
/** Enqueues a job to make sure the VPG is up-to-date with the
* workspace, and instructs it to automatically update itself
* when files are added, changed, or deleted in the workspace.
*/
public void start()
{
ensureVPGClosedOnWorkbenchShutdown();
getLog().readLogFromFile();
// Now listen for changes to workspace resources
ResourcesPlugin.getWorkspace().addResourceChangeListener(new VPGResourceChangeListener(), IResourceChangeEvent.POST_CHANGE);
// The C/C++ Development Tool (see org.eclipse.cdt.internal.core.model.CModelManager) handles
// IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.PRE_CLOSE
// and also executes
// Platform.getContentTypeManager().addContentTypeChangeListener(this);
queueJobToEnsureVPGIsUpToDate();
}
/** Ensures that the database is closed and flushed when the workbench shuts down */
private void ensureVPGClosedOnWorkbenchShutdown()
{
Workbench.getInstance().addWorkbenchListener(new IWorkbenchListener()
{
public boolean preShutdown(IWorkbench workbench, boolean forced)
{
FlushDatabaseJob job = new FlushDatabaseJob();
job.setRule(
MultiRule.combine(
VPGSchedulingRule.getInstance(),
ResourcesPlugin.getWorkspace().getRoot()));
job.schedule();
return true;
}
public void postShutdown(IWorkbench workbench)
{
}
});
}
private final class FlushDatabaseJob extends WorkspaceJob
{
private FlushDatabaseJob()
{
super(Messages.bind(Messages.EclipseVPG_WritingDatabaseToDisk, syncMessage));
}
@Override
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException
{
try
{
flushDatabase();
getLog().writeToFile();
return Status.OK_STATUS;
}
catch (Throwable e)
{
return new Status(IStatus.ERROR, null, e.getMessage(), e);
}
}
}
///////////////////////////////////////////////////////////////////////////
// Resource Visitor
///////////////////////////////////////////////////////////////////////////
public WorkspaceJob queueJobToEnsureVPGIsUpToDate()
{
WorkspaceJob job = new VPGInitialWorkspaceSyncJob(syncMessage);
job.setRule(MultiRule.combine(VPGSchedulingRule.getInstance(),
ResourcesPlugin.getWorkspace().getRoot()));
job.schedule();
return job;
}
private final class VPGInitialWorkspaceSyncJob extends VPGJob<A, T>
{
private VPGInitialWorkspaceSyncJob(String name)
{
super(name);
}
@Override public IStatus runInWorkspace(final IProgressMonitor monitor)
{
try
{
monitor.beginTask(Messages.EclipseVPG_Indexing, IProgressMonitor.UNKNOWN);
return ensureVPGIsUpToDate(monitor);
}
finally
{
monitor.done();
}
}
}
/** Ensures that the VPG database contains up-to-date information for all of
* the resources in the workspace.
*/
public IStatus ensureVPGIsUpToDate(IProgressMonitor monitor)
{
try
{
long start = System.currentTimeMillis();
WorkspaceSyncResourceVisitor visitor = new WorkspaceSyncResourceVisitor();
collectFilesToIndex(visitor, monitor);
visitor.calculateDependencies(monitor);
visitor.index(monitor);
long end = System.currentTimeMillis();
debug("Total time in #ensureVPGIsUpToDate: " + (end-start) + " ms", null); //$NON-NLS-1$ //$NON-NLS-2$
return Status.OK_STATUS;
}
catch (CoreException e)
{
return e.getStatus();
}
}
private void collectFilesToIndex(WorkspaceSyncResourceVisitor visitor, IProgressMonitor monitor) throws CoreException
{
monitor.subTask(Messages.EclipseVPG_SearchingForWorkspaceModifications);
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
workspaceRoot.accept(visitor); // Collect list of files to index
}
// private EclipseVPGWriter<A, T, R> getBuilder()
// {
// return (EclipseVPGWriter<A, T, R>)getBuilder();
// }
private final class WorkspaceSyncResourceVisitor implements IResourceVisitor
{
private final ArrayList<String> files;
private WorkspaceSyncResourceVisitor()
{
this.files = new ArrayList<String>(1024);
}
public boolean visit(IResource resource)
{
try
{
if (!resource.isAccessible()) return false;
if (resource instanceof IProject && !shouldProcessProject((IProject)resource)) return false;
if (resource instanceof IFile && shouldProcessFile((IFile)resource))
files.add(EclipseVPG.getFilenameForIFile((IFile)resource));
}
catch (Exception e)
{
getLog().logError(e);
}
return !(resource instanceof IFile);
}
public void calculateDependencies(IProgressMonitor monitor)
{
ArrayList<String> queue = files;
int completed = 0, total = queue.size();
for (String filename : queue)
{
if (monitor.isCanceled()) throw new OperationCanceledException();
monitor.subTask(
filename + " " + //$NON-NLS-1$
Messages.bind(Messages.EclipseVPG_CalculatingDependencies,
++completed,
total));
calculateDependenciesIfNotUpToDate(filename);
}
}
public void index(IProgressMonitor monitor)
{
List<String> queue = sortFilesAccordingToDependencies(files); //, monitor);
int completed = 0, total = countFilesInQueue(queue);
for (String filename : queue)
{
if (monitor.isCanceled()) throw new OperationCanceledException();
if (shouldListFileInIndexerProgressMessages(filename))
monitor.subTask(filename + " " + //$NON-NLS-1$
Messages.bind(Messages.EclipseVPG_XofY,
++completed,
total));
indexIfNotUpToDate(filename);
}
}
}
private int countFilesInQueue(List<String> queue)
{
int total = 0;
for (String filename : queue)
if (shouldListFileInIndexerProgressMessages(filename))
total++;
return total;
}
protected boolean shouldListFileInIndexerProgressMessages(String filename)
{
return !isVirtualFile(filename);
}
//public ArrayList<String> sortFilesAccordingToDependencies(final ArrayList<String> files, final IProgressMonitor monitor)
///////////////////////////////////////////////////////////////////////////
// Resource Change Listener
///////////////////////////////////////////////////////////////////////////
private final class VPGResourceChangeListener implements IResourceChangeListener
{
public void resourceChanged(IResourceChangeEvent event)
{
if (!(event.getSource() instanceof IWorkspace)) return;
if (event.getType() != IResourceChangeEvent.POST_CHANGE) return;
final IResourceDelta delta = event.getDelta();
if (delta == null) return;
new VPGResourceDeltaJob(syncMessage, delta).schedule();
}
}
private final class VPGResourceDeltaJob extends VPGJob<A, T>
{
private final IResourceDelta delta;
private VPGResourceDeltaJob(String name, IResourceDelta delta)
{
super(name);
this.delta = delta;
}
@Override public IStatus runInWorkspace(final IProgressMonitor monitor)
{
try
{
monitor.beginTask(Messages.EclipseVPG_Indexing, IProgressMonitor.UNKNOWN);
long start = System.currentTimeMillis();
// Re-index or delete entries for files when they are added/changed or deleted, respectively
VPGResourceDeltaVisitor visitor = new VPGResourceDeltaVisitor();
monitor.subTask(Messages.EclipseVPG_SearchingForWorkspaceModifications);
delta.accept(visitor); // Collect files to index
visitor.calculateDependencies(monitor);
visitor.index(monitor);
long end = System.currentTimeMillis();
debug("Total time indexing resource delta: " + (end-start) + " ms", null); //$NON-NLS-1$ //$NON-NLS-2$
return Status.OK_STATUS;
}
catch (CoreException e)
{
return e.getStatus();
}
finally
{
monitor.done();
}
}
}
private final class VPGResourceDeltaVisitor implements IResourceDeltaVisitor
{
private ArrayList<String> files;
private HashMap<String, Boolean> forceReindex;
public VPGResourceDeltaVisitor()
{
this.files = new ArrayList<String>(256);
this.forceReindex = new HashMap<String, Boolean>(256);
}
public boolean visit(IResourceDelta delta)
{
try
{
IResource resource = delta.getResource();
if (!(resource instanceof IFile))
{
if (resource instanceof IProject)
return shouldProcessProject((IProject)resource);
else
return true;
}
IFile file = (IFile)resource;
if (!shouldProcessFile(file)) return true;
String filename = getFilenameForIFile(file);
switch (delta.getKind())
{
case IResourceDelta.ADDED:
debug("Resource Delta: Add", filename); //$NON-NLS-1$
files.add(filename);
forceReindex.put(filename, true); // Was false
break;
case IResourceDelta.CHANGED:
debug("Resource Delta: Change", filename); //$NON-NLS-1$
if ((delta.getFlags() & (IResourceDelta.CONTENT|IResourceDelta.REPLACED)) != 0)
{
files.add(filename);
forceReindex.put(filename, true);
}
break;
case IResourceDelta.REMOVED:
debug("Resource Delta: Remove", filename); //$NON-NLS-1$
deleteAllEntriesFor(filename);
break;
}
}
catch (Exception e)
{
getLog().logError(e);
}
return true;
}
public void calculateDependencies(IProgressMonitor monitor)
{
ArrayList<String> queue = files;
int completed = 0, total = queue.size();
for (String filename : queue)
{
if (monitor.isCanceled()) throw new OperationCanceledException();
monitor.subTask(filename + " " + //$NON-NLS-1$
Messages.bind(Messages.EclipseVPG_XofY,
++completed,
total));
Boolean force = forceReindex.get(filename);
if (force == null || force)
forceRecomputationOfDependencies(filename);
else
calculateDependenciesIfNotUpToDate(filename);
}
}
public void index(IProgressMonitor monitor)
{
List<String> queue = sortFilesAccordingToDependencies(files); //, monitor);
int completed = 0, total = countFilesInQueue(queue);
for (String filename : queue)
{
if (monitor.isCanceled()) throw new OperationCanceledException();
if (shouldListFileInIndexerProgressMessages(filename))
monitor.subTask(filename + " " + //$NON-NLS-1$
Messages.bind(Messages.EclipseVPG_XofY,
++completed,
total));
Boolean force = forceReindex.get(filename);
if (force == null || force)
forceRecomputationOfEdgesAndAnnotations(filename);
else
indexIfNotUpToDate(filename);
}
}
}
/** Updates the VPG database's dependency information for the given file
* if the stored information is out of date
* @param monitor */
protected void calculateDependenciesIfNotUpToDate(String filename)
{
if (isOutOfDate(filename))
{
debug(Messages.EclipseVPG_Indexing, filename);
forceRecomputationOfDependencies(filename);
}
else
{
debug(Messages.EclipseVPG_IndexIsUpToDate, filename);
}
}
/** Updates the VPG database information for the given file if the stored
* information is out of date
* @param monitor */
protected void indexIfNotUpToDate(String filename)
{
if (isOutOfDate(filename))
{
debug(Messages.EclipseVPG_Indexing, filename);
forceRecomputationOfEdgesAndAnnotations(filename);
}
else
{
debug(Messages.EclipseVPG_IndexIsUpToDate, filename);
}
}
///////////////////////////////////////////////////////////////////////////
// Utility Methods (IFile<->Filename Mapping)
///////////////////////////////////////////////////////////////////////////
public static IFile getIFileForFilename(String filename)
{
return ResourceUtil.getIFileForFilename(filename);
}
public static String getFilenameForIFile(IFile file)
{
return ResourceUtil.getFilenameForIFile(file);
}
public static IResource getIResourceForFilename(String filename)
{
return ResourceUtil.getIResourceForFilename(filename);
}
public static String getFilenameForIResource(IResource resource)
{
return ResourceUtil.getFilenameForIResource(resource);
}
///////////////////////////////////////////////////////////////////////////
// IFile-Based AST Acquisition & Release API
///////////////////////////////////////////////////////////////////////////
public A acquireTransientAST(IFile file)
{
return file == null ? null : acquireTransientAST(getFilenameForIFile(file));
}
public A acquirePermanentAST(IFile file)
{
return file == null ? null : acquirePermanentAST(getFilenameForIFile(file));
}
public A makeTransientASTPermanent(IFile file)
{
return file == null ? null : makeTransientASTPermanent(getFilenameForIFile(file));
}
public A makeTransientASTPermanent(IFile file, A ast)
{
return file == null ? null : makeTransientASTPermanent(getFilenameForIFile(file), ast);
}
public void releaseAST(IFile file)
{
if (file != null) releaseAST(getFilenameForIFile(file));
}
public IFile getIFileCorrespondingTo(A ast)
{
String filename = getFilenameCorrespondingTo(ast);
return filename == null ? null : getIFileForFilename(filename);
}
///////////////////////////////////////////////////////////////////////////
// VPG Overrides
///////////////////////////////////////////////////////////////////////////
/** Forces the database to be updated based on the current in-memory AST for the given file */
public void commitChangesFromInMemoryASTs(IProgressMonitor pm, int ticks, IFile file)
{
commitChangesFromInMemoryASTs(pm, ticks, getFilenameForIFile(file));
}
/**
* Forces the database to be updated based on the current in-memory ASTs for the given files
*/
public void commitChangesFromInMemoryASTs(IProgressMonitor pm, int ticks, Set<IFile> files)
{
String[] filenames = new String[files.size()];
int i = 0;
for (IFile file : files)
filenames[i++] = getFilenameForIFile(file);
commitChangesFromInMemoryASTs(pm, ticks, filenames);
}
///////////////////////////////////////////////////////////////////////////
// Abstract Methods (Resource Filtering)
///////////////////////////////////////////////////////////////////////////
/** @return true iff the given file should be parsed */
@Override public boolean shouldProcessFile(String filename)
{
IFile file = EclipseVPG.getIFileForFilename(filename);
return file == null ? false : shouldProcessFile(file);
}
/** @return true if the given project should be indexed */
public abstract boolean shouldProcessProject(IProject project);
/** @return true iff the given file should be indexed */
public abstract boolean shouldProcessFile(IFile file);
}