package net.sourceforge.tagsea.java.waypoints;
import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.sourceforge.tagsea.AbstractWaypointDelegate;
import net.sourceforge.tagsea.TagSEAPlugin;
import net.sourceforge.tagsea.core.BasicWaypointIdentifier;
import net.sourceforge.tagsea.core.ITag;
import net.sourceforge.tagsea.core.IWaypoint;
import net.sourceforge.tagsea.core.IWaypointChangeEvent;
import net.sourceforge.tagsea.core.IWaypointIdentifier;
import net.sourceforge.tagsea.core.TagDelta;
import net.sourceforge.tagsea.core.TagSEAChangeStatus;
import net.sourceforge.tagsea.core.TagSEAOperation;
import net.sourceforge.tagsea.core.WaypointDelta;
import net.sourceforge.tagsea.java.IJavaWaypointAttributes;
import net.sourceforge.tagsea.java.IJavaWaypointsConstants;
import net.sourceforge.tagsea.java.JavaTagsPlugin;
import net.sourceforge.tagsea.java.JavaWaypointUtils;
import net.sourceforge.tagsea.java.resources.internal.FileWaypointRefreshJob;
import net.sourceforge.tagsea.java.resources.internal.JavaResourceListener;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.texteditor.ITextEditor;
public class JavaWaypointDelegate extends AbstractWaypointDelegate {
/**
* Map of waypoints to markers.
*/
private HashMap<IWaypoint, IMarker> waypointMarkerMap;
private HashMap<IMarker, IWaypoint> markerWaypointMap;
/**
* List of buffers to listen for changes on.
*/
private HashMap<IDocument, ITextFileBuffer> documentBufferMap;
private JavaDocumentChangedListener documentChangedListener;
/**
* Flag to be set if refactoring events should be listened for.
*/
//private boolean shouldRefactor;
/**
* Flag to be set if document changes should be listened for.
*/
// private boolean listenToDocuments;
private IResourceChangeListener resourceListener;
/**
* An enumeration indicating the current state of the delegate: whether it is
* handling internal changes (and therefore expects changes to waypoints), is
* idle, or is changing waypoints itself.
* @author Del Myers
*/
private enum JavaWaypointState {
/**
* The delegate is idle.
*/
IDLE,
/**
* The delegate is updating waypoints, and expects changes.
*/
UPDATING,
/**
* The delegate is refactoring, and doesn't expect changes.
*/
REFACTORING
}
private JavaWaypointState state;
/**
* A stack of previous states so that they can be restored.
*/
private LinkedList<JavaWaypointState> stateStack;
public JavaWaypointDelegate() {
waypointMarkerMap = new HashMap<IWaypoint, IMarker>();
markerWaypointMap = new HashMap<IMarker, IWaypoint>();
documentBufferMap = new HashMap<IDocument, ITextFileBuffer>();
documentChangedListener = new JavaDocumentChangedListener();
//shouldRefactor = true;
// listenToDocuments = true;
this.state = JavaWaypointState.IDLE;
this.stateStack = new LinkedList<JavaWaypointState>();
}
private class JavaDocumentChangedListener implements IDocumentListener {
/* (non-Javadoc)
* @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent event) {
}
/* (non-Javadoc)
* @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
*/
public void documentChanged(DocumentEvent event) {
// if (!listenToDocuments) return;
//we are refactoring, don't listen for document changes.
if (state == JavaWaypointState.REFACTORING) return;
//note: refactor operations run inside the ui thread, so document changes can't happen at the same time.
TagSEAPlugin.run(new DocumentChangeOperation(event, documentBufferMap.get(event.fDocument)), false);
}
}
/**
* Object used to synchronize refactorings.
*/
private static final Object REFACTORLOCK = new Object();
/**
* A runs refactoring operations inside the UI thread.
* @author Del Myers
*/
private class RefactoringOperation implements Runnable {
private WaypointDelta delta;
public RefactoringOperation(WaypointDelta delta) {
this.delta = delta;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
//remove the resource listener.
synchronized (REFACTORLOCK) {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceListener);
Shell shell =
JavaTagsPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell();
ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
// boolean oldDocumentValue;
// boolean oldRefactorValue;
//@tag tagsea.buggy : this could cause bugs later. Check here if inconsistency occurs in the documents.
// oldDocumentValue = listenToDocuments;
// listenToDocuments = false;
// oldRefactorValue = shouldRefactor;
// shouldRefactor = false;
if (state != JavaWaypointState.IDLE) {
JavaTagsPlugin.getDefault().getLog().log(new Status(
IStatus.WARNING,
JavaTagsPlugin.PLUGIN_ID,
IStatus.WARNING,
"Refactoring occurred while delegate in " + state.toString(),
null
));
}
pushState(JavaWaypointState.REFACTORING);
try {
dialog.run(true, false, getDialogRunner());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
popState();
//add the listener back again.
ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceListener);
}
}
/**
* Returns the operation that will be run within the dialog.
* @return
*/
private IRunnableWithProgress getDialogRunner() {
return new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
monitor.beginTask("Refactoring... ", 150);
// check for synchronization
DirtyEditorSynchronizer synchronizer = new DirtyEditorSynchronizer(delta);
synchronizer.run(new SubProgressMonitor(monitor, 50, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK));
ModelChangeOperation refacteror = new ModelChangeOperation(synchronizer.getWaypointChanges());
refacteror.run(new SubProgressMonitor(monitor, 100, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK));
}
};
}
public void start() {
synchronized (REFACTORLOCK) {
Display.getDefault().asyncExec(this);
}
}
}
/**
* An internal operation that is run to ensure that refactoring doesn't occur when the Java
* plugin makes changes to the model.
* @author Del Myers
*/
public static abstract class InternalOperation extends TagSEAOperation {
public InternalOperation() {
super();
}
public InternalOperation(String name) {
super(name);
}
/* (non-Javadoc)
* @see net.sourceforge.tagsea.core.TagSEAOperation#operationDone()
*/
@Override
public void operationDone() {
super.operationDone();
JavaWaypointDelegate delegate =
(JavaWaypointDelegate) TagSEAPlugin.getDefault().getWaypointDelegate(JavaTagsPlugin.JAVA_WAYPOINT);
delegate.popState();
}
/* (non-Javadoc)
* @see net.sourceforge.tagsea.core.TagSEAOperation#operationStarted()
*/
@Override
public void operationStarted() {
super.operationStarted();
JavaWaypointDelegate delegate =
(JavaWaypointDelegate) TagSEAPlugin.getDefault().getWaypointDelegate(JavaTagsPlugin.JAVA_WAYPOINT);
delegate.pushState(JavaWaypointState.UPDATING);
}
}
@Override
public void load() {
//@tag tagsea.todo : load some previously saved data about java waypoints.
for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
if (project.isAccessible()) {
TagSEAPlugin.run(new LoadWaypointsOperation(project), false);
}
}
//LoadWaypointsJob job = new LoadWaypointsJob();
resourceListener = new JavaResourceListener();
ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceListener);
// job.addJobChangeListener(new IJobChangeListener(){
// public void aboutToRun(IJobChangeEvent event) {
// }
// public void awake(IJobChangeEvent event) {
// }
// public void done(IJobChangeEvent event) {
// //add the resource listener when the job is finished.
// ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceListener);
//
// }
// public void running(IJobChangeEvent event) {
// }
// public void scheduled(IJobChangeEvent event) {
// }
// public void sleeping(IJobChangeEvent event) {
// }
// });
// job.schedule();
FileBuffers.getTextFileBufferManager().addFileBufferListener(new JavaFileBufferListener());
}
@Override
public void navigate(final IWaypoint waypoint) {
Display.getDefault().asyncExec(new Runnable(){
public void run() {
IFile file = JavaWaypointUtils.getFile(waypoint);
if (file != null && file.exists()) {
IWorkbenchPage page =
JavaTagsPlugin.
getDefault().
getWorkbench().
getActiveWorkbenchWindow().
getActivePage();
try {
if (page == null) return;
IEditorPart part = IDE.openEditor(page, file);
if (part instanceof ITextEditor) {
((ITextEditor)part).selectAndReveal(JavaWaypointUtils.getOffset(waypoint), JavaWaypointUtils.getLength(waypoint));
}
} catch (PartInitException e) {
}
}
}
});
}
public void tagsChanged(TagDelta delta) {
}
public void waypointsChanged(WaypointDelta delta) {
//can't do anything if we are closing
//@tag tagsea.bug.78.fix
if (PlatformUI.getWorkbench().isClosing()) return;
for (IWaypointChangeEvent event : delta.changes) {
switch (event.getType()) {
case IWaypointChangeEvent.NEW:
try {
handleNew(event.getWaypoint());
} catch (CoreException e1) {
JavaTagsPlugin.getDefault().log(e1);
}
break;
case IWaypointChangeEvent.DELETE:
handleDelete(event.getWaypoint());
break;
case IWaypointChangeEvent.CHANGE:
try {
handleChange(event);
} catch (CoreException e) {
JavaTagsPlugin.getDefault().log(e);
}
break;
}
}
if (state == JavaWaypointState.IDLE) {
new RefactoringOperation(delta).start();
}
}
/**
* @param event
* @throws CoreException
*/
@SuppressWarnings({"unchecked"}) //$NON-NLS-1$
private void handleChange(IWaypointChangeEvent event) throws CoreException {
if (!event.getWaypoint().exists()) return;
//do simple changes with the markers.
String[] attributes = event.getChangedAttributes();
Map newMarkerAttributes = new HashMap();
int newEnd = event.getWaypoint().getIntValue(IJavaWaypointAttributes.ATTR_CHAR_END, 0);
//int newStart = event.getWaypoint().getIntValue(IJavaWaypointAttributes.ATTR_CHAR_START, 1);
boolean locationChanged = false;
for (String attribute : attributes) {
if (IJavaWaypointAttributes.ATTR_CHAR_START.equals(attribute)) {
newMarkerAttributes.put(IMarker.CHAR_START, event.getNewValue(attribute));
//newStart = (Integer)event.getNewValue(attribute);
locationChanged = true;
} else if (IJavaWaypointAttributes.ATTR_CHAR_END.equals(attribute)) {
newEnd = (Integer)event.getNewValue(attribute);
locationChanged = true;
} else if (IWaypoint.ATTR_MESSAGE.equals(attribute)) {
newMarkerAttributes.put(IMarker.MESSAGE, event.getNewValue(attribute));
} else if (IJavaWaypointAttributes.ATTR_RESOURCE.equals(attribute)) {
if (waypointMarkerMap.get(event.getWaypoint()) == null) {
createNewMarker(event.getWaypoint());
}
} else if (IWaypoint.ATTR_DATE.equals(attribute)) {
Date date = event.getWaypoint().getDate();
if (date != null) {
Locale loc = Locale.getDefault();
String s = loc.getLanguage()+loc.getCountry()+":"+DateFormat.getDateInstance().format(date); //$NON-NLS-1$
newMarkerAttributes.put(IWaypoint.ATTR_DATE, s);
}
}
}
ITag[] tags = event.getWaypoint().getTags();
String tagString = ""; //$NON-NLS-1$
for (ITag tag : tags) {
tagString += tag.getName() + " "; //$NON-NLS-1$
}
newMarkerAttributes.put("tags", tagString); //$NON-NLS-1$
if (locationChanged) {
newMarkerAttributes.put(IMarker.CHAR_END, new Integer(newEnd));
}
IMarker marker = waypointMarkerMap.get(event.getWaypoint());
Map markerMap = marker.getAttributes();
for (Object key : newMarkerAttributes.keySet()) {
markerMap.put(key, newMarkerAttributes.get(key));
}
marker.setAttributes(markerMap);
}
/**
* @param waypoint
*/
@SuppressWarnings({"unchecked"}) //$NON-NLS-1$
private void createNewMarker(IWaypoint waypoint) throws CoreException {
String fileName = waypoint.getStringValue(IJavaWaypointAttributes.ATTR_RESOURCE, ""); //$NON-NLS-1$
if (!"".equals(fileName)) { //$NON-NLS-1$
IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(fileName));
//can't create a marker while the tree is locked.
if (file.getWorkspace().isTreeLocked()) {
throw new CoreException(new Status(
Status.WARNING,
JavaTagsPlugin.PLUGIN_ID,
Status.WARNING,
"Could not create marker for Java Waypoint",
null
));
}
HashMap attrs = new HashMap();
int start = waypoint.getIntValue(IJavaWaypointAttributes.ATTR_CHAR_START, 1);
int end = waypoint.getIntValue(IJavaWaypointAttributes.ATTR_CHAR_END,1);
String message = waypoint.getText();
attrs.put(IMarker.CHAR_START, start);
attrs.put(IMarker.MESSAGE, message);
attrs.put(IMarker.CHAR_END, end);
attrs.put("waypointType", JavaTagsPlugin.JAVA_WAYPOINT);
ITag[] tags = waypoint.getTags();
String tagString = ""; //$NON-NLS-1$
for (ITag tag : tags) {
tagString += tag.getName() + " "; //$NON-NLS-1$
}
attrs.put("tags", tagString); //$NON-NLS-1$
Date date = waypoint.getDate();
if (date != null) {
Locale loc = Locale.getDefault();
String s = loc.getLanguage()+loc.getCountry()+":"+DateFormat.getDateInstance().format(date); //$NON-NLS-1$
attrs.put(IWaypoint.ATTR_DATE, s);
}
if (file != null && file.exists()) {
if (PlatformUI.getWorkbench().isClosing()) return;
IMarker marker = file.createMarker(IJavaWaypointsConstants.WAYPOINT_MARKER);
if (marker != null && marker.exists()) {
marker.setAttributes(attrs);
waypointMarkerMap.put(waypoint, marker);
markerWaypointMap.put(marker, waypoint);
}
}
}
}
/**
* @param waypoint
*/
private void handleDelete(IWaypoint waypoint) {
//remove the marker for the given waypoint.
IMarker marker = waypointMarkerMap.get(waypoint);
waypointMarkerMap.remove(waypoint);
markerWaypointMap.remove(marker);
if (marker != null && marker.exists()) {
try {
marker.delete();
} catch (CoreException e) {
e.printStackTrace();
}
}
}
/**
* @param waypoint
* @throws CoreException
*/
private void handleNew(IWaypoint waypoint) throws CoreException {
createNewMarker(waypoint);
}
protected void addFileBuffer(ITextFileBuffer buffer) {
documentBufferMap.put(buffer.getDocument(), buffer);
buffer.getDocument().addDocumentListener(documentChangedListener);
}
protected void removeFileBuffer(ITextFileBuffer buffer) {
documentBufferMap.put(buffer.getDocument(), buffer);
buffer.getDocument().removeDocumentListener(documentChangedListener);
if (buffer.isDirty()) {
IFile file = FileBuffers.getWorkspaceFileAtLocation(buffer.getLocation());
//@tag tagsea.bug.150.fix : if the file is referenced from outside the workspace, null will be returned. We don't track files outside the workspace.
if (file != null) {
LinkedList<IFile> files = new LinkedList<IFile>();
files.add(file);
TagSEAPlugin.run(new FileWaypointRefreshJob(files), false);
}
}
}
// /**
// * Runs the given operation internally so that refactor events won't occur due to changes
// * in the model.
// * @param op
// */
// public void internalRun(TagSEAOperation op, boolean wait) {
// TagSEAPlugin.run(new InternalOperation(op), wait);
// }
//
/**
* Checks for equality between all of the attributes of the waypoints except the length
* of it.
*/
@Override
public boolean waypointsEqual(IWaypoint wp0, IWaypoint wp1) {
boolean equal = true;
for (String name : wp0.getAttributes()) {
if (name.equals(IJavaWaypointAttributes.ATTR_CHAR_END)) continue;
Object value1 = wp0.getValue(name);
Object value2 = wp1.getValue(name);
equal &= (value1.equals(value2));
if (!equal)
break;
}
return equal;
}
/* (non-Javadoc)
* @see net.sourceforge.tagsea.AbstractWaypointDelegate#computeWaypointIdentifier(net.sourceforge.tagsea.core.IWaypoint)
*/
@Override
public IWaypointIdentifier computeWaypointIdentifier(IWaypoint waypoint) {
return new BasicWaypointIdentifier(waypoint);
}
/* (non-Javadoc)
* @see net.sourceforge.tagsea.AbstractWaypointDelegate#processChange(net.sourceforge.tagsea.model.internal.WaypointChangeEvent)
*/
@Override
public TagSEAChangeStatus processChange(IWaypointChangeEvent event) {
if (this.state == JavaWaypointState.REFACTORING) {
return new TagSEAChangeStatus(JavaTagsPlugin.PLUGIN_ID, false, JavaTagsPlugin.CONCURRENT_CHANGE_ERROR, "Could not perform waypoint change due to concurrent changes in the java waypoint platform.");
}
if (this.state == JavaWaypointState.IDLE) {
List<String> attributes = Arrays.asList(event.getChangedAttributes());
if (attributes.contains(IJavaWaypointAttributes.ATTR_CHAR_START) ||
attributes.contains(IJavaWaypointAttributes.ATTR_RESOURCE) ||
attributes.contains(IJavaWaypointAttributes.ATTR_CHAR_END)) {
//System.err.println("Big Mistake!");
return new TagSEAChangeStatus(JavaTagsPlugin.PLUGIN_ID, false, JavaTagsPlugin.ILLEGAL_CHANGE_ERROR, "Attribute cannot be changed outside the Java Waypoints platform.");
}
}
return super.processChange(event);
}
/**
* Sets the current state to the given state, and saves the last one on the stack.
* @param state
*/
private synchronized void pushState(JavaWaypointState state) {
stateStack.add(this.state);
this.state = state;
}
/**
* Resets the state of tehdelegate to the previous state.
*/
private synchronized void popState() {
this.state = stateStack.removeLast();
}
public IWaypoint getWaypointForMarker(IMarker marker) {
return markerWaypointMap.get(marker);
}
public IMarker getMarkerForWaypoint(IWaypoint waypoint) {
return waypointMarkerMap.get(waypoint);
}
}