package com.redhat.ceylon.eclipse.code.editor;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.ISuspendResume;
import org.eclipse.debug.ui.actions.IRunToLineTarget;
import org.eclipse.debug.ui.actions.IToggleBreakpointsTarget;
import org.eclipse.debug.ui.actions.RunToLineHandler;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.debug.core.IJavaStratumLineBreakpoint;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.debug.ui.IJavaDebugUIConstants;
import org.eclipse.jdt.internal.debug.ui.BreakpointUtils;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.ide.FileStoreEditorInput;
import com.redhat.ceylon.model.typechecker.model.Unit;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
import com.redhat.ceylon.eclipse.core.builder.CeylonBuilder;
import com.redhat.ceylon.eclipse.core.external.ExternalSourceArchiveManager;
import com.redhat.ceylon.eclipse.ui.CeylonPlugin;
import com.redhat.ceylon.ide.common.model.IResourceAware;
public class ToggleBreakpointAdapter implements IToggleBreakpointsTarget, IRunToLineTarget {
private static final String JDT_DEBUG_PLUGIN_ID = "org.eclipse.jdt.debug";
public ToggleBreakpointAdapter() {}
static interface BreakpointAction {
void doIt(ITextSelection selection, Map.Entry<Integer, Node> firstValidLocation, IFile sourceFile) throws CoreException;
}
public void doOnBreakpoints(IWorkbenchPart part, ISelection selection, BreakpointAction action) throws CoreException {
if (selection instanceof ITextSelection) {
ITextSelection textSel= (ITextSelection) selection;
IEditorPart editorPart= (IEditorPart) part.getAdapter(IEditorPart.class);
//TODO: handle org.eclipse.ui.ide.FileStoreEditorInput
// to set breakpoints in code from archives
IEditorInput editorInput = editorPart.getEditorInput();
final IFile origSrcFile = getSourceFile(editorInput);
int line = textSel.getStartLine();
boolean emptyLine = true;
Map.Entry<Integer, Node> location = null;
try {
CeylonEditor editor = (CeylonEditor) editorPart;
IDocument document = editor.getCeylonSourceViewer().getDocument();
int lineCount = document.getNumberOfLines();
String text = null;
while (line < lineCount) {
final IRegion lineInformation = document.getLineInformation(line);
text = document.get(lineInformation.getOffset(),
lineInformation.getLength()).trim();
if (! text.isEmpty()) {
emptyLine = false;
break;
}
line ++;
};
if (!emptyLine) {
Tree.CompilationUnit rootNode = editor.getParseController().getLastCompilationUnit();
if (rootNode != null) {
location = getFirstValidLocation(rootNode, document, textSel);
}
}
} catch (Exception e) {
e.printStackTrace();
}
action.doIt(textSel, location, origSrcFile);
}
}
public void toggleLineBreakpoints(IWorkbenchPart part, ISelection selection) throws CoreException {
doOnBreakpoints(part, selection, new BreakpointAction() {
@Override
public void doIt(final ITextSelection selection, final Map.Entry<Integer, Node> firstValidLocation,
final IFile sourceFile) throws CoreException {
final int requestedLineNumber = selection.getStartLine();
IWorkspaceRunnable wr = new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
IMarker marker = findBreakpointMarker(sourceFile, requestedLineNumber + 1);
if (marker!=null) {
// The following will delete the associated marker
clearLineBreakpoint(sourceFile, requestedLineNumber + 1);
} else if (firstValidLocation != null) {
// The following will create a marker as a side-effect
createLineBreakpoint(sourceFile, firstValidLocation.getKey() + 1, null, false);
}
}
};
try {
getWorkspace().run(wr, null);
}
catch (CoreException e) {
throw new DebugException(e.getStatus());
}
}
});
}
private IFile getSourceFile(IEditorInput editorInput) {
final IFile origSrcFile;
if (editorInput instanceof IFileEditorInput) {
origSrcFile= ((IFileEditorInput)editorInput).getFile();
} else if (editorInput instanceof FileStoreEditorInput) {
URI uri = ((FileStoreEditorInput) editorInput).getURI();
IResource resource = ExternalSourceArchiveManager.toResource(uri);
if (resource instanceof IFile) {
origSrcFile = (IFile) resource;
} else {
origSrcFile = null;
}
} else {
origSrcFile = null;
}
return origSrcFile;
}
private static Map.Entry<Integer, Node> getFirstValidLocation(Tree.CompilationUnit rootNode,
final IDocument document, final ITextSelection textSelection) {
class BreakpointVisitor extends Visitor {
TreeMap<Integer, Node> nodes = new TreeMap<>();
int requestedLine = textSelection.getStartLine();
void check(Node node) {
Integer startIndex = node.getStartIndex();
Integer stopIndex = node.getStopIndex();
if (startIndex != null && stopIndex != null) {
stopIndex ++;
int nodeStartLine;
try {
nodeStartLine = document.getLineOfOffset(startIndex);
if (nodeStartLine >= requestedLine) {
nodes.put(nodeStartLine, node);
} else {
int nodeEndLine = document.getLineOfOffset(stopIndex);
if (nodeEndLine >= requestedLine) {
nodes.put(requestedLine, node);
}
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
}
@Override
public void visit(Tree.Annotation that) {}
// @Override
// public void visit(Tree.MethodDefinition that) {
// if (in(that.getIdentifier())) {
// result = that;
// }
// super.visit(that);
// }
// @Override
// public void visit(Tree.ClassDefinition that) {
// if (in(that.getIdentifier())) {
// result = that;
// }
// super.visit(that);
// }
// @Override
// public void visit(Tree.Constructor that) {
// if (in(that.getIdentifier())) {
// result = that;
// }
// super.visit(that);
// }
// @Override
// public void visit(Tree.AttributeGetterDefinition that) {
// if (in(that.getIdentifier())) {
// result = that;
// }
// super.visit(that);
// }
// @Override
// public void visit(Tree.AttributeSetterDefinition that) {
// if (in(that.getIdentifier())) {
// result = that;
// }
// super.visit(that);
// }
@Override
public void visit(Tree.ExecutableStatement that) {
check(that);
super.visit(that);
}
@Override
public void visit(Tree.SpecifierOrInitializerExpression that) {
check(that);
super.visit(that);
}
@Override
public void visit(Tree.Expression that) {
check(that);
super.visit(that);
}
};
BreakpointVisitor visitor = new BreakpointVisitor();
visitor.visit(rootNode);
return visitor.nodes.firstEntry();
}
private IMarker findBreakpointMarker(IFile srcFile, int lineNumber) throws CoreException {
IMarker[] markers = srcFile.findMarkers(IBreakpoint.LINE_BREAKPOINT_MARKER, true, IResource.DEPTH_INFINITE);
for (int k = 0; k < markers.length; k++ ){
if (((Integer) markers[k].getAttribute(IMarker.LINE_NUMBER)).intValue() == lineNumber){
return markers[k];
}
}
return null;
}
public static final String ORIGIN = CeylonPlugin.PLUGIN_ID + ".breakpointAttribute.Origin";
public IJavaStratumLineBreakpoint createLineBreakpoint(IFile file, int lineNumber, Map<String,Object> attributes, boolean isForRunToLine) throws CoreException {
String srcFileName = file.getName();
String srcPath = null;
IPath relativePath = null;
String classnamePattern = null;
IPath projectRelativePath = file.getProjectRelativePath();
if (ExternalSourceArchiveManager.isInSourceArchive(file)) {
relativePath = ExternalSourceArchiveManager.getSourceArchiveEntryPath(file);
} else {
IResourceAware<IProject,IFolder,IFile> unit = CeylonBuilder.getUnit(file);
if (unit != null) {
relativePath = new Path(((Unit)unit).getRelativePath());
}
}
if (relativePath != null) {
srcPath = relativePath.toOSString();
} else {
String[] segments = projectRelativePath.segments();
if (segments.length == 1) {
srcPath=srcFileName;
} else {
String pattern = buildClassNamePattern(segments);
classnamePattern = pattern;
}
}
if (attributes == null) {
attributes = new HashMap<String, Object>();
}
attributes.put(ORIGIN, CeylonPlugin.PLUGIN_ID);
try {
return JDIDebugModel.createStratumBreakpoint(isForRunToLine ? getWorkspace().getRoot() : file, null, srcFileName,
srcPath, classnamePattern, lineNumber, -1, -1, 0, !isForRunToLine, attributes);
}
catch (CoreException e) {
e.printStackTrace();
}
return null;
}
private String buildClassNamePattern(String[] projectRelativePathSegments) {
String currentPattern = "*";
String[] patterns = new String[projectRelativePathSegments.length-1];
for (int i=projectRelativePathSegments.length-2; i>=0; i--) {
currentPattern = projectRelativePathSegments[i] + "." + currentPattern;
patterns[i] = currentPattern;
}
String pattern = patterns[0];
for (int i=1; i<patterns.length; i++) {
pattern += "," + patterns[i];
}
return pattern;
}
public void clearLineBreakpoint(IFile file, int lineNumber) throws CoreException {
try {
IBreakpoint lineBkpt = findStratumBreakpoint(file, lineNumber);
if (lineBkpt != null) {
lineBkpt.delete();
}
}
catch (CoreException e) {
e.printStackTrace();
}
}
public void disableLineBreakpoint(IFile file, int lineNumber) throws CoreException {
try {
IBreakpoint lineBkpt = findStratumBreakpoint(file, lineNumber);
if (lineBkpt != null) {
lineBkpt.setEnabled(false);
}
}
catch (CoreException e) {
e.printStackTrace();
}
}
public void enableLineBreakpoint(IFile file, int lineNumber) throws CoreException {
try {
IBreakpoint lineBkpt = findStratumBreakpoint(file, lineNumber);
if (lineBkpt != null) {
lineBkpt.setEnabled(true);
}
}
catch (CoreException e) {
e.printStackTrace();
}
}
/**
* Returns a Java line breakpoint that is already registered with the breakpoint
* manager for a type with the given name at the given line number.
*
* @param typeName fully qualified type name
* @param lineNumber line number
* @return a Java line breakpoint that is already registered with the breakpoint
* manager for a type with the given name at the given line number or <code>null</code>
* if no such breakpoint is registered
* @exception CoreException if unable to retrieve the associated marker
* attributes (line number).
*/
public static IJavaLineBreakpoint findStratumBreakpoint(IResource resource, int lineNumber) throws CoreException {
String modelId = JDT_DEBUG_PLUGIN_ID;
String markerType = "org.eclipse.jdt.debug.javaStratumLineBreakpointMarker";
IBreakpointManager manager = DebugPlugin.getDefault().getBreakpointManager();
IBreakpoint[] breakpoints = manager.getBreakpoints(modelId);
for (int i = 0; i < breakpoints.length; i++) {
if (!(breakpoints[i] instanceof IJavaLineBreakpoint)) {
continue;
}
IJavaLineBreakpoint breakpoint = (IJavaLineBreakpoint) breakpoints[i];
IMarker marker = breakpoint.getMarker();
if (marker != null && marker.exists() && marker.getType().equals(markerType)) {
if (breakpoint.getLineNumber() == lineNumber &&
resource.equals(marker.getResource())) {
return breakpoint;
}
}
}
return null;
}
public boolean canToggleLineBreakpoints(IWorkbenchPart part, ISelection selection) {
return true;
}
public void toggleMethodBreakpoints(IWorkbenchPart part, ISelection selection) throws CoreException {
}
public boolean canToggleMethodBreakpoints(IWorkbenchPart part, ISelection selection) {
return false;
}
public void toggleWatchpoints(IWorkbenchPart part, ISelection selection) throws CoreException {
}
public boolean canToggleWatchpoints(IWorkbenchPart part, ISelection selection) {
return false;
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.actions.IRunToLineTarget#runToLine(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection, org.eclipse.debug.core.model.ISuspendResume)
*/
public void runToLine(IWorkbenchPart part, ISelection selection, final ISuspendResume target) throws CoreException {
doOnBreakpoints(part, selection, new BreakpointAction() {
@Override
public void doIt(final ITextSelection textSelection, final Map.Entry<Integer, Node> firstValidLocation,
final IFile sourceFile) throws CoreException {
final int requestedLineNumber = textSelection.getStartLine();
String errorMessage; //$NON-NLS-1$
if (firstValidLocation != null && requestedLineNumber == firstValidLocation.getKey().intValue()) {
errorMessage = "Unable to locate debug target"; //$NON-NLS-1$
if (target instanceof IAdaptable) {
IDebugTarget debugTarget = (IDebugTarget) ((IAdaptable)target).getAdapter(IDebugTarget.class);
if (debugTarget != null) {
IBreakpoint breakpoint= null;
Map<String, Object> attributes = new HashMap<String, Object>(4);
BreakpointUtils.addRunToLineAttributes(attributes);
breakpoint = createLineBreakpoint(sourceFile, firstValidLocation.getKey() + 1, attributes, true);
RunToLineHandler handler = new RunToLineHandler(debugTarget, target, breakpoint);
handler.run(new NullProgressMonitor());
return;
}
}
} else {
// invalid line
if (textSelection.getLength() > 0) {
errorMessage = "Selected line is not a valid location to run to"; //$NON-NLS-1$
} else {
errorMessage = "Cursor position is not a valid location to run to"; //$NON-NLS-1$
}
}
throw new CoreException(new Status(IStatus.ERROR, JDIDebugUIPlugin.getUniqueIdentifier(), IJavaDebugUIConstants.INTERNAL_ERROR,
errorMessage, null));
}
});
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.actions.IRunToLineTarget#canRunToLine(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection, org.eclipse.debug.core.model.ISuspendResume)
*/
public boolean canRunToLine(IWorkbenchPart part, ISelection selection, ISuspendResume target) {
if (target instanceof IDebugElement && target.canResume()) {
IDebugElement element = (IDebugElement) target;
IJavaDebugTarget adapter = (IJavaDebugTarget) element.getDebugTarget().getAdapter(IJavaDebugTarget.class);
return adapter != null;
}
return false;
}
}