/*******************************************************************************
* Copyright (c) 2012-2015 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.jseditor.client.debug;
import org.eclipse.che.ide.api.editor.EditorAgent;
import org.eclipse.che.ide.api.editor.EditorPartPresenter;
import org.eclipse.che.ide.api.parts.ConsolePart;
import org.eclipse.che.ide.api.project.tree.VirtualFile;
import org.eclipse.che.ide.collections.Array;
import org.eclipse.che.ide.collections.StringMap;
import org.eclipse.che.ide.commons.exception.ServerException;
import org.eclipse.che.ide.debug.Breakpoint;
import org.eclipse.che.ide.debug.BreakpointManager;
import org.eclipse.che.ide.debug.BreakpointRenderer;
import org.eclipse.che.ide.debug.BreakpointRenderer.LineChangeAction;
import org.eclipse.che.ide.debug.Debugger;
import org.eclipse.che.ide.debug.DebuggerManager;
import org.eclipse.che.ide.debug.HasBreakpointRenderer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.web.bindery.event.shared.EventBus;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
/** Implementation of {@link BreakpointManager} for jseditor. */
public class BreakpointManagerImpl implements BreakpointManager, LineChangeAction {
/** The logger. */
private static final Logger LOG = Logger.getLogger(BreakpointManagerImpl.class.getName());
private final Map<String, List<Breakpoint>> breakpoints;
private final EditorAgent editorAgent;
private final DebuggerManager debuggerManager;
private final ConsolePart console;
private Breakpoint currentBreakpoint;
@Inject
public BreakpointManagerImpl(final EditorAgent editorAgent,
final DebuggerManager debuggerManager,
final ConsolePart console,
final EventBus eventBus) {
this.editorAgent = editorAgent;
this.breakpoints = new HashMap<>();
this.debuggerManager = debuggerManager;
this.console = console;
}
@Override
public void changeBreakPointState(final int lineNumber) {
final Debugger debugger = debuggerManager.getDebugger();
if (debugger == null) {
return;
}
final VirtualFile activeFile = editorAgent.getActiveEditor().getEditorInput().getFile();
final BreakpointRenderer breakpointRenderer = getBreakpointRendererForFile(activeFile);
if (breakpointRenderer == null) {
return;
}
final List<Breakpoint> breakpointsForPath = this.breakpoints.get(activeFile.getPath());
if (breakpointsForPath != null) {
for (final Breakpoint breakpoint: breakpointsForPath) {
if (breakpoint.getLineNumber() == lineNumber) {
LOG.fine("Attempt to remove breakpoint on line " + lineNumber);
debugger.deleteBreakpoint(activeFile, lineNumber, new AsyncCallback<Void>() {
@Override
public void onSuccess(final Void result) {
breakpointsForPath.remove(breakpoint);
breakpointRenderer.removeBreakpointMark(lineNumber);
}
@Override
public void onFailure(final Throwable exception) {
if (exception instanceof ServerException) {
final ServerException e = (ServerException)exception;
if (e.isErrorMessageProvided()) {
BreakpointManagerImpl.this.console.print(e.getMessage());
return;
}
}
BreakpointManagerImpl.this.console.print("Can't delete breakpoint at line " + (lineNumber + 1));
}
});
LOG.fine("Breakpoint removed.");
if (breakpointsForPath.isEmpty()) {
breakpoints.remove(activeFile.getPath());
}
return;
}
}
}
debugger.addBreakpoint(activeFile, lineNumber, new AsyncCallback<Breakpoint>() {
@Override
public void onSuccess(final Breakpoint result) {
if (breakpointsForPath != null) {
breakpointsForPath.add(result);
} else {
final List<Breakpoint> newList = new ArrayList<>();
newList.add(result);
BreakpointManagerImpl.this.breakpoints.put(activeFile.getPath(), newList);
}
breakpointRenderer.addBreakpointMark(lineNumber, BreakpointManagerImpl.this);
}
@Override
public void onFailure(final Throwable exception) {
if (exception instanceof ServerException) {
final ServerException e = (ServerException)exception;
if (e.isErrorMessageProvided()) {
BreakpointManagerImpl.this.console.print(e.getMessage());
return;
}
}
BreakpointManagerImpl.this.console.print("Can't add breakpoint at line " + (lineNumber + 1));
}
});
}
@Override
public void removeAllBreakpoints() {
LOG.fine("Remove all breakpoints");
for (final Entry<String, List<Breakpoint>> entry: this.breakpoints.entrySet()) {
final String path = entry.getKey();
final List<Breakpoint> pathBreakpoints = entry.getValue();
removeBreakpointsForPath(path, pathBreakpoints);
}
this.breakpoints.clear();
}
private void removeBreakpointsForPath(final String path, final List<Breakpoint> pathBreakpoints) {
LOG.fine("\tRemove all breakpoints for path " + path);
EditorPartPresenter editor = null;
for (final Breakpoint breakpoint: pathBreakpoints) {
EditorPartPresenter editorForBreakpoint;
if (editor == null) {
editorForBreakpoint = getEditorForFile(breakpoint.getFile());
if (editorForBreakpoint == null) {
// we won't be able to have an editor for any other breakpoint
return;
}
editor = editorForBreakpoint;
} else {
editorForBreakpoint = editor;
}
final BreakpointRenderer breakpointRenderer = getBreakpointRendererForEditor(editorForBreakpoint);
if (breakpointRenderer == null) {
// the renderer will also be null for the other breakpoints for the same path
return;
}
breakpointRenderer.removeBreakpointMark(breakpoint.getLineNumber());
}
}
@Override
@Deprecated
public boolean isBreakPointExist(int lineNumber) {
return breakpointExists(lineNumber);
}
@Override
public boolean breakpointExists(final int lineNumber) {
if (editorAgent.getActiveEditor() == null) {
return false;
}
final VirtualFile activeFile = editorAgent.getActiveEditor().getEditorInput().getFile();
final List<Breakpoint> breakPoints = this.breakpoints.get(activeFile.getPath());
if (breakPoints != null) {
for (final Breakpoint breakpoint : breakPoints) {
if (breakpoint.getLineNumber() == lineNumber) {
return true;
}
}
}
return false;
}
@Override
public List<Breakpoint> getBreakpointList() {
final List<Breakpoint> result = new ArrayList<>();
for (final List<Breakpoint> fileBreakpoints: this.breakpoints.values()) {
result.addAll(fileBreakpoints);
}
return result;
}
@Override
@Deprecated
public Array<Breakpoint> getBreakpoints() {
return org.eclipse.che.ide.collections.Collections.createArray(getBreakpointList());
}
@Override
public void markCurrentBreakpoint(int lineNumber) {
unmarkCurrentBreakpoint();
LOG.fine("Mark current breakpoint on line " + lineNumber);
final VirtualFile activeFile = editorAgent.getActiveEditor().getEditorInput().getFile();
this.currentBreakpoint = new Breakpoint(Breakpoint.Type.CURRENT, lineNumber, activeFile.getPath(), activeFile);
final BreakpointRenderer breakpointRenderer = getBreakpointRendererForFile(activeFile);
if (breakpointRenderer != null) {
if (breakpointExists(lineNumber)) {
breakpointRenderer.setBreakpointActive(lineNumber, true);
}
breakpointRenderer.setLineActive(lineNumber, true);
}
}
@Override
public void unmarkCurrentBreakpoint() {
if (this.currentBreakpoint != null) {
final int oldLineNumber = this.currentBreakpoint.getLineNumber();
LOG.fine("Unmark current breakpoint on line " + oldLineNumber);
final BreakpointRenderer breakpointRenderer = getBreakpointRendererForFile(this.currentBreakpoint.getFile());
this.currentBreakpoint = null;
if (breakpointRenderer != null) {
if (breakpointExists(oldLineNumber)) {
breakpointRenderer.setBreakpointActive(oldLineNumber, false);
}
breakpointRenderer.setLineActive(oldLineNumber, false);
}
}
}
@Override
public boolean isCurrentBreakpoint(int lineNumber) {
if (this.currentBreakpoint != null) {
final VirtualFile activeFile = editorAgent.getActiveEditor().getEditorInput().getFile();
boolean isFileWithMarkBreakPoint = activeFile.getPath().equals(this.currentBreakpoint.getPath());
boolean isCurrentLine = lineNumber == this.currentBreakpoint.getLineNumber();
return isFileWithMarkBreakPoint && isCurrentLine;
}
return false;
}
@Override
@Deprecated
public boolean isMarkedLine(int lineNumber) {
return isCurrentBreakpoint(lineNumber);
}
private EditorPartPresenter getEditorForFile(VirtualFile fileNode) {
final StringMap<EditorPartPresenter> openedEditors = editorAgent.getOpenedEditors();
for (final String key : openedEditors.getKeys().asIterable()) {
final EditorPartPresenter editor = openedEditors.get(key);
if (fileNode.getPath().equals(editor.getEditorInput().getFile().getPath())) {
return editor;
}
}
return null;
}
private BreakpointRenderer getBreakpointRendererForFile(VirtualFile fileNode) {
final EditorPartPresenter editor = getEditorForFile(fileNode);
if (editor != null) {
return getBreakpointRendererForEditor(editor);
} else {
return null;
}
}
private BreakpointRenderer getBreakpointRendererForEditor(final EditorPartPresenter editor) {
if (editor instanceof HasBreakpointRenderer) {
final BreakpointRenderer renderer = ((HasBreakpointRenderer)editor).getBreakpointRenderer();
if (renderer != null && renderer.isReady()) {
return renderer;
} else {
return null;
}
} else {
return null;
}
}
@Override
public void onLineChange(final VirtualFile file, final int firstLine, final int linesAdded, final int linesRemoved) {
final List<Breakpoint> fileBreakpoints = this.breakpoints.get(file.getPath());
final int delta = linesAdded - linesRemoved;
if (fileBreakpoints != null) {
LOG.fine("Change in file with breakpoints " + file.getPath());
final List<Breakpoint> toRemove = new ArrayList<>();
final List<Breakpoint> toAdd = new ArrayList<>();
for (final Breakpoint breakpoint: fileBreakpoints) {
final int lineNumber = breakpoint.getLineNumber();
if (lineNumber < firstLine) {
// we're before any change
continue;
}
if (lineNumber < firstLine + linesRemoved) {
// the line was removed
LOG.fine("Removing breakpoint " + breakpoint + " (removed line).");
toRemove.add(breakpoint);
} else {
// all other lines are shifted by linesAdded - linesRemoved
LOG.fine("Moving breakpoint " + breakpoint + " delta=" + delta);
toRemove.add(breakpoint);
toAdd.add(new Breakpoint(breakpoint.getType(), breakpoint.getLineNumber() + delta,
breakpoint.getPath(), breakpoint.getFile(), breakpoint.getMessage()));
}
}
for (final Breakpoint breakpoint: toRemove) {
changeBreakPointState(breakpoint.getLineNumber());
}
for (final Breakpoint breakpoint: toAdd) {
changeBreakPointState(breakpoint.getLineNumber());
}
}
if (this.currentBreakpoint != null && this.currentBreakpoint.getLineNumber() > firstLine) {
// whatever happens, that can't be handled well
// so i'll just unmark the current line without marking the new one as it doesn't make sense (the
// current position is not synchronized with the debugger)
unmarkCurrentBreakpoint();
}
}
}