package org.rubypeople.rdt.internal.core;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
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.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.PerformanceStats;
import org.eclipse.core.runtime.SafeRunner;
import org.rubypeople.rdt.core.ElementChangedEvent;
import org.rubypeople.rdt.core.IElementChangedListener;
import org.rubypeople.rdt.core.ILoadpathEntry;
import org.rubypeople.rdt.core.IRubyElement;
import org.rubypeople.rdt.core.IRubyElementDelta;
import org.rubypeople.rdt.core.IRubyModel;
import org.rubypeople.rdt.core.IRubyProject;
import org.rubypeople.rdt.core.IRubyScript;
import org.rubypeople.rdt.core.ISourceFolder;
import org.rubypeople.rdt.core.ISourceFolderRoot;
import org.rubypeople.rdt.core.RubyCore;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.internal.core.builder.RubyBuilder;
import org.rubypeople.rdt.internal.core.hierarchy.TypeHierarchy;
import org.rubypeople.rdt.internal.core.search.AbstractSearchScope;
import org.rubypeople.rdt.internal.core.search.RubyWorkspaceScope;
import org.rubypeople.rdt.internal.core.search.indexing.IndexManager;
import org.rubypeople.rdt.internal.core.util.CharOperation;
import org.rubypeople.rdt.internal.core.util.Util;
public class DeltaProcessor
{
static class RootInfo
{
char[][] inclusionPatterns;
char[][] exclusionPatterns;
RubyProject project;
IPath rootPath;
int entryKind;
ISourceFolderRoot root;
RootInfo(RubyProject project, IPath rootPath, char[][] inclusionPatterns, char[][] exclusionPatterns,
int entryKind)
{
this.project = project;
this.rootPath = rootPath;
this.inclusionPatterns = inclusionPatterns;
this.exclusionPatterns = exclusionPatterns;
this.entryKind = entryKind;
}
ISourceFolderRoot getSourceFolderRoot(IResource resource)
{
if (this.root == null)
{
if (resource != null)
{
this.root = this.project.getSourceFolderRoot(resource);
}
else
{
Object target = RubyModel.getTarget(this.rootPath, false/* don't check existence */);
if (target instanceof IResource)
{
this.root = this.project.getSourceFolderRoot((IResource) target);
}
else
{
this.root = this.project.getSourceFolderRoot(this.rootPath.toOSString());
}
}
}
return this.root;
}
boolean isRootOfProject(IPath path)
{
return this.rootPath.equals(path) && this.project.getProject().getFullPath().isPrefixOf(path);
}
public String toString()
{
StringBuffer buffer = new StringBuffer("project="); //$NON-NLS-1$
if (this.project == null)
{
buffer.append("null"); //$NON-NLS-1$
}
else
{
buffer.append(this.project.getElementName());
}
buffer.append("\npath="); //$NON-NLS-1$
if (this.rootPath == null)
{
buffer.append("null"); //$NON-NLS-1$
}
else
{
buffer.append(this.rootPath.toString());
}
buffer.append("\nincluding="); //$NON-NLS-1$
if (this.inclusionPatterns == null)
{
buffer.append("null"); //$NON-NLS-1$
}
else
{
for (int i = 0, length = this.inclusionPatterns.length; i < length; i++)
{
buffer.append(new String(this.inclusionPatterns[i]));
if (i < length - 1)
{
buffer.append("|"); //$NON-NLS-1$
}
}
}
buffer.append("\nexcluding="); //$NON-NLS-1$
if (this.exclusionPatterns == null)
{
buffer.append("null"); //$NON-NLS-1$
}
else
{
for (int i = 0, length = this.exclusionPatterns.length; i < length; i++)
{
buffer.append(new String(this.exclusionPatterns[i]));
if (i < length - 1)
{
buffer.append("|"); //$NON-NLS-1$
}
}
}
return buffer.toString();
}
}
private final static String EXTERNAL_JAR_ADDED = "external jar added"; //$NON-NLS-1$
private final static String EXTERNAL_JAR_CHANGED = "external jar changed"; //$NON-NLS-1$
private final static String EXTERNAL_JAR_REMOVED = "external jar removed"; //$NON-NLS-1$
private final static String EXTERNAL_JAR_UNCHANGED = "external jar unchanged"; //$NON-NLS-1$
private final static String INTERNAL_JAR_IGNORE = "internal jar ignore"; //$NON-NLS-1$
public static final int DEFAULT_CHANGE_EVENT = 0; // must not collide with
private final static int NON_RUBY_RESOURCE = -1;
// ElementChangedEvent
// event masks
public static boolean DEBUG;
public static boolean VERBOSE = false;
public static boolean PERF = false;
/*
* Used to update the RubyModel for <code>IRubyElementDelta</code>s.
*/
private final ModelUpdater modelUpdater = new ModelUpdater();
/* A set of IRubyProject whose caches need to be reset */
private HashSet<IRubyProject> projectCachesToReset = new HashSet<IRubyProject>();
/*
* A table from IRubyProject to an array of ISourceFolderRoot. This table contains the src folder roots of the
* project that are being deleted.
*/
public Map<IRubyProject, ISourceFolderRoot[]> removedRoots;
/*
* A list of IRubyElement used as a scope for external archives refresh during POST_CHANGE. This is null if no
* refresh is needed.
*/
private HashSet<IRubyElement> refreshedElements;
private DeltaProcessingState state;
private RubyModelManager manager;
/*
* Turns delta firing on/off. By default it is on.
*/
private boolean isFiring = true;
/*
* Queue of deltas created explicily by the Ruby Model that have yet to be fired.
*/
public ArrayList<IRubyElementDelta> rubyModelDeltas = new ArrayList<IRubyElementDelta>();
/*
* Queue of reconcile deltas on working copies that have yet to be fired. This is a table from IWorkingCopy to
* IRubyElementDelta
*/
public HashMap<IRubyScript, IRubyElementDelta> reconcileDeltas = new HashMap<IRubyScript, IRubyElementDelta>();
/*
* The ruby element that was last created (see createElement(IResource)). This is used as a stack of ruby elements
* (using getParent() to pop it, and using the various get*(...) to push it.
*/
private Openable currentElement;
/* A set of IRubyProject whose source folder roots need to be refreshed */
private HashSet<IRubyProject> rootsToRefresh = new HashSet<IRubyProject>();
/*
* The <code>RubyElementDelta</code> corresponding to the <code>IResourceDelta</code> being translated.
*/
private RubyElementDelta currentDelta;
/*
* Type of event that should be processed no matter what the real event type is.
*/
public int overridenEventType = -1;
private SourceElementParser sourceElementParserCache;
public DeltaProcessor(DeltaProcessingState state, RubyModelManager manager)
{
this.state = state;
this.manager = manager;
}
public void registerRubyModelDelta(IRubyElementDelta delta)
{
this.rubyModelDeltas.add(delta);
}
public void updateRubyModel(IRubyElementDelta customDelta)
{
if (customDelta == null)
{
for (int i = 0, length = this.rubyModelDeltas.size(); i < length; i++)
{
IRubyElementDelta delta = this.rubyModelDeltas.get(i);
this.modelUpdater.processRubyDelta(delta);
}
}
else
{
this.modelUpdater.processRubyDelta(customDelta);
}
}
/*
* Fire Java Model delta, flushing them after the fact after post_change notification. If the firing mode has been
* turned off, this has no effect.
*/
public void fire(IRubyElementDelta customDelta, int eventType)
{
if (!this.isFiring)
return;
if (DEBUG)
{
System.out
.println("-----------------------------------------------------------------------------------------------------------------------");//$NON-NLS-1$
}
IRubyElementDelta deltaToNotify;
if (customDelta == null)
{
deltaToNotify = this.mergeDeltas(this.rubyModelDeltas);
}
else
{
deltaToNotify = customDelta;
}
// Refresh internal scopes
if (deltaToNotify != null)
{
Iterator<AbstractSearchScope> scopes = this.manager.searchScopes.keySet().iterator();
while (scopes.hasNext())
{
AbstractSearchScope scope = scopes.next();
scope.processDelta(deltaToNotify, eventType);
}
RubyWorkspaceScope workspaceScope = this.manager.workspaceScope;
if (workspaceScope != null)
workspaceScope.processDelta(deltaToNotify, eventType);
}
// Notification
// Important: if any listener reacts to notification by updating the
// listeners list or mask, these lists will
// be duplicated, so it is necessary to remember original lists in a
// variable (since field values may change under us)
IElementChangedListener[] listeners = this.state.elementChangedListeners;
int[] listenerMask = this.state.elementChangedListenerMasks;
int listenerCount = this.state.elementChangedListenerCount;
switch (eventType)
{
case DEFAULT_CHANGE_EVENT:
firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount);
fireReconcileDelta(listeners, listenerMask, listenerCount);
break;
case ElementChangedEvent.POST_CHANGE:
firePostChangeDelta(deltaToNotify, listeners, listenerMask, listenerCount);
fireReconcileDelta(listeners, listenerMask, listenerCount);
break;
}
}
/*
* Merges all awaiting deltas.
*/
private IRubyElementDelta mergeDeltas(Collection deltas)
{
if (deltas.size() == 0)
return null;
if (deltas.size() == 1)
return (IRubyElementDelta) deltas.iterator().next();
if (VERBOSE)
{
System.out.println("MERGING " + deltas.size() + " DELTAS [" + Thread.currentThread() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
Iterator iterator = deltas.iterator();
RubyElementDelta rootDelta = new RubyElementDelta(this.manager.rubyModel);
boolean insertedTree = false;
while (iterator.hasNext())
{
RubyElementDelta delta = (RubyElementDelta) iterator.next();
if (VERBOSE)
{
System.out.println(delta.toString());
}
IRubyElement element = delta.getElement();
if (this.manager.rubyModel.equals(element))
{
IRubyElementDelta[] children = delta.getAffectedChildren();
for (int j = 0; j < children.length; j++)
{
RubyElementDelta projectDelta = (RubyElementDelta) children[j];
rootDelta.insertDeltaTree(projectDelta.getElement(), projectDelta);
insertedTree = true;
}
IResourceDelta[] resourceDeltas = delta.getResourceDeltas();
if (resourceDeltas != null)
{
for (int i = 0, length = resourceDeltas.length; i < length; i++)
{
rootDelta.addResourceDelta(resourceDeltas[i]);
insertedTree = true;
}
}
}
else
{
rootDelta.insertDeltaTree(element, delta);
insertedTree = true;
}
}
if (insertedTree)
return rootDelta;
return null;
}
private void firePostChangeDelta(IRubyElementDelta deltaToNotify, IElementChangedListener[] listeners,
int[] listenerMask, int listenerCount)
{
// post change deltas
if (DEBUG)
{
System.out.println("FIRING POST_CHANGE Delta [" + Thread.currentThread() + "]:"); //$NON-NLS-1$//$NON-NLS-2$
System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
}
if (deltaToNotify != null)
{
// flush now so as to keep listener reactions to post their own
// deltas for subsequent iteration
this.flush();
notifyListeners(deltaToNotify, ElementChangedEvent.POST_CHANGE, listeners, listenerMask, listenerCount);
}
}
private void fireReconcileDelta(IElementChangedListener[] listeners, int[] listenerMask, int listenerCount)
{
IRubyElementDelta deltaToNotify = mergeDeltas(this.reconcileDeltas.values());
if (DEBUG)
{
System.out.println("FIRING POST_RECONCILE Delta [" + Thread.currentThread() + "]:"); //$NON-NLS-1$//$NON-NLS-2$
System.out.println(deltaToNotify == null ? "<NONE>" : deltaToNotify.toString()); //$NON-NLS-1$
}
if (deltaToNotify != null)
{
// flush now so as to keep listener reactions to post their own
// deltas for subsequent iteration
this.reconcileDeltas = new HashMap<IRubyScript, IRubyElementDelta>();
notifyListeners(deltaToNotify, ElementChangedEvent.POST_RECONCILE, listeners, listenerMask, listenerCount);
}
}
/*
* Flushes all deltas without firing them.
*/
public void flush()
{
this.rubyModelDeltas = new ArrayList<IRubyElementDelta>();
}
private void notifyListeners(IRubyElementDelta deltaToNotify, int eventType, IElementChangedListener[] listeners,
int[] listenerMask, int listenerCount)
{
final ElementChangedEvent extraEvent = new ElementChangedEvent(deltaToNotify, eventType);
for (int i = 0; i < listenerCount; i++)
{
if ((listenerMask[i] & eventType) != 0)
{
final IElementChangedListener listener = listeners[i];
long start = -1;
if (VERBOSE)
{
System.out.print("Listener #" + (i + 1) + "=" + listener.toString());//$NON-NLS-1$//$NON-NLS-2$
start = System.currentTimeMillis();
}
// wrap callbacks with Safe runnable for subsequent listeners to
// be called when some are causing grief
SafeRunner.run(new ISafeRunnable()
{
public void handleException(Throwable exception)
{
Util.log(exception, "Exception occurred in listener of Java element change notification"); //$NON-NLS-1$
}
public void run() throws Exception
{
PerformanceStats stats = null;
if (PERF)
{
stats = PerformanceStats.getStats(RubyModelManager.DELTA_LISTENER_PERF, listener);
stats.startRun();
}
listener.elementChanged(extraEvent);
if (PERF)
{
stats.endRun();
}
}
});
if (VERBOSE)
{
System.out.println(" -> " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
}
/*
* Notification that some resource changes have happened on the platform, and that the Ruby Model should update any
* required internal structures such that its elements remain consistent. Translates <code>IResourceDeltas</code>
* into <code>IRubyElementDeltas</code>.
* @see IResourceDelta
* @see IResource
*/
public void resourceChanged(IResourceChangeEvent event)
{
if (event.getSource() instanceof IWorkspace)
{
int eventType = this.overridenEventType == -1 ? event.getType() : this.overridenEventType;
IResource resource = event.getResource();
IResourceDelta delta = event.getDelta();
switch (eventType)
{
case IResourceChangeEvent.PRE_DELETE:
try
{
if (resource.getType() == IResource.PROJECT
&& ((IProject) resource).hasNature(RubyCore.NATURE_ID))
{
deleting((IProject) resource);
}
}
catch (CoreException e)
{
// project doesn't exist or is not open: ignore
}
return;
case IResourceChangeEvent.POST_CHANGE:
if (isAffectedBy(delta))
{ // avoid populating for SYNC or
// MARKER deltas
try
{
try
{
stopDeltas();
checkProjectsBeingAddedOrRemoved(delta);
if (this.refreshedElements != null)
{
createExternalArchiveDelta(null);
}
IRubyElementDelta translatedDelta = processResourceDelta(delta);
if (translatedDelta != null)
{
registerRubyModelDelta(translatedDelta);
}
}
finally
{
startDeltas();
}
IElementChangedListener[] listeners;
int listenerCount;
synchronized (this.state)
{
listeners = this.state.elementChangedListeners;
listenerCount = this.state.elementChangedListenerCount;
}
notifyTypeHierarchies(listeners, listenerCount);
fire(null, ElementChangedEvent.POST_CHANGE);
}
finally
{
// workaround for bug 15168 circular errors not reported
this.state.resetOldRubyProjectNames();
this.removedRoots = null;
}
}
return;
case IResourceChangeEvent.PRE_BUILD:
DeltaProcessingState.ProjectUpdateInfo[] updates = this.state.removeAllProjectUpdates();
if (updates != null)
{
for (int i = 0, length = updates.length; i < length; i++)
{
try
{
updates[i].updateProjectReferencesIfNecessary();
}
catch (RubyModelException e)
{
// do nothing
}
}
}
// this.processPostChange = false;
if (isAffectedBy(delta))
{ // avoid populating for SYNC or MARKER deltas
updateLoadpathMarkers(delta, updates);
RubyBuilder.buildStarting();
}
// does not fire any deltas
return;
case IResourceChangeEvent.POST_BUILD:
RubyBuilder.buildFinished();
return;
}
}
}
private void notifyTypeHierarchies(IElementChangedListener[] listeners, int listenerCount)
{
for (int i = 0; i < listenerCount; i++)
{
final IElementChangedListener listener = listeners[i];
if (!(listener instanceof TypeHierarchy))
continue;
// wrap callbacks with Safe runnable for subsequent listeners to be called when some are causing grief
SafeRunner.run(new ISafeRunnable()
{
public void handleException(Throwable exception)
{
Util.log(exception, "Exception occurred in listener of Ruby element change notification"); //$NON-NLS-1$
}
public void run() throws Exception
{
TypeHierarchy typeHierarchy = (TypeHierarchy) listener;
if (typeHierarchy.hasFineGrainChanges())
{
// case of changes in primary working copies
typeHierarchy.needsRefresh = true;
typeHierarchy.fireChange();
}
}
});
}
}
/*
* Update the .loadpath format, missing entries and cycle markers for the projects affected by the given delta.
*/
private void updateLoadpathMarkers(IResourceDelta delta, DeltaProcessingState.ProjectUpdateInfo[] updates)
{
Map<RubyProject, ILoadpathEntry[]> preferredClasspaths = new HashMap<RubyProject, ILoadpathEntry[]>(5);
Map preferredOutputs = new HashMap(5);
HashSet<IPath> affectedProjects = new HashSet<IPath>(5);
// read .loadpath files that have changed, and create markers if format is wrong or if an entry cannot be found
RubyModel.flushExternalFileCache();
updateLoadpathMarkers(delta, affectedProjects, preferredClasspaths, preferredOutputs);
// update .loadpath format markers for affected projects (dependent projects
// or projects that reference a library in one of the projects that have changed)
if (!affectedProjects.isEmpty())
{
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
IProject[] projects = workspaceRoot.getProjects();
int length = projects.length;
for (int i = 0; i < length; i++)
{
IProject project = projects[i];
RubyProject rubyProject = (RubyProject) RubyCore.create(project);
if (preferredClasspaths.get(rubyProject) == null)
{ // not already updated
try
{
IPath projectPath = project.getFullPath();
ILoadpathEntry[] classpath = rubyProject.getResolvedLoadpath(true/* ignoreUnresolvedEntry */,
false/* don't generateMarkerOnError */, false/* don't returnResolutionInProgress */); // allowed
// to
// reuse
// model
// cache
for (int j = 0, cpLength = classpath.length; j < cpLength; j++)
{
ILoadpathEntry entry = classpath[j];
switch (entry.getEntryKind())
{
case ILoadpathEntry.CPE_PROJECT:
if (affectedProjects.contains(entry.getPath()))
{
rubyProject.updateLoadpathMarkers(null, null);
}
break;
case ILoadpathEntry.CPE_LIBRARY:
IPath entryPath = entry.getPath();
IPath libProjectPath = entryPath.removeLastSegments(entryPath.segmentCount() - 1);
if (!libProjectPath.equals(projectPath) // if library contained in another project
&& affectedProjects.contains(libProjectPath))
{
rubyProject.updateLoadpathMarkers(null, null);
}
break;
}
}
}
catch (RubyModelException e)
{
// project no longer exists
}
}
}
}
if (!affectedProjects.isEmpty() || updates != null)
{
// update all cycle markers since the given delta may have affected cycles
if (updates != null)
{
for (int i = 0, length = updates.length; i < length; i++)
{
DeltaProcessingState.ProjectUpdateInfo info = updates[i];
if (!preferredClasspaths.containsKey(info.project))
preferredClasspaths.put(info.project, info.newResolvedPath);
}
}
try
{
RubyProject.updateAllCycleMarkers(preferredClasspaths);
}
catch (RubyModelException e)
{
// project no longer exist
}
}
}
/*
* Check whether .classpath files are affected by the given delta. Creates/removes problem markers if needed.
* Remember the affected projects in the given set.
*/
private void updateLoadpathMarkers(IResourceDelta delta, HashSet<IPath> affectedProjects, Map preferredClasspaths,
Map preferredOutputs)
{
IResource resource = delta.getResource();
boolean processChildren = false;
switch (resource.getType())
{
case IResource.ROOT:
if (delta.getKind() == IResourceDelta.CHANGED)
{
processChildren = true;
}
break;
case IResource.PROJECT:
IProject project = (IProject) resource;
int kind = delta.getKind();
boolean isRubyProject = RubyProject.hasRubyNature(project);
switch (kind)
{
case IResourceDelta.ADDED:
processChildren = isRubyProject;
affectedProjects.add(project.getFullPath());
break;
case IResourceDelta.CHANGED:
processChildren = isRubyProject;
if ((delta.getFlags() & IResourceDelta.OPEN) != 0)
{
// project opened or closed: remember project and its dependents
affectedProjects.add(project.getFullPath());
if (isRubyProject)
{
RubyProject rubyProject = (RubyProject) RubyCore.create(project);
rubyProject.updateLoadpathMarkers(preferredClasspaths, preferredOutputs); // in case
// .loadpath
// got
// modified
// while
// closed
}
}
else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0)
{
boolean wasRubyProject = this.state.findRubyProject(project.getName()) != null;
if (wasRubyProject && !isRubyProject)
{
// project no longer has Ruby nature, discard Ruby related obsolete markers
affectedProjects.add(project.getFullPath());
// flush loadpath markers
RubyProject javaProject = (RubyProject) RubyCore.create(project);
javaProject.flushLoadpathProblemMarkers(true, // flush cycle markers
true // flush loadpath format markers
);
// remove problems and tasks created by the builder
RubyBuilder.removeProblemsAndTasksFor(project);
}
}
else if (isRubyProject)
{
// check if all entries exist
try
{
RubyProject javaProject = (RubyProject) RubyCore.create(project);
javaProject.getResolvedLoadpath(true/* ignoreUnresolvedEntry */,
true/* generateMarkerOnError */, false/* don't returnResolutionInProgress */);
}
catch (RubyModelException e)
{
// project doesn't exist: ignore
}
}
break;
case IResourceDelta.REMOVED:
affectedProjects.add(project.getFullPath());
break;
}
break;
case IResource.FILE:
/* check loadpath file change */
IFile file = (IFile) resource;
if (file.getName().equals(RubyProject.LOADPATH_FILENAME))
{
affectedProjects.add(file.getProject().getFullPath());
RubyProject rubyProject = (RubyProject) RubyCore.create(file.getProject());
rubyProject.updateLoadpathMarkers(preferredClasspaths, preferredOutputs);
break;
}
// /* check custom preference file change */
// if (file.getName().equals(JavaProject.PREF_FILENAME)) {
// reconcilePreferenceFileUpdate(delta, file, project);
// break;
// }
break;
}
if (processChildren)
{
IResourceDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; i++)
{
updateLoadpathMarkers(children[i], affectedProjects, preferredClasspaths, preferredOutputs);
}
}
}
/*
* Converts a <code>IResourceDelta</code> rooted in a <code>Workspace</code> into the corresponding set of
* <code>IRubyElementDelta</code>, rooted in the relevant <code>RubyModel</code>s.
*/
private IRubyElementDelta processResourceDelta(IResourceDelta changes)
{
try
{
IRubyModel model = this.manager.getRubyModel();
if (!model.isOpen())
{
// force opening of ruby model so that ruby element delta are
// reported
try
{
model.open(null);
}
catch (RubyModelException e)
{
if (VERBOSE)
{
e.printStackTrace();
}
return null;
}
}
this.state.initializeRoots();
this.currentElement = null;
// get the workspace delta, and start processing there.
IResourceDelta[] deltas = changes.getAffectedChildren();
for (int i = 0; i < deltas.length; i++)
{
IResourceDelta delta = deltas[i];
IResource res = delta.getResource();
// find out the element type
RootInfo rootInfo = null;
int elementType;
IProject proj = (IProject) res;
boolean wasRubyProject = this.state.findRubyProject(proj.getName()) != null;
boolean isRubyProject = RubyProject.hasRubyNature(proj);
if (!wasRubyProject && !isRubyProject)
{
elementType = NON_RUBY_RESOURCE;
}
else
{
rootInfo = this.enclosingRootInfo(res.getFullPath(), delta.getKind());
if (rootInfo != null && rootInfo.isRootOfProject(res.getFullPath()))
{
elementType = IRubyElement.SOURCE_FOLDER_ROOT;
}
else
{
elementType = IRubyElement.RUBY_PROJECT;
}
}
// traverse delta
this.traverseDelta(delta, elementType, rootInfo);
if (elementType == NON_RUBY_RESOURCE
|| (wasRubyProject != isRubyProject && (delta.getKind()) == IResourceDelta.CHANGED))
{ // project
// has
// changed
// nature
// (description
// or
// open/closed)
try
{
// add child as non ruby resource
nonRubyResourcesChanged((RubyModel) model, delta);
}
catch (RubyModelException e)
{
// ruby model could not be opened
}
}
}
resetProjectCaches();
return this.currentDelta;
}
finally
{
this.currentDelta = null;
this.rootsToRefresh.clear();
this.projectCachesToReset.clear();
}
}
/*
* Finds the root info this path is included in. Returns null if not found.
*/
private RootInfo enclosingRootInfo(IPath path, int kind)
{
while (path != null && path.segmentCount() > 0)
{
RootInfo rootInfo = this.rootInfo(path, kind);
if (rootInfo != null)
return rootInfo;
path = path.removeLastSegments(1);
}
return null;
}
/*
* Returns the root info for the given path. Look in the old roots table if kind is REMOVED.
*/
private RootInfo rootInfo(IPath path, int kind)
{
if (kind == IResourceDelta.REMOVED)
{
return (RootInfo) this.state.oldRoots.get(path);
}
return (RootInfo) this.state.roots.get(path);
}
/*
* Refresh source folder roots of projects that were affected
*/
private void refreshSourceFolderRoots()
{
Iterator iterator = this.rootsToRefresh.iterator();
while (iterator.hasNext())
{
RubyProject project = (RubyProject) iterator.next();
project.updateSourceFolderRoots();
}
}
private RubyElementDelta currentDelta()
{
if (this.currentDelta == null)
{
this.currentDelta = new RubyElementDelta(this.manager.getRubyModel());
}
return this.currentDelta;
}
/*
* Traverse the set of projects which have changed namespace, and reset their caches and their dependents
*/
private void resetProjectCaches()
{
Iterator iterator = this.projectCachesToReset.iterator();
HashMap projectDepencies = this.state.projectDependencies;
HashSet<IRubyProject> affectedDependents = new HashSet<IRubyProject>();
while (iterator.hasNext())
{
RubyProject project = (RubyProject) iterator.next();
project.resetCaches();
addDependentProjects(project, projectDepencies, affectedDependents);
}
// reset caches of dependent projects
iterator = affectedDependents.iterator();
while (iterator.hasNext())
{
RubyProject project = (RubyProject) iterator.next();
project.resetCaches();
}
}
/*
* Adds the dependents of the given project to the list of the projects to update.
*/
private void addDependentProjects(IRubyProject project, HashMap projectDependencies, HashSet<IRubyProject> result)
{
IRubyProject[] dependents = (IRubyProject[]) projectDependencies.get(project);
if (dependents == null)
return;
for (int i = 0, length = dependents.length; i < length; i++)
{
IRubyProject dependent = dependents[i];
if (result.contains(dependent))
continue; // no need to go further
// as the project is
// already known
result.add(dependent);
addDependentProjects(dependent, projectDependencies, result);
}
}
/*
* Generic processing for elements with changed contents:<ul> <li>The element is closed such that any subsequent
* accesses will re-open the element reflecting its new structure. <li>An entry is made in the delta reporting a
* content change (K_CHANGE with F_CONTENT flag set). </ul>
*/
private void nonRubyResourcesChanged(Openable element, IResourceDelta delta) throws RubyModelException
{
// reset non-ruby resources if element was open
if (element.isOpen())
{
RubyElementInfo info = (RubyElementInfo) element.getElementInfo();
switch (element.getElementType())
{
case IRubyElement.RUBY_MODEL:
((RubyModelInfo) info).nonRubyResources = null;
currentDelta().addResourceDelta(delta);
return;
case IRubyElement.RUBY_PROJECT:
((RubyProjectElementInfo) info).setNonRubyResources(null);
// if a package fragment root is the project, clear it too
RubyProject project = (RubyProject) element;
SourceFolderRoot projectRoot = (SourceFolderRoot) project.getSourceFolderRoot(project.getProject());
if (projectRoot.isOpen())
{
((SourceFolderRootInfo) projectRoot.getElementInfo()).setNonRubyResources(null);
}
break;
case IRubyElement.SOURCE_FOLDER:
((SourceFolderInfo) info).setNonRubyResources(null);
break;
case IRubyElement.SOURCE_FOLDER_ROOT:
((SourceFolderRootInfo) info).setNonRubyResources(null);
}
}
RubyElementDelta current = currentDelta();
RubyElementDelta elementDelta = current.find(element);
if (elementDelta == null)
{
// don't use find after creating the delta as it can be null (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=63434)
elementDelta = current.changed(element, IRubyElementDelta.F_CONTENT);
}
elementDelta.addResourceDelta(delta);
}
/*
* Turns the firing mode to off. That is, deltas that are/have been registered will not be fired until deltas are
* started again.
*/
private void stopDeltas()
{
this.isFiring = false;
}
/*
* Turns the firing mode to on. That is, deltas that are/have been registered will be fired.
*/
private void startDeltas()
{
this.isFiring = true;
}
/*
* Note that the project is about to be deleted.
*/
private void deleting(IProject project)
{
try
{
// discard indexing jobs that belong to this project so that the project can be
// deleted without interferences from the index manager
// FIXME Uncomment when we have a more sophisticated index manager
// this.manager.indexManager.discardJobs(project.getName());
RubyProject rubyProject = (RubyProject) RubyCore.create(project);
// remember roots of this project
if (this.removedRoots == null)
{
this.removedRoots = new HashMap<IRubyProject, ISourceFolderRoot[]>();
}
if (rubyProject.isOpen())
{
this.removedRoots.put(rubyProject, rubyProject.getSourceFolderRoots());
}
else
{
// compute roots without opening project
this.removedRoots.put(rubyProject, rubyProject.computeSourceFolderRoots(rubyProject
.getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/* don't generateMarkerOnError */,
false/* don't returnResolutionInProgress */), false, null /* no reverse map */));
}
rubyProject.close();
// workaround for bug 15168 circular errors not reported
this.state.getOldRubyProjecNames(); // foce list to be computed
this.removeFromParentInfo(rubyProject);
// remove preferences from per project info
this.manager.resetProjectPreferences(rubyProject);
}
catch (RubyModelException e)
{
// java project doesn't exist: ignore
}
}
/*
* Removes the given element from its parents cache of children. If the element does not have a parent, or the
* parent is not currently open, this has no effect.
*/
private void removeFromParentInfo(Openable child)
{
Openable parent = (Openable) child.getParent();
if (parent != null && parent.isOpen())
{
try
{
RubyElementInfo info = (RubyElementInfo) parent.getElementInfo();
info.removeChild(child);
}
catch (RubyModelException e)
{
// do nothing - we already checked if open
}
}
}
/*
* Returns whether a given delta contains some information relevant to the JavaModel, in particular it will not
* consider SYNC or MARKER only deltas.
*/
private boolean isAffectedBy(IResourceDelta rootDelta)
{
// if (rootDelta == null) System.out.println("NULL DELTA");
// long start = System.currentTimeMillis();
if (rootDelta != null)
{
// use local exception to quickly escape from delta traversal
class FoundRelevantDeltaException extends RuntimeException
{
private static final long serialVersionUID = 7137113252936111022L; // backward
// compatible
// only the class name is used (to differenciate from other
// RuntimeExceptions)
}
try
{
rootDelta.accept(new IResourceDeltaVisitor()
{
public boolean visit(IResourceDelta delta) /*
* throws CoreException
*/
{
switch (delta.getKind())
{
case IResourceDelta.ADDED:
case IResourceDelta.REMOVED:
throw new FoundRelevantDeltaException();
case IResourceDelta.CHANGED:
// if any flag is set but SYNC or MARKER, this delta
// should be considered
if (delta.getAffectedChildren().length == 0 // only
// check
// leaf
// delta
// nodes
&& (delta.getFlags() & ~(IResourceDelta.SYNC | IResourceDelta.MARKERS)) != 0)
{
throw new FoundRelevantDeltaException();
}
}
return true;
}
});
}
catch (FoundRelevantDeltaException e)
{
// System.out.println("RELEVANT DELTA detected in: "+
// (System.currentTimeMillis() - start));
return true;
}
catch (CoreException e)
{ // ignore delta if not able to traverse
}
}
// System.out.println("IGNORE SYNC DELTA took: "+
// (System.currentTimeMillis() - start));
return false;
}
/*
* Process the given delta and look for projects being added, opened, closed or with a java nature being added or
* removed. Note that projects being deleted are checked in deleting(IProject). In all cases, add the project's
* dependents to the list of projects to update so that the classpath related markers can be updated.
*/
private void checkProjectsBeingAddedOrRemoved(IResourceDelta delta)
{
IResource resource = delta.getResource();
boolean processChildren = false;
switch (resource.getType())
{
case IResource.ROOT:
// workaround for bug 15168 circular errors not reported
this.state.getOldRubyProjecNames(); // force list to be computed
processChildren = true;
break;
case IResource.PROJECT:
// NB: No need to check project's nature as if the project is not a
// ruby project:
// - if the project is added or changed this is a noop for
// projectsBeingDeleted
// - if the project is closed, it has already lost its ruby nature
IProject project = (IProject) resource;
RubyProject rubyProject = (RubyProject) RubyCore.create(project);
switch (delta.getKind())
{
case IResourceDelta.ADDED:
this.manager.batchContainerInitializations = true;
// remember project and its dependents
this.addToRootsToRefreshWithDependents(rubyProject);
// workaround for bug 15168 circular errors not reported
if (RubyProject.hasRubyNature(project))
{
this.addToParentInfo(rubyProject);
// ensure project references are updated (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=121569)
try
{
this.state.updateProjectReferences(rubyProject, null/* no old loadpath */, null/*
* compute
* new
* resolved
* loadpath
* later
*/,
null/* read raw loadpath later */, false/* cannot change resources */);
}
catch (RubyModelException e1)
{
// project always exists
}
}
this.state.rootsAreStale = true;
break;
case IResourceDelta.CHANGED:
if ((delta.getFlags() & IResourceDelta.OPEN) != 0)
{
this.manager.batchContainerInitializations = true;
// project opened or closed: remember project and its dependents
this.addToRootsToRefreshWithDependents(rubyProject);
// workaround for bug 15168 circular errors not reported
if (project.isOpen())
{
if (RubyProject.hasRubyNature(project))
{
this.addToParentInfo(rubyProject);
}
}
else
{
try
{
rubyProject.close();
}
catch (RubyModelException e)
{
// ruby project doesn't exist: ignore
}
this.removeFromParentInfo(rubyProject);
this.manager.removePerProjectInfo(rubyProject);
this.manager.containerRemove(rubyProject);
}
this.state.rootsAreStale = true;
}
else if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0)
{
boolean wasJavaProject = this.state.findRubyProject(project.getName()) != null;
boolean isJavaProject = RubyProject.hasRubyNature(project);
if (wasJavaProject != isJavaProject)
{
this.manager.batchContainerInitializations = true;
// ruby nature added or removed: remember project and its dependents
this.addToRootsToRefreshWithDependents(rubyProject);
// workaround for bug 15168 circular errors not reported
if (isJavaProject)
{
this.addToParentInfo(rubyProject);
}
else
{
// remove classpath cache so that initializeRoots()
// will not consider the project has a classpath
this.manager.removePerProjectInfo((RubyProject) RubyCore.create(project));
// remove container cache for this project
this.manager.containerRemove(rubyProject);
// close project
try
{
rubyProject.close();
}
catch (RubyModelException e)
{
// ruby project doesn't exist: ignore
}
this.removeFromParentInfo(rubyProject);
}
this.state.rootsAreStale = true;
}
else
{
// in case the project was removed then added then
// changed (see bug 19799)
if (isJavaProject)
{ // need nature check - 18698
this.addToParentInfo(rubyProject);
processChildren = true;
}
}
}
else
{
// workaround for bug 15168 circular errors not reported
// in case the project was removed then added then changed
if (RubyProject.hasRubyNature(project))
{ // need nature
// check - 18698
this.addToParentInfo(rubyProject);
processChildren = true;
}
}
break;
case IResourceDelta.REMOVED:
this.manager.batchContainerInitializations = true;
// remove classpath cache so that initializeRoots() will not consider the project has a
// classpath
this.manager.removePerProjectInfo(rubyProject);
// remove container cache for this project
this.manager.containerRemove(rubyProject);
this.state.rootsAreStale = true;
break;
}
// in all cases, refresh the external jars for this project
addForRefresh(rubyProject);
break;
case IResource.FILE:
IFile file = (IFile) resource;
/* loadpath file change */
if (file.getName().equals(RubyProject.LOADPATH_FILENAME))
{
this.manager.batchContainerInitializations = true;
reconcileLoadpathFileUpdate(delta, (RubyProject) RubyCore.create(file.getProject()));
this.state.rootsAreStale = true;
}
break;
}
if (processChildren)
{
IResourceDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; i++)
{
checkProjectsBeingAddedOrRemoved(children[i]);
}
}
}
/*
* Adds the given project and its dependents to the list of the roots to refresh.
*/
private void addToRootsToRefreshWithDependents(IRubyProject rubyProject)
{
this.rootsToRefresh.add(rubyProject);
this.addDependentProjects(rubyProject, this.state.projectDependencies, this.rootsToRefresh);
}
/*
* Adds the given element to the list of elements used as a scope for external jars refresh.
*/
public void addForRefresh(IRubyElement element)
{
if (this.refreshedElements == null)
{
this.refreshedElements = new HashSet<IRubyElement>();
}
this.refreshedElements.add(element);
}
/*
* Adds the given child handle to its parent's cache of children.
*/
private void addToParentInfo(Openable child)
{
Openable parent = (Openable) child.getParent();
if (parent != null && parent.isOpen())
{
try
{
RubyElementInfo info = (RubyElementInfo) parent.getElementInfo();
info.addChild(child);
}
catch (RubyModelException e)
{
// do nothing - we already checked if open
}
}
}
/*
* Converts an <code>IResourceDelta</code> and its children into the corresponding <code>IRubyElementDelta</code>s.
*/
private void traverseDelta(IResourceDelta delta, int elementType, RootInfo rootInfo)
{
IResource res = delta.getResource();
// set stack of elements
if (this.currentElement == null && rootInfo != null)
{
this.currentElement = rootInfo.project;
}
// process current delta
boolean processChildren = true;
if (res instanceof IProject)
{
processChildren = updateCurrentDeltaAndIndex(delta,
elementType == IRubyElement.SOURCE_FOLDER_ROOT ? IRubyElement.RUBY_PROJECT : // case of prj=src,
elementType, rootInfo);
}
else if (rootInfo != null)
{
processChildren = this.updateCurrentDeltaAndIndex(delta, elementType, rootInfo);
}
else
{
// not yet inside a package fragment root
processChildren = true;
}
// process children if needed
if (processChildren)
{
IResourceDelta[] children = delta.getAffectedChildren();
boolean oneChildOnLoadpath = false;
int length = children.length;
IResourceDelta[] orphanChildren = null;
Openable parent = null;
boolean isValidParent = true;
for (int i = 0; i < length; i++)
{
IResourceDelta child = children[i];
IResource childRes = child.getResource();
// find out whether the child is a source folder root of the current project
IPath childPath = childRes.getFullPath();
int childKind = child.getKind();
RootInfo childRootInfo = this.rootInfo(childPath, childKind);
if (childRootInfo != null && !childRootInfo.isRootOfProject(childPath))
{
// package fragment root of another project (dealt with later)
childRootInfo = null;
}
// compute child type
int childType = this.elementType(childRes, childKind, elementType, rootInfo == null ? childRootInfo
: rootInfo);
// is childRes in the output folder and is it filtered out ?
boolean isResFilteredFromOutput = false;
boolean isNestedRoot = rootInfo != null && childRootInfo != null;
if (!isResFilteredFromOutput && !isNestedRoot)
{ // do not treat as non-ruby rsc if nested root
this.traverseDelta(child, childType, rootInfo == null ? childRootInfo : rootInfo); // traverse delta
// for child in
// the same
// project
if (childType == NON_RUBY_RESOURCE)
{
if (rootInfo != null)
{ // if inside a source folder root
if (!isValidParent)
continue;
if (parent == null)
{
// find the parent of the non-ruby resource to attach to
if (this.currentElement == null
|| !rootInfo.project.equals(this.currentElement.getRubyProject()))
{ // note if currentElement is the IRubyModel, getJavaProject() is null
// force the currentProject to be used
this.currentElement = rootInfo.project;
}
if (elementType == IRubyElement.RUBY_PROJECT
|| (elementType == IRubyElement.SOURCE_FOLDER_ROOT && res instanceof IProject))
{
// NB: attach non-ruby resource to project (not to its package fragment root)
parent = rootInfo.project;
}
else
{
parent = this.createElement(res, elementType, rootInfo);
}
if (parent == null)
{
isValidParent = false;
continue;
}
}
// add child as non ruby resource
try
{
nonRubyResourcesChanged(parent, child);
}
catch (RubyModelException e)
{
// ignore
}
}
else
{
// the non-ruby resource (or its parent folder) will be attached to the ruby project
if (orphanChildren == null)
orphanChildren = new IResourceDelta[length];
orphanChildren[i] = child;
}
}
else
{
oneChildOnLoadpath = true;
}
}
else
{
oneChildOnLoadpath = true; // to avoid reporting child delta as non-ruby resource delta
}
// if child is a nested root
// or if it is not a package fragment root of the current project
// but it is a package fragment root of another project, traverse delta too
if (isNestedRoot
|| (childRootInfo == null && (childRootInfo = this.rootInfo(childPath, childKind)) != null))
{
this.traverseDelta(child, IRubyElement.SOURCE_FOLDER_ROOT, childRootInfo); // binary output of
// childRootInfo.project
// cannot be this root
}
// if the child is a package fragment root of one or several other projects
ArrayList<RootInfo> rootList;
if ((rootList = this.otherRootsInfo(childPath, childKind)) != null)
{
Iterator<RootInfo> iterator = rootList.iterator();
while (iterator.hasNext())
{
childRootInfo = iterator.next();
this.traverseDelta(child, IRubyElement.SOURCE_FOLDER_ROOT, childRootInfo); // binary output of
// childRootInfo.project
// cannot be this
// root
}
}
}
if (orphanChildren != null && (oneChildOnLoadpath // orphan
// children are
// siblings of a
// package
// fragment root
|| res instanceof IProject))
{ // non-ruby resource
// directly under a project
// attach orphan children
IProject rscProject = res.getProject();
RubyProject adoptiveProject = (RubyProject) RubyCore.create(rscProject);
if (adoptiveProject != null && RubyProject.hasRubyNature(rscProject))
{ // delta
// iff
// Ruby
// project
// (18698)
for (int i = 0; i < length; i++)
{
if (orphanChildren[i] != null)
{
try
{
nonRubyResourcesChanged(adoptiveProject, orphanChildren[i]);
}
catch (RubyModelException e)
{
// ignore
}
}
}
}
} // else resource delta will be added by parent
} // else resource delta will be added by parent
}
/*
* Returns the other root infos for the given path. Look in the old other roots table if kind is REMOVED.
*/
private ArrayList<RootInfo> otherRootsInfo(IPath path, int kind)
{
if (kind == IResourceDelta.REMOVED)
{
return this.state.oldOtherRoots.get(path);
}
return this.state.otherRoots.get(path);
}
/*
* Closes the given element, which removes it from the cache of open elements.
*/
private void close(Openable element)
{
try
{
element.close();
}
catch (RubyModelException e)
{
// do nothing
}
}
/*
* Creates the openables corresponding to this resource. Returns null if none was found.
*/
private Openable createElement(IResource resource, int elementType, RootInfo rootInfo)
{
if (resource == null)
return null;
IPath path = resource.getFullPath();
IRubyElement element = null;
switch (elementType)
{
case IRubyElement.RUBY_PROJECT:
// note that non-ruby resources rooted at the project level will
// also enter this code with
// an elementType PROJECT (see #elementType(...)).
if (resource instanceof IProject)
{
this.popUntilPrefixOf(path);
if (this.currentElement != null
&& this.currentElement.getElementType() == IRubyElement.RUBY_PROJECT
&& ((IRubyProject) this.currentElement).getProject().equals(resource))
{
return this.currentElement;
}
if (rootInfo != null && rootInfo.project.getProject().equals(resource))
{
element = rootInfo.project;
break;
}
IProject proj = (IProject) resource;
if (RubyProject.hasRubyNature(proj))
{
element = RubyCore.create(proj);
}
else
{
// java project may have been been closed or removed (look
// for
// element amongst old ruby project s list).
element = this.manager.getRubyModel().findRubyProject(proj);
}
}
break;
case IRubyElement.SOURCE_FOLDER_ROOT:
element = rootInfo == null ? RubyCore.create(resource) : rootInfo.getSourceFolderRoot(resource);
break;
case IRubyElement.SOURCE_FOLDER:
if (rootInfo != null)
{
if (rootInfo.project.contains(resource))
{
SourceFolderRoot root = (SourceFolderRoot) rootInfo.getSourceFolderRoot(null);
// create package handle
IPath pkgPath = path.removeFirstSegments(rootInfo.rootPath.segmentCount());
String[] pkgName = pkgPath.segments();
element = root.getSourceFolder(pkgName);
}
}
else
{
// find the element that encloses the resource
this.popUntilPrefixOf(path);
if (this.currentElement == null)
{
element = RubyCore.create(resource);
}
else
{
// find the root
SourceFolderRoot root = this.currentElement.getSourceFolderRoot();
if (root == null)
{
element = RubyCore.create(resource);
}
else if (((RubyProject) root.getRubyProject()).contains(resource))
{
// create package handle
IPath pkgPath = path.removeFirstSegments(root.getPath().segmentCount());
String[] pkgName = pkgPath.segments();
element = root.getSourceFolder(pkgName);
}
}
}
break;
case IRubyElement.SCRIPT:
// find the element that encloses the resource
this.popUntilPrefixOf(path);
element = RubyCore.create(resource);
break;
}
if (element == null)
return null;
this.currentElement = (Openable) element;
return this.currentElement;
}
private void popUntilPrefixOf(IPath path)
{
while (this.currentElement != null)
{
IPath currentElementPath = null;
IResource currentElementResource = this.currentElement.getResource();
if (currentElementResource != null)
{
currentElementPath = currentElementResource.getFullPath();
}
if (currentElementPath != null)
{
if (currentElementPath.isPrefixOf(path))
{
return;
}
}
this.currentElement = (Openable) this.currentElement.getParent();
}
}
/*
* Processing for an element that has been added:<ul> <li>If the element is a project, do nothing, and do not
* process children, as when a project is created it does not yet have any natures - specifically a java nature.
* <li>If the elemet is not a project, process it as added (see <code>basicElementAdded</code>. </ul> Delta argument
* could be null if processing an external JAR change
*/
private void elementAdded(Openable element, IResourceDelta delta, RootInfo rootInfo)
{
int elementType = element.getElementType();
if (elementType == IRubyElement.RUBY_PROJECT)
{
// project add is handled by RubyProject.configure() because
// when a project is created, it does not yet have a ruby nature
if (delta != null && RubyProject.hasRubyNature((IProject) delta.getResource()))
{
addToParentInfo(element);
if ((delta.getFlags() & IResourceDelta.MOVED_FROM) != 0)
{
Openable movedFromElement = (Openable) element.getRubyModel().getRubyProject(
delta.getMovedFromPath().lastSegment());
currentDelta().movedTo(element, movedFromElement);
}
else
{
currentDelta().added(element);
}
this.state.updateRoots(element.getPath(), delta, this);
// refresh pkg fragment roots and caches of the project (and its
// dependents)
this.rootsToRefresh.add((IRubyProject) element);
this.projectCachesToReset.add((IRubyProject) element);
}
}
else
{
if (delta == null || (delta.getFlags() & IResourceDelta.MOVED_FROM) == 0)
{
// regular element addition
if (isPrimaryWorkingCopy(element, elementType))
{
// filter out changes to primary compilation unit in working
// copy mode
// just report a change to the resource (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
currentDelta().changed(element, IRubyElementDelta.F_PRIMARY_RESOURCE);
}
else
{
addToParentInfo(element);
// Force the element to be closed as it might have been
// opened
// before the resource modification came in and it might
// have a new child
// For example, in an IWorkspaceRunnable:
// 1. create a package fragment p using a java model
// operation
// 2. open package p
// 3. add file X.java in folder p
// When the resource delta comes in, only the addition of p
// is notified,
// but the package p is already opened, thus its children
// are not recomputed
// and it appears empty.
close(element);
currentDelta().added(element);
}
}
else
{
// element is moved
addToParentInfo(element);
close(element);
IPath movedFromPath = delta.getMovedFromPath();
IResource res = delta.getResource();
IResource movedFromRes;
if (res instanceof IFile)
{
movedFromRes = res.getWorkspace().getRoot().getFile(movedFromPath);
}
else
{
movedFromRes = res.getWorkspace().getRoot().getFolder(movedFromPath);
}
// find the element type of the moved from element
RootInfo movedFromInfo = this.enclosingRootInfo(movedFromPath, IResourceDelta.REMOVED);
int movedFromType = this.elementType(movedFromRes, IResourceDelta.REMOVED, element.getParent()
.getElementType(), movedFromInfo);
// reset current element as it might be inside a nested root
// (popUntilPrefixOf() may use the outer root)
this.currentElement = null;
// create the moved from element
Openable movedFromElement = elementType != IRubyElement.RUBY_PROJECT
&& movedFromType == IRubyElement.RUBY_PROJECT ? null : // outside
// loadpath
this.createElement(movedFromRes, movedFromType, rootInfo);
if (movedFromElement == null)
{
// moved from outside classpath
currentDelta().added(element);
}
else
{
currentDelta().movedTo(element, movedFromElement);
}
}
switch (elementType)
{
case IRubyElement.SOURCE_FOLDER_ROOT:
// when a root is added, and is on the loadpath, the project must be updated
RubyProject project = (RubyProject) element.getRubyProject();
// refresh src folder roots and caches of the project (and its dependents)
this.rootsToRefresh.add(project);
this.projectCachesToReset.add(project);
break;
case IRubyElement.SOURCE_FOLDER:
// reset project's source folder cache
project = (RubyProject) element.getRubyProject();
this.projectCachesToReset.add(project);
break;
}
}
}
/*
* Returns whether the given element is a primary compilation unit in working copy mode.
*/
private boolean isPrimaryWorkingCopy(IRubyElement element, int elementType)
{
if (elementType == IRubyElement.SCRIPT)
{
RubyScript cu = (RubyScript) element;
return cu.isPrimary() && cu.isWorkingCopy();
}
return false;
}
/*
* Generic processing for a removed element:<ul> <li>Close the element, removing its structure from the cache
* <li>Remove the element from its parent's cache of children <li>Add a REMOVED entry in the delta </ul> Delta
* argument could be null if processing an external JAR change
*/
private void elementRemoved(Openable element, IResourceDelta delta, RootInfo rootInfo)
{
int elementType = element.getElementType();
if (delta == null || (delta.getFlags() & IResourceDelta.MOVED_TO) == 0)
{
// regular element removal
if (isPrimaryWorkingCopy(element, elementType))
{
// filter out changes to primary compilation unit in working
// copy mode
// just report a change to the resource (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
currentDelta().changed(element, IRubyElementDelta.F_PRIMARY_RESOURCE);
}
else
{
close(element);
removeFromParentInfo(element);
currentDelta().removed(element);
}
}
else
{
// element is moved
close(element);
removeFromParentInfo(element);
IPath movedToPath = delta.getMovedToPath();
IResource res = delta.getResource();
IResource movedToRes;
switch (res.getType())
{
case IResource.PROJECT:
movedToRes = res.getWorkspace().getRoot().getProject(movedToPath.lastSegment());
break;
case IResource.FOLDER:
movedToRes = res.getWorkspace().getRoot().getFolder(movedToPath);
break;
case IResource.FILE:
movedToRes = res.getWorkspace().getRoot().getFile(movedToPath);
break;
default:
return;
}
// find the element type of the moved from element
RootInfo movedToInfo = this.enclosingRootInfo(movedToPath, IResourceDelta.ADDED);
int movedToType = this.elementType(movedToRes, IResourceDelta.ADDED, element.getParent().getElementType(),
movedToInfo);
// reset current element as it might be inside a nested root
// (popUntilPrefixOf() may use the outer root)
this.currentElement = null;
// create the moved To element
Openable movedToElement = elementType != IRubyElement.RUBY_PROJECT
&& movedToType == IRubyElement.RUBY_PROJECT ? null : // outside
// loadpath
this.createElement(movedToRes, movedToType, rootInfo);
if (movedToElement == null)
{
// moved outside classpath
currentDelta().removed(element);
}
else
{
currentDelta().movedFrom(element, movedToElement);
}
}
switch (elementType)
{
case IRubyElement.RUBY_MODEL:
// this.manager.indexManager.reset();
break;
case IRubyElement.RUBY_PROJECT:
this.state.updateRoots(element.getPath(), delta, this);
// refresh pkg fragment roots and caches of the project (and its dependents)
this.rootsToRefresh.add((IRubyProject) element);
this.projectCachesToReset.add((IRubyProject) element);
break;
case IRubyElement.SOURCE_FOLDER_ROOT:
RubyProject project = (RubyProject) element.getRubyProject();
// refresh src folder roots and caches of the project (and its dependents)
this.rootsToRefresh.add(project);
this.projectCachesToReset.add(project);
break;
case IRubyElement.SOURCE_FOLDER:
// reset sourc folder cache
project = (RubyProject) element.getRubyProject();
this.projectCachesToReset.add(project);
break;
}
}
/*
* Generic processing for elements with changed contents:<ul> <li>The element is closed such that any subsequent
* accesses will re-open the element reflecting its new structure. <li>An entry is made in the delta reporting a
* content change (K_CHANGE with F_CONTENT flag set). </ul> Delta argument could be null if processing an external
* JAR change
*/
private void contentChanged(Openable element)
{
boolean isPrimary = false;
boolean isPrimaryWorkingCopy = false;
if (element.getElementType() == IRubyElement.SCRIPT)
{
RubyScript cu = (RubyScript) element;
isPrimary = cu.isPrimary();
isPrimaryWorkingCopy = isPrimary && cu.isWorkingCopy();
}
if (isPrimaryWorkingCopy)
{
// filter out changes to primary compilation unit in working copy
// mode
// just report a change to the resource (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=59500)
currentDelta().changed(element, IRubyElementDelta.F_PRIMARY_RESOURCE);
}
else
{
close(element);
int flags = IRubyElementDelta.F_CONTENT;
if (isPrimary)
{
flags |= IRubyElementDelta.F_PRIMARY_RESOURCE;
}
currentDelta().changed(element, flags);
}
}
/*
* Returns the type of the ruby element the given delta matches to. Returns NON_RUBY_RESOURCE if unknown (e.g. a
* non-ruby resource or excluded .rb file)
*/
private int elementType(IResource res, int kind, int parentType, RootInfo rootInfo)
{
switch (parentType)
{
case IRubyElement.RUBY_MODEL:
// case of a movedTo or movedFrom project (other cases are handled in processResourceDelta(...)
return IRubyElement.RUBY_PROJECT;
case NON_RUBY_RESOURCE:
case IRubyElement.RUBY_PROJECT:
if (rootInfo == null)
{
rootInfo = this.enclosingRootInfo(res.getFullPath(), kind);
}
if (rootInfo != null && rootInfo.isRootOfProject(res.getFullPath()))
{
return IRubyElement.SOURCE_FOLDER_ROOT;
}
// not yet in a source folder root or root of another project
// or source folder to be included (see below)
// -> let it go through
case IRubyElement.SOURCE_FOLDER_ROOT:
case IRubyElement.SOURCE_FOLDER:
if (rootInfo == null)
{
rootInfo = this.enclosingRootInfo(res.getFullPath(), kind);
}
if (rootInfo == null)
{
return NON_RUBY_RESOURCE;
}
if (Util.isExcluded(res, rootInfo.inclusionPatterns, rootInfo.exclusionPatterns))
{
return NON_RUBY_RESOURCE;
}
if (res.getType() == IResource.FOLDER)
{
if (parentType == NON_RUBY_RESOURCE
&& !Util
.isExcluded(res.getParent(), rootInfo.inclusionPatterns, rootInfo.exclusionPatterns))
// parent is a non-Ruby resource because it doesn't have a valid package name (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=130982)
return NON_RUBY_RESOURCE;
// if (Util.isValidFolderNameForPackage(res.getName())) {
return IRubyElement.SOURCE_FOLDER;
// }
// return NON_RUBY_RESOURCE;
}
String fileName = res.getName();
if (Util.isValidRubyOrERBScriptName(fileName))
{
return IRubyElement.SCRIPT;
}
else if (this.rootInfo(res.getFullPath(), kind) != null)
{
// case of proj=src=bin and resource is a jar file on the classpath
return IRubyElement.SOURCE_FOLDER_ROOT;
}
else
{
return NON_RUBY_RESOURCE;
}
default:
return NON_RUBY_RESOURCE;
}
}
/*
* Answer a combination of the lastModified stamp and the size. Used for detecting external JAR changes
*/
public static long getTimeStamp(File file)
{
return file.lastModified() + file.length();
}
/*
* Update the RubyModel according to a .loadpath file change. The file can have changed as a result of a previous
* call to RubyProject#setRawLoadpath or as a result of some user update (through repository)
*/
private void reconcileLoadpathFileUpdate(IResourceDelta delta, RubyProject project)
{
switch (delta.getKind())
{
case IResourceDelta.REMOVED: // recreate one based on in-memory classpath
try
{
RubyModelManager.PerProjectInfo info = project.getPerProjectInfo();
if (info.rawLoadpath != null)
{ // if there is an in-memory classpath
project.saveLoadpath(info.rawLoadpath, info.outputLocation);
}
}
catch (RubyModelException e)
{
if (project.getProject().isAccessible())
{
Util.log(e, "Could not save loadpath for " + project.getPath());
}
}
break;
case IResourceDelta.CHANGED:
int flags = delta.getFlags();
if ((flags & IResourceDelta.CONTENT) == 0 // only consider content change
&& (flags & IResourceDelta.ENCODING) == 0 // and encoding change
&& (flags & IResourceDelta.MOVED_FROM) == 0)
{// and also move and overide scenario (see http://dev.eclipse.org/bugs/show_bug.cgi?id=21420)
break;
}
// fall through
case IResourceDelta.ADDED:
try
{
project.forceLoadpathReload(null);
}
catch (RuntimeException e)
{
if (VERBOSE)
{
e.printStackTrace();
}
}
catch (RubyModelException e)
{
if (VERBOSE)
{
e.printStackTrace();
}
}
}
}
private void updateIndex(Openable element, IResourceDelta delta)
{
IndexManager indexManager = this.manager.getIndexManager();
if (indexManager == null)
return;
switch (element.getElementType())
{
case IRubyElement.RUBY_PROJECT:
switch (delta.getKind())
{
case IResourceDelta.ADDED:
indexManager.indexAll(element.getRubyProject().getProject());
break;
case IResourceDelta.REMOVED:
indexManager.removeIndexFamily(element.getRubyProject().getProject().getFullPath());
// NB: Discarding index jobs belonging to this project was done during PRE_DELETE
break;
// NB: Update of index if project is opened, closed, or its java nature is added or removed
// is done in updateCurrentDeltaAndIndex
}
break;
case IRubyElement.SOURCE_FOLDER_ROOT:
if (element instanceof ExternalSourceFolderRoot)
{
ExternalSourceFolderRoot root = (ExternalSourceFolderRoot) element;
// index jar file only once (if the root is in its declaring project)
IPath jarPath = root.getPath();
switch (delta.getKind())
{
case IResourceDelta.ADDED:
// index the new jar
indexManager.indexLibrary(jarPath, root.getRubyProject().getProject());
break;
case IResourceDelta.CHANGED:
// first remove the index so that it is forced to be re-indexed
indexManager.removeIndex(jarPath);
// then index the jar
indexManager.indexLibrary(jarPath, root.getRubyProject().getProject());
break;
case IResourceDelta.REMOVED:
// the jar was physically removed: remove the index
indexManager.discardJobs(jarPath.toString());
indexManager.removeIndex(jarPath);
break;
}
break;
}
int kind = delta.getKind();
if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED)
{
SourceFolderRoot root = (SourceFolderRoot) element;
this.updateRootIndex(root, CharOperation.NO_STRINGS, delta);
break;
}
// don't break as packages of the package fragment root can be indexed below
case IRubyElement.SOURCE_FOLDER:
switch (delta.getKind())
{
case IResourceDelta.ADDED:
case IResourceDelta.REMOVED:
ISourceFolder pkg = null;
if (element instanceof ISourceFolderRoot)
{
SourceFolderRoot root = (SourceFolderRoot) element;
pkg = root.getSourceFolder(CharOperation.NO_STRINGS);
}
else
{
pkg = (ISourceFolder) element;
}
RootInfo rootInfo = rootInfo(pkg.getParent().getPath(), delta.getKind());
boolean isSource = rootInfo == null // if null, defaults to source
|| rootInfo.entryKind == ILoadpathEntry.CPE_SOURCE;
IResourceDelta[] children = delta.getAffectedChildren();
for (int i = 0, length = children.length; i < length; i++)
{
IResourceDelta child = children[i];
IResource resource = child.getResource();
// TODO (philippe) Why do this? Every child is added anyway as the delta is walked
if (resource instanceof IFile)
{
String name = resource.getName();
if (isSource)
{
if (org.rubypeople.rdt.internal.core.util.Util.isRubyOrERBLikeFileName(name))
{
Openable cu = (Openable) pkg.getRubyScript(name);
this.updateIndex(cu, child);
}
}
}
}
break;
}
break;
case IRubyElement.SCRIPT:
IFile file = (IFile) delta.getResource();
switch (delta.getKind())
{
case IResourceDelta.CHANGED:
// no need to index if the content has not changed
int flags = delta.getFlags();
if ((flags & IResourceDelta.CONTENT) == 0 && (flags & IResourceDelta.ENCODING) == 0)
break;
case IResourceDelta.ADDED:
indexManager.addSource(file, file.getProject().getFullPath(), getSourceElementParser(element));
// Clean file from secondary types cache but do not update indexing secondary type cache as it
// will be updated through indexing itself
this.manager.secondaryTypesRemoving(file, false);
break;
case IResourceDelta.REMOVED:
indexManager.remove(Util.relativePath(file.getFullPath(), 1/* remove project segment */), file
.getProject().getFullPath());
// Clean file from secondary types cache and update indexing secondary type cache as indexing
// cannot remove secondary types from cache
this.manager.secondaryTypesRemoving(file, true);
break;
}
}
}
private SourceElementParser getSourceElementParser(Openable element)
{
if (this.sourceElementParserCache == null)
this.sourceElementParserCache = this.manager.getIndexManager().getSourceElementParser(
element.getRubyProject(), null/* requestor will be set by indexer */);
return this.sourceElementParserCache;
}
/*
* Updates the index of the given root (assuming it's an addition or a removal). This is done recusively, pkg being
* the current package.
*/
private void updateRootIndex(SourceFolderRoot root, String[] pkgName, IResourceDelta delta)
{
Openable pkg = root.getSourceFolder(pkgName);
this.updateIndex(pkg, delta);
IResourceDelta[] children = delta.getAffectedChildren();
for (int i = 0, length = children.length; i < length; i++)
{
IResourceDelta child = children[i];
IResource resource = child.getResource();
if (resource instanceof IFolder)
{
String[] subpkgName = Util.arrayConcat(pkgName, resource.getName());
this.updateRootIndex(root, subpkgName, child);
}
}
}
/*
* Update the current delta (ie. add/remove/change the given element) and update the correponding index. Returns
* whether the children of the given delta must be processed.
* @throws a RubyModelException if the delta doesn't correspond to a ruby element of the given type.
*/
public boolean updateCurrentDeltaAndIndex(IResourceDelta delta, int elementType, RootInfo rootInfo)
{
Openable element;
switch (delta.getKind())
{
case IResourceDelta.ADDED:
IResource deltaRes = delta.getResource();
element = createElement(deltaRes, elementType, rootInfo);
if (element == null)
{
// resource might be containing shared roots (see bug 19058)
this.state.updateRoots(deltaRes.getFullPath(), delta, this);
return rootInfo != null && rootInfo.inclusionPatterns != null;
}
updateIndex(element, delta);
elementAdded(element, delta, rootInfo);
return elementType == IRubyElement.SOURCE_FOLDER;
case IResourceDelta.REMOVED:
deltaRes = delta.getResource();
element = createElement(deltaRes, elementType, rootInfo);
if (element == null)
{
// resource might be containing shared roots (see bug 19058)
this.state.updateRoots(deltaRes.getFullPath(), delta, this);
return rootInfo != null && rootInfo.inclusionPatterns != null;
}
updateIndex(element, delta);
elementRemoved(element, delta, rootInfo);
if (deltaRes.getType() == IResource.PROJECT)
{
// reset the corresponding project built state, since cannot reuse if added back
if (RubyBuilder.DEBUG)
System.out.println("Clearing last state for removed project : " + deltaRes); //$NON-NLS-1$
this.manager.setLastBuiltState((IProject) deltaRes, null /* no state */);
// clean up previous session containers (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=89850)
this.manager.previousSessionContainers.remove(element);
}
return elementType == IRubyElement.SOURCE_FOLDER;
case IResourceDelta.CHANGED:
int flags = delta.getFlags();
if ((flags & IResourceDelta.CONTENT) != 0 || (flags & IResourceDelta.ENCODING) != 0)
{
// content or encoding has changed
element = createElement(delta.getResource(), elementType, rootInfo);
if (element == null)
return false;
updateIndex(element, delta);
contentChanged(element);
}
else if (elementType == IRubyElement.RUBY_PROJECT)
{
if ((flags & IResourceDelta.OPEN) != 0)
{
// project has been opened or closed
IProject res = (IProject) delta.getResource();
element = createElement(res, elementType, rootInfo);
if (element == null)
{
// resource might be containing shared roots (see bug 19058)
this.state.updateRoots(res.getFullPath(), delta, this);
return false;
}
if (res.isOpen())
{
if (RubyProject.hasRubyNature(res))
{
addToParentInfo(element);
currentDelta().opened(element);
this.state.updateRoots(element.getPath(), delta, this);
// refresh pkg fragment roots and caches of the project (and its dependents)
this.rootsToRefresh.add((IRubyProject) element);
this.projectCachesToReset.add((IRubyProject) element);
this.manager.getIndexManager().indexAll(res);
}
}
else
{
boolean wasJavaProject = this.state.findRubyProject(res.getName()) != null;
if (wasJavaProject)
{
close(element);
removeFromParentInfo(element);
currentDelta().closed(element);
this.manager.getIndexManager().discardJobs(element.getElementName());
this.manager.getIndexManager().removeIndexFamily(res.getFullPath());
}
}
return false; // when a project is open/closed don't process children
}
if ((flags & IResourceDelta.DESCRIPTION) != 0)
{
IProject res = (IProject) delta.getResource();
boolean wasJavaProject = this.state.findRubyProject(res.getName()) != null;
boolean isJavaProject = RubyProject.hasRubyNature(res);
if (wasJavaProject != isJavaProject)
{
// project's nature has been added or removed
element = this.createElement(res, elementType, rootInfo);
if (element == null)
return false; // note its resources are still visible as roots to other projects
if (isJavaProject)
{
elementAdded(element, delta, rootInfo);
this.manager.getIndexManager().indexAll(res);
}
else
{
elementRemoved(element, delta, rootInfo);
this.manager.getIndexManager().discardJobs(element.getElementName());
this.manager.getIndexManager().removeIndexFamily(res.getFullPath());
// reset the corresponding project built state, since cannot reuse if added back
if (RubyBuilder.DEBUG)
System.out.println("Clearing last state for project loosing Java nature: " + res); //$NON-NLS-1$
this.manager.setLastBuiltState(res, null /* no state */);
}
return false; // when a project's nature is added/removed don't process children
}
}
}
return true;
}
return true;
}
/*
* Check all external archive (referenced by given roots, projects or model) status and issue a corresponding root
* delta. Also triggers index updates
*/
public void checkExternalArchiveChanges(IRubyElement[] elementsToRefresh, IProgressMonitor monitor)
throws RubyModelException
{
try
{
for (int i = 0, length = elementsToRefresh.length; i < length; i++)
{
this.addForRefresh(elementsToRefresh[i]);
}
boolean hasDelta = this.createExternalArchiveDelta(monitor);
if (monitor != null && monitor.isCanceled())
return;
if (hasDelta)
{
// force loadpath marker refresh of affected projects
RubyModel.flushExternalFileCache();
IRubyElementDelta[] projectDeltas = this.currentDelta.getAffectedChildren();
final int length = projectDeltas.length;
// final IProject[] projectsToTouch = new IProject[length];
for (int i = 0; i < length; i++)
{
IRubyElementDelta delta = projectDeltas[i];
RubyProject rubyProject = (RubyProject) delta.getElement();
rubyProject.getResolvedLoadpath(true/* ignoreUnresolvedEntry */, true/* generateMarkerOnError */,
false/* don't returnResolutionInProgress */);
// projectsToTouch[i] = rubyProject.getProject();
}
// no need to rebuild if external folders/files change
// // touch the projects to force them to be recompiled while taking the workspace lock
// // so that there is no concurrency with the Java builder
// // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96575
// IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
// public void run(IProgressMonitor progressMonitor) throws CoreException {
// for (int i = 0; i < length; i++) {
// IProject project = projectsToTouch[i];
//
// // touch to force a build of this project
// if (RubyBuilder.DEBUG)
// System.out.println("Touching project " + project.getName() + " due to external jar file change"); //$NON-NLS-1$ //$NON-NLS-2$
// project.touch(progressMonitor);
// }
// }
// };
// try {
// ResourcesPlugin.getWorkspace().run(runnable, monitor);
// } catch (CoreException e) {
// throw new RubyModelException(e);
// }
if (this.currentDelta != null)
{ // if delta has not been fired while creating markers
this.fire(this.currentDelta, DEFAULT_CHANGE_EVENT);
}
}
}
finally
{
this.currentDelta = null;
if (monitor != null)
monitor.done();
}
}
/*
* Check if external archives have changed and create the corresponding deltas. Returns whether at least on delta
* was created.
*/
private boolean createExternalArchiveDelta(IProgressMonitor monitor)
{
if (this.refreshedElements == null)
return false;
HashMap<IPath, String> externalArchivesStatus = new HashMap<IPath, String>();
boolean hasDelta = false;
// find JARs to refresh
HashSet<IPath> archivePathsToRefresh = new HashSet<IPath>();
Iterator<IRubyElement> iterator = this.refreshedElements.iterator();
this.refreshedElements = null; // null out early to avoid concurrent modification exception (see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=63534)
while (iterator.hasNext())
{
IRubyElement element = (IRubyElement) iterator.next();
switch (element.getElementType())
{
case IRubyElement.SOURCE_FOLDER_ROOT:
archivePathsToRefresh.add(element.getPath());
break;
case IRubyElement.RUBY_PROJECT:
RubyProject javaProject = (RubyProject) element;
if (!RubyProject.hasRubyNature(javaProject.getProject()))
{
// project is not accessible or has lost its Ruby nature
break;
}
ILoadpathEntry[] classpath;
try
{
classpath = javaProject.getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/*
* don't
* generateMarkerOnError
*/, false/*
* don't
* returnResolutionInProgress
*/);
for (int j = 0, cpLength = classpath.length; j < cpLength; j++)
{
if (classpath[j].getEntryKind() == ILoadpathEntry.CPE_LIBRARY)
{
archivePathsToRefresh.add(classpath[j].getPath());
}
}
}
catch (RubyModelException e)
{
// project doesn't exist -> ignore
}
break;
case IRubyElement.RUBY_MODEL:
Iterator<String> projectNames = this.state.getOldRubyProjecNames().iterator();
while (projectNames.hasNext())
{
String projectName = projectNames.next();
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
if (!RubyProject.hasRubyNature(project))
{
// project is not accessible or has lost its Ruby nature
continue;
}
javaProject = (RubyProject) RubyCore.create(project);
try
{
classpath = javaProject
.getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/*
* don't
* generateMarkerOnError
*/, false/*
* don't
* returnResolutionInProgress
*/);
}
catch (RubyModelException e2)
{
// project doesn't exist -> ignore
continue;
}
for (int k = 0, cpLength = classpath.length; k < cpLength; k++)
{
if (classpath[k].getEntryKind() == ILoadpathEntry.CPE_LIBRARY)
{
archivePathsToRefresh.add(classpath[k].getPath());
}
}
}
break;
}
}
// perform refresh
Iterator<String> projectNames = this.state.getOldRubyProjecNames().iterator();
IWorkspaceRoot wksRoot = ResourcesPlugin.getWorkspace().getRoot();
while (projectNames.hasNext())
{
if (monitor != null && monitor.isCanceled())
break;
String projectName = projectNames.next();
IProject project = wksRoot.getProject(projectName);
if (!RubyProject.hasRubyNature(project))
{
// project is not accessible or has lost its Ruby nature
continue;
}
RubyProject javaProject = (RubyProject) RubyCore.create(project);
ILoadpathEntry[] entries;
try
{
entries = javaProject.getResolvedLoadpath(true/* ignoreUnresolvedEntry */, false/*
* don't
* generateMarkerOnError
*/, false/*
* don't
* returnResolutionInProgress
*/);
}
catch (RubyModelException e1)
{
// project does not exist -> ignore
continue;
}
for (int j = 0; j < entries.length; j++)
{
if (entries[j].getEntryKind() == ILoadpathEntry.CPE_LIBRARY)
{
IPath entryPath = entries[j].getPath();
if (!archivePathsToRefresh.contains(entryPath))
continue; // not supposed to be refreshed
String status = (String) externalArchivesStatus.get(entryPath);
if (status == null)
{
// compute shared status
Object targetLibrary = RubyModel.getTarget(entryPath, true);
if (targetLibrary == null)
{ // missing JAR
if (this.state.getExternalLibTimeStamps().remove(entryPath) != null)
{
externalArchivesStatus.put(entryPath, EXTERNAL_JAR_REMOVED);
// the jar was physically removed: remove the index
this.manager.indexManager.removeIndex(entryPath);
}
}
else if (targetLibrary instanceof File)
{ // external JAR
File externalFile = (File) targetLibrary;
// check timestamp to figure if JAR has changed in some way
Long oldTimestamp = (Long) this.state.getExternalLibTimeStamps().get(entryPath);
long newTimeStamp = getTimeStamp(externalFile);
if (oldTimestamp != null)
{
if (newTimeStamp == 0)
{ // file doesn't exist
externalArchivesStatus.put(entryPath, EXTERNAL_JAR_REMOVED);
this.state.getExternalLibTimeStamps().remove(entryPath);
// remove the index
this.manager.indexManager.removeIndex(entryPath);
}
else if (oldTimestamp.longValue() != newTimeStamp)
{
externalArchivesStatus.put(entryPath, EXTERNAL_JAR_CHANGED);
this.state.getExternalLibTimeStamps().put(entryPath, new Long(newTimeStamp));
// first remove the index so that it is forced to be re-indexed
this.manager.indexManager.removeIndex(entryPath);
// then index the jar
this.manager.indexManager.indexLibrary(entryPath, project.getProject());
}
else
{
externalArchivesStatus.put(entryPath, EXTERNAL_JAR_UNCHANGED);
}
}
else
{
if (newTimeStamp == 0)
{ // jar still doesn't exist
externalArchivesStatus.put(entryPath, EXTERNAL_JAR_UNCHANGED);
}
else
{
externalArchivesStatus.put(entryPath, EXTERNAL_JAR_ADDED);
this.state.getExternalLibTimeStamps().put(entryPath, new Long(newTimeStamp));
// index the new jar
this.manager.indexManager.indexLibrary(entryPath, project.getProject());
}
}
}
else
{ // internal JAR
externalArchivesStatus.put(entryPath, INTERNAL_JAR_IGNORE);
}
}
// according to computed status, generate a delta
status = (String) externalArchivesStatus.get(entryPath);
if (status != null)
{
if (status == EXTERNAL_JAR_ADDED)
{
SourceFolderRoot root = (SourceFolderRoot) javaProject.getSourceFolderRoot(entryPath
.toString());
if (VERBOSE)
{
System.out.println("- External JAR ADDED, affecting root: " + root.getElementName()); //$NON-NLS-1$
}
elementAdded(root, null, null);
hasDelta = true;
}
else if (status == EXTERNAL_JAR_CHANGED)
{
SourceFolderRoot root = (SourceFolderRoot) javaProject.getSourceFolderRoot(entryPath
.toString());
if (VERBOSE)
{
System.out.println("- External JAR CHANGED, affecting root: " + root.getElementName()); //$NON-NLS-1$
}
contentChanged(root);
hasDelta = true;
}
else if (status == EXTERNAL_JAR_REMOVED)
{
SourceFolderRoot root = (SourceFolderRoot) javaProject.getSourceFolderRoot(entryPath
.toString());
if (VERBOSE)
{
System.out.println("- External JAR REMOVED, affecting root: " + root.getElementName()); //$NON-NLS-1$
}
elementRemoved(root, null, null);
hasDelta = true;
}
}
}
}
}
return hasDelta;
}
}