package com.aptana.ruby.debug.ui;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
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.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.Position;
import org.eclipse.ui.texteditor.AbstractMarkerAnnotationModel;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import com.aptana.ruby.internal.debug.core.breakpoints.RubyLineBreakpoint;
/**
* A marker annotation model whose underlying source of markers is the workspace root. This uses our hack of a special
* attribute name to point to the real absolute filepath of the file we want the marker for.
* <p>
* This class may be instantiated; it is not intended to be subclassed.
* </p>
*
* @noextend This class is not intended to be subclassed by clients.
*/
public class ExternalRubyFileAnnotationModel extends AbstractMarkerAnnotationModel
{
/**
* Internal resource change listener.
*/
class ResourceChangeListener implements IResourceChangeListener
{
/*
* @see IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
*/
public void resourceChanged(IResourceChangeEvent e)
{
IResourceDelta delta = e.getDelta();
if (delta != null && fResource != null)
{
IResourceDelta child = delta.findMember(fResource.getFullPath());
if (child != null)
update(child.getMarkerDeltas());
}
}
}
/** The workspace. */
private IWorkspace fWorkspace;
/** The resource. */
private IResource fResource;
/** The resource change listener. */
private IResourceChangeListener fResourceChangeListener = new ResourceChangeListener();
private IPath fPath;
/**
* Creates a marker annotation model with the given resource as the source of the markers.
*
* @param resource
* the absolute path of a file external to the workspace whose breakpoints we want to listen for and
* display in the ruler
*/
public ExternalRubyFileAnnotationModel(IPath resource)
{
Assert.isNotNull(resource);
fPath = resource;
fResource = ResourcesPlugin.getWorkspace().getRoot();
fWorkspace = ResourcesPlugin.getWorkspace();
listenToMarkerChanges(true);
}
/*
* @see AbstractMarkerAnnotationModel#isAcceptable(IMarker)
*/
protected boolean isAcceptable(IMarker marker)
{
return marker != null && fResource.equals(marker.getResource()) && sameFile(marker);
}
private boolean sameFile(IMarker marker)
{
String filename = marker.getAttribute(RubyLineBreakpoint.EXTERNAL_FILENAME, (String) null);
if (filename == null)
return false;
return fPath.equals(Path.fromPortableString(filename));
}
/**
* Updates this model to the given marker deltas.
*
* @param markerDeltas
* the array of marker deltas
*/
protected void update(IMarkerDelta[] markerDeltas)
{
if (markerDeltas.length == 0)
return;
if (markerDeltas.length == 1)
{
IMarkerDelta delta = markerDeltas[0];
switch (delta.getKind())
{
case IResourceDelta.ADDED:
addMarkerAnnotation(delta.getMarker());
break;
case IResourceDelta.REMOVED:
removeMarkerAnnotation(delta.getMarker());
break;
case IResourceDelta.CHANGED:
modifyMarkerAnnotation(delta.getMarker());
break;
}
}
else
batchedUpdate(markerDeltas);
fireModelChanged();
}
/**
* Updates this model to the given marker deltas.
*
* @param markerDeltas
* the array of marker deltas
*/
private void batchedUpdate(IMarkerDelta[] markerDeltas)
{
HashSet<IMarker> removedMarkers = new HashSet<IMarker>(markerDeltas.length);
HashSet<IMarker> modifiedMarkers = new HashSet<IMarker>(markerDeltas.length);
for (int i = 0; i < markerDeltas.length; i++)
{
IMarkerDelta delta = markerDeltas[i];
switch (delta.getKind())
{
case IResourceDelta.ADDED:
addMarkerAnnotation(delta.getMarker());
break;
case IResourceDelta.REMOVED:
removedMarkers.add(delta.getMarker());
break;
case IResourceDelta.CHANGED:
modifiedMarkers.add(delta.getMarker());
break;
}
}
if (modifiedMarkers.isEmpty() && removedMarkers.isEmpty())
return;
@SuppressWarnings("rawtypes")
Iterator e = getAnnotationIterator(false);
while (e.hasNext())
{
Object o = e.next();
if (o instanceof MarkerAnnotation)
{
MarkerAnnotation a = (MarkerAnnotation) o;
IMarker marker = a.getMarker();
if (removedMarkers.remove(marker))
removeAnnotation(a, false);
if (modifiedMarkers.remove(marker))
{
Position p = createPositionFromMarker(marker);
if (p != null)
{
a.update();
modifyAnnotationPosition(a, p, false);
}
}
if (modifiedMarkers.isEmpty() && removedMarkers.isEmpty())
return;
}
}
Iterator<IMarker> iter = modifiedMarkers.iterator();
while (iter.hasNext())
addMarkerAnnotation(iter.next());
}
/*
* @see AbstractMarkerAnnotationModel#listenToMarkerChanges(boolean)
*/
protected void listenToMarkerChanges(boolean listen)
{
if (listen)
fWorkspace.addResourceChangeListener(fResourceChangeListener);
else
fWorkspace.removeResourceChangeListener(fResourceChangeListener);
}
/*
* @see AbstractMarkerAnnotationModel#deleteMarkers(IMarker[])
*/
protected void deleteMarkers(final IMarker[] markers) throws CoreException
{
fWorkspace.run(new IWorkspaceRunnable()
{
public void run(IProgressMonitor monitor) throws CoreException
{
for (int i = 0; i < markers.length; ++i)
{
markers[i].delete();
}
}
}, null, IWorkspace.AVOID_UPDATE, null);
}
/*
* @see AbstractMarkerAnnotationModel#retrieveMarkers()
*/
protected IMarker[] retrieveMarkers() throws CoreException
{
// Limit to those markers with the same filename!
List<IMarker> filtered = new ArrayList<IMarker>();
IMarker[] markers = fResource.findMarkers(IMarker.MARKER, true, IResource.DEPTH_ZERO);
for (IMarker marker : markers)
{
if (!sameFile(marker))
{
continue;
}
filtered.add(marker);
}
return filtered.toArray(new IMarker[filtered.size()]);
}
}