/******************************************************************************* * Copyright (c) 2012-2016 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.ext.java.jdi.client.debug; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.gwt.user.client.ui.IsWidget; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; import com.google.web.bindery.event.shared.HandlerRegistration; import org.eclipse.che.api.machine.gwt.client.events.ExtServerStateEvent; import org.eclipse.che.api.machine.gwt.client.events.ExtServerStateHandler; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.app.CurrentProject; import org.eclipse.che.ide.api.editor.EditorAgent; import org.eclipse.che.ide.api.editor.EditorPartPresenter; import org.eclipse.che.ide.api.event.ActivePartChangedEvent; import org.eclipse.che.ide.api.event.ActivePartChangedHandler; import org.eclipse.che.ide.api.event.FileEvent; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.api.notification.StatusNotification; import org.eclipse.che.ide.api.parts.PartStackType; import org.eclipse.che.ide.api.parts.WorkspaceAgent; import org.eclipse.che.ide.api.parts.base.BasePresenter; import org.eclipse.che.ide.api.project.node.HasStorablePath; import org.eclipse.che.ide.api.project.node.Node; import org.eclipse.che.ide.api.project.tree.VirtualFile; import org.eclipse.che.ide.debug.Breakpoint; import org.eclipse.che.ide.debug.BreakpointManager; import org.eclipse.che.ide.debug.BreakpointStateEvent; import org.eclipse.che.ide.debug.Debugger; import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.ext.java.client.project.node.JavaNodeManager; import org.eclipse.che.ide.ext.java.client.project.node.jar.JarFileNode; import org.eclipse.che.ide.ext.java.client.projecttree.JavaSourceFolderUtil; import org.eclipse.che.ide.ext.java.jdi.client.JavaRuntimeExtension; import org.eclipse.che.ide.ext.java.jdi.client.JavaRuntimeLocalizationConstant; import org.eclipse.che.ide.ext.java.jdi.client.JavaRuntimeResources; import org.eclipse.che.ide.ext.java.jdi.client.debug.changevalue.ChangeValuePresenter; import org.eclipse.che.ide.ext.java.jdi.client.debug.expression.EvaluateExpressionPresenter; import org.eclipse.che.ide.ext.java.jdi.client.fqn.FqnResolver; import org.eclipse.che.ide.ext.java.jdi.client.fqn.FqnResolverFactory; import org.eclipse.che.ide.ext.java.jdi.client.fqn.FqnResolverObserver; import org.eclipse.che.ide.ext.java.jdi.client.marshaller.DebuggerEventListUnmarshallerWS; import org.eclipse.che.ide.ext.java.jdi.shared.BreakPoint; import org.eclipse.che.ide.ext.java.jdi.shared.BreakPointEvent; import org.eclipse.che.ide.ext.java.jdi.shared.BreakpointActivatedEvent; import org.eclipse.che.ide.ext.java.jdi.shared.DebuggerEvent; import org.eclipse.che.ide.ext.java.jdi.shared.DebuggerEventList; import org.eclipse.che.ide.ext.java.jdi.shared.DebuggerInfo; import org.eclipse.che.ide.ext.java.jdi.shared.Location; import org.eclipse.che.ide.ext.java.jdi.shared.StackFrameDump; import org.eclipse.che.ide.ext.java.jdi.shared.StepEvent; import org.eclipse.che.ide.ext.java.jdi.shared.Value; import org.eclipse.che.ide.ext.java.jdi.shared.Variable; import org.eclipse.che.ide.ext.java.shared.JarEntry; import org.eclipse.che.ide.part.explorer.project.ProjectExplorerPresenter; import org.eclipse.che.ide.project.node.FileReferenceNode; import org.eclipse.che.ide.rest.AsyncRequestCallback; import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; import org.eclipse.che.ide.rest.HTTPStatus; import org.eclipse.che.ide.util.loging.Log; import org.eclipse.che.ide.util.storage.LocalStorage; import org.eclipse.che.ide.util.storage.LocalStorageProvider; import org.eclipse.che.ide.websocket.MessageBus; import org.eclipse.che.ide.websocket.MessageBusProvider; import org.eclipse.che.ide.websocket.WebSocketException; import org.eclipse.che.ide.websocket.rest.SubscriptionHandler; import org.eclipse.che.ide.websocket.rest.exceptions.ServerException; import org.eclipse.che.ide.workspace.perspectives.project.ProjectPerspective; import org.vectomatic.dom.svg.ui.SVGResource; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static org.eclipse.che.ide.api.event.FileEvent.FileOperation.OPEN; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.PROGRESS; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.SUCCESS; import static org.eclipse.che.ide.debug.DebuggerStateEvent.createConnectedStateEvent; import static org.eclipse.che.ide.debug.DebuggerStateEvent.createDisconnectedStateEvent; import static org.eclipse.che.ide.debug.DebuggerStateEvent.createInitializedStateEvent; import static org.eclipse.che.ide.ext.java.jdi.shared.DebuggerEvent.BREAKPOINT; import static org.eclipse.che.ide.ext.java.jdi.shared.DebuggerEvent.BREAKPOINT_ACTIVATED; import static org.eclipse.che.ide.ext.java.jdi.shared.DebuggerEvent.STEP; /** * The presenter provides debug java application. * * @author Vitaly Parfonov * @author Artem Zatsarynnyi * @author Valeriy Svydenko * @author Dmitry Shnurenko * @author Anatoliy Bazko */ @Singleton public class DebuggerPresenter extends BasePresenter implements DebuggerView.ActionDelegate, Debugger, FqnResolverObserver { protected static final String LOCAL_STORAGE_DEBUGGER_KEY = "che-debugger"; private static final String TITLE = "Debug"; private final DtoFactory dtoFactory; private final DtoUnmarshallerFactory dtoUnmarshallerFactory; private final AppContext appContext; private final ProjectExplorerPresenter projectExplorer; private final JavaNodeManager javaNodeManager; private final JavaRuntimeResources javaRuntimeResources; private final LocalStorageProvider localStorageProvider; /** Channel identifier to receive events from debugger over WebSocket. */ private String debuggerEventsChannel; /** Channel identifier to receive event when debugger will be disconnected. */ private String debuggerDisconnectedChannel; private DebuggerView view; private EventBus eventBus; private DebuggerServiceClient service; private JavaRuntimeLocalizationConstant constant; private DebuggerInfo debuggerInfo; private MessageBus messageBus; private BreakpointManager breakpointManager; private WorkspaceAgent workspaceAgent; private FqnResolverFactory resolverFactory; private EditorAgent editorAgent; private DebuggerVariable selectedVariable; private EvaluateExpressionPresenter evaluateExpressionPresenter; private ChangeValuePresenter changeValuePresenter; private NotificationManager notificationManager; /** Handler for processing events which is received from debugger over WebSocket connection. */ private SubscriptionHandler<DebuggerEventList> debuggerEventsHandler; private SubscriptionHandler<Void> debuggerDisconnectedHandler; private List<DebuggerVariable> variables; private Location executionPoint; @Inject public DebuggerPresenter(DebuggerView view, final DebuggerServiceClient service, final EventBus eventBus, final JavaRuntimeLocalizationConstant constant, WorkspaceAgent workspaceAgent, final BreakpointManager breakpointManager, FqnResolverFactory resolverFactory, EditorAgent editorAgent, final EvaluateExpressionPresenter evaluateExpressionPresenter, ChangeValuePresenter changeValuePresenter, final NotificationManager notificationManager, final DtoFactory dtoFactory, DtoUnmarshallerFactory dtoUnmarshallerFactory, final AppContext appContext, ProjectExplorerPresenter projectExplorer, final MessageBusProvider messageBusProvider, final JavaNodeManager javaNodeManager, JavaRuntimeResources javaRuntimeResources, LocalStorageProvider localStorageProvider) { this.view = view; this.eventBus = eventBus; this.dtoFactory = dtoFactory; this.dtoUnmarshallerFactory = dtoUnmarshallerFactory; this.appContext = appContext; this.projectExplorer = projectExplorer; this.javaRuntimeResources = javaRuntimeResources; this.view.setDelegate(this); this.view.setTitle(TITLE); this.service = service; this.constant = constant; this.workspaceAgent = workspaceAgent; this.breakpointManager = breakpointManager; this.resolverFactory = resolverFactory; this.resolverFactory.addFqnResolverObserver(this); this.variables = new ArrayList<>(); this.editorAgent = editorAgent; this.evaluateExpressionPresenter = evaluateExpressionPresenter; this.changeValuePresenter = changeValuePresenter; this.notificationManager = notificationManager; this.javaNodeManager = javaNodeManager; this.addRule(ProjectPerspective.PROJECT_PERSPECTIVE_ID); this.localStorageProvider = localStorageProvider; this.debuggerInfo = EmptyDebuggerInfo.INSTANCE; eventBus.addHandler(ExtServerStateEvent.TYPE, new ExtServerStateHandler() { @Override public void onExtServerStarted(ExtServerStateEvent event) { messageBus = messageBusProvider.getMachineMessageBus(); debuggerInfo = loadDebugInfo(); if (isDebuggerConnected()) { service.checkEvents(debuggerInfo.getId(), new AsyncRequestCallback<DebuggerEventList>() { @Override protected void onSuccess(DebuggerEventList result) { onDebuggerConnected(); } @Override protected void onFailure(Throwable exception) { debuggerInfo = EmptyDebuggerInfo.INSTANCE; preserveDebugInfo(); } }); } } @Override public void onExtServerStopped(ExtServerStateEvent event) { } }); this.debuggerEventsHandler = new SubscriptionHandler<DebuggerEventList>(new DebuggerEventListUnmarshallerWS(dtoFactory)) { @Override public void onMessageReceived(DebuggerEventList result) { onEventListReceived(result); } @Override public void onErrorReceived(Throwable exception) { try { messageBus.unsubscribe(debuggerEventsChannel, this); } catch (WebSocketException e) { Log.error(DebuggerPresenter.class, e); } if (exception instanceof ServerException) { ServerException serverException = (ServerException)exception; if (HTTPStatus.INTERNAL_ERROR == serverException.getHTTPStatus() && serverException.getMessage() != null && serverException.getMessage().contains("not found")) { onDebuggerDisconnected(); return; } } } }; this.debuggerDisconnectedHandler = new SubscriptionHandler<Void>() { @Override protected void onMessageReceived(Void result) { try { messageBus.unsubscribe(debuggerDisconnectedChannel, this); } catch (WebSocketException e) { Log.error(DebuggerPresenter.class, e); } evaluateExpressionPresenter.closeDialog(); onDebuggerDisconnected(); } @Override protected void onErrorReceived(Throwable exception) { try { messageBus.unsubscribe(debuggerDisconnectedChannel, this); } catch (WebSocketException e) { Log.error(DebuggerPresenter.class, e); } } }; eventBus.fireEvent(createInitializedStateEvent(this)); } /** {@inheritDoc} */ @Override @NotNull public String getTitle() { return TITLE; } /** {@inheritDoc} */ @Override public void setVisible(boolean visible) { view.setVisible(visible); } /** {@inheritDoc} */ @Override public IsWidget getView() { return view; } /** {@inheritDoc} */ @Override public ImageResource getTitleImage() { return null; } /** {@inheritDoc} */ @Override public SVGResource getTitleSVGImage() { return javaRuntimeResources.debug(); } /** {@inheritDoc} */ @Override public String getTitleToolTip() { return "Debug"; } /** {@inheritDoc} */ @Override public void go(AcceptsOneWidget container) { view.setBreakpoints(breakpointManager.getBreakpointList()); view.setVariables(variables); container.setWidget(view); } private void onEventListReceived(@NotNull DebuggerEventList eventList) { if (eventList.getEvents().size() == 0) { return; } VirtualFile activeFile = null; EditorPartPresenter activeEditor = editorAgent.getActiveEditor(); if (activeEditor != null) { activeFile = activeEditor.getEditorInput().getFile(); } Location location; List<DebuggerEvent> events = eventList.getEvents(); for (DebuggerEvent event : events) { switch (event.getType()) { case STEP: location = ((StepEvent)event).getLocation(); break; case BREAKPOINT_ACTIVATED: BreakPoint breakPoint = ((BreakpointActivatedEvent)event).getBreakPoint(); activateBreakpoint(breakPoint); return; case BREAKPOINT: location = ((BreakPointEvent)event).getBreakPoint().getLocation(); partStack.setActivePart(this); break; default: Log.error(DebuggerPresenter.class, "Unknown type of debugger event: " + event.getType()); return; } this.executionPoint = location; List<String> filePaths = resolveFilePathByLocation(location); if (activeFile == null || !filePaths.contains(activeFile.getPath())) { final Location finalLocation = location; openFile(location, filePaths, 0, new AsyncCallback<VirtualFile>() { @Override public void onSuccess(VirtualFile result) { breakpointManager.setCurrentBreakpoint(finalLocation.getLineNumber() - 1); } @Override public void onFailure(Throwable caught) { notificationManager.notify(caught.getMessage(), StatusNotification.Status.FAIL, false); } }); } else { breakpointManager.setCurrentBreakpoint(location.getLineNumber() - 1); } getStackFrameDump(); changeButtonsEnableState(true); } } /** * Breakpoint became active. It might happens because of different reasons: * <li>breakpoint was deferred and VM eventually loaded class and added it</li> * <li>condition triggered</li> * <li>etc</li> */ private void activateBreakpoint(BreakPoint breakPoint) { Location location = breakPoint.getLocation(); List<String> filePaths = resolveFilePathByLocation(location); for (String filePath : filePaths) { eventBus.fireEvent( new BreakpointStateEvent(BreakpointStateEvent.BreakpointState.ACTIVE, filePath, location.getLineNumber() - 1)); } } /** * Create file path from {@link Location}. * * @param location * location of class * @return file path */ @NotNull private List<String> resolveFilePathByLocation(@NotNull Location location) { CurrentProject currentProject = appContext.getCurrentProject(); if (currentProject == null) { return Collections.emptyList(); } String pathSuffix = location.getClassName().replace(".", "/") + ".java"; List<String> sourceFolders = JavaSourceFolderUtil.getSourceFolders(currentProject); List<String> filePaths = new ArrayList<>(sourceFolders.size() + 1); for (String sourceFolder : sourceFolders) { filePaths.add(sourceFolder + pathSuffix); } filePaths.add(location.getClassName()); return filePaths; } /** * Tries to open file from the project. * If fails then method will try to find resource from external dependencies. */ private void openFile(@NotNull final Location location, final List<String> filePaths, final int pathNumber, final AsyncCallback<VirtualFile> callback) { if (pathNumber == filePaths.size()) { Log.error(DebuggerPresenter.class, "Can't open resource " + location); return; } String filePath = filePaths.get(pathNumber); if (!filePath.startsWith("/")) { openExternalResource(location, callback); return; } projectExplorer.getNodeByPath(new HasStorablePath.StorablePath(filePath)).then(new Operation<Node>() { public HandlerRegistration handlerRegistration; @Override public void apply(final Node node) throws OperationException { if (!(node instanceof FileReferenceNode)) { return; } handlerRegistration = eventBus.addHandler(ActivePartChangedEvent.TYPE, new ActivePartChangedHandler() { @Override public void onActivePartChanged(ActivePartChangedEvent event) { if (event.getActivePart() instanceof EditorPartPresenter) { final VirtualFile openedFile = ((EditorPartPresenter)event.getActivePart()).getEditorInput().getFile(); if (((FileReferenceNode)node).getStorablePath().equals(openedFile.getPath())) { handlerRegistration.removeHandler(); // give the editor some time to fully render it's view new Timer() { @Override public void run() { callback.onSuccess((VirtualFile)node); } }.schedule(300); } } } }); eventBus.fireEvent(new FileEvent((VirtualFile)node, OPEN)); } }).catchError(new Operation<PromiseError>() { @Override public void apply(PromiseError error) throws OperationException { // try another path openFile(location, filePaths, pathNumber + 1, callback); } }); } private void openExternalResource(Location location, final AsyncCallback<VirtualFile> callback) { String className = location.getClassName(); JarEntry jarEntry = dtoFactory.createDto(JarEntry.class); jarEntry.setPath(className); jarEntry.setName(className.substring(className.lastIndexOf(".") + 1) + ".class"); jarEntry.setType(JarEntry.JarEntryType.CLASS_FILE); final JarFileNode jarFileNode = javaNodeManager.getJavaNodeFactory().newJarFileNode(jarEntry, null, appContext.getCurrentProject().getProjectConfig(), javaNodeManager.getJavaSettingsProvider() .getSettings()); editorAgent.openEditor(jarFileNode, new EditorAgent.OpenEditorCallback() { @Override public void onEditorOpened(EditorPartPresenter editor) { // give the editor some time to fully render it's view new Timer() { @Override public void run() { callback.onSuccess(jarFileNode); } }.schedule(300); } }); } private void getStackFrameDump() { service.getStackFrameDump(debuggerInfo.getId(), new AsyncRequestCallback<StackFrameDump>(dtoUnmarshallerFactory.newUnmarshaller(StackFrameDump.class)) { @Override protected void onSuccess(StackFrameDump result) { List<Variable> variables = new ArrayList<>(); variables.addAll(result.getFields()); variables.addAll(result.getLocalVariables()); List<DebuggerVariable> debuggerVariables = getDebuggerVariables(variables); DebuggerPresenter.this.variables = debuggerVariables; view.setVariables(debuggerVariables); if (!variables.isEmpty()) { view.setExecutionPoint(variables.get(0).isExistInformation(), executionPoint); } } @Override protected void onFailure(Throwable exception) { Log.error(DebuggerPresenter.class, exception); } }); } @NotNull private List<DebuggerVariable> getDebuggerVariables(@NotNull List<Variable> variables) { List<DebuggerVariable> debuggerVariables = new ArrayList<>(); for (Variable variable : variables) { debuggerVariables.add(new DebuggerVariable(variable)); } return debuggerVariables; } /** Change enable state of all buttons (except Disconnect button) on Debugger panel. */ private void changeButtonsEnableState(boolean isEnable) { view.setEnableResumeButton(isEnable); view.setEnableStepIntoButton(isEnable); view.setEnableStepOverButton(isEnable); view.setEnableStepReturnButton(isEnable); view.setEnableEvaluateExpressionButtonEnable(isEnable); } /** {@inheritDoc} */ @Override public void onResumeButtonClicked() { changeButtonsEnableState(false); breakpointManager.removeCurrentBreakpoint(); service.resume(debuggerInfo.getId(), new AsyncRequestCallback<Void>() { @Override protected void onSuccess(Void result) { resetStates(); } @Override protected void onFailure(Throwable exception) { Log.error(DebuggerPresenter.class, exception); } }); } /** {@inheritDoc} */ @Override public void onRemoveAllBreakpointsButtonClicked() { if (isDebuggerConnected()) { service.deleteAllBreakpoints(debuggerInfo.getId(), new AsyncRequestCallback<String>() { @Override protected void onSuccess(String result) { breakpointManager.removeAllBreakpoints(); updateBreakPoints(); } @Override protected void onFailure(Throwable exception) { Log.error(DebuggerPresenter.class, exception); } }); } else { breakpointManager.removeAllBreakpoints(); updateBreakPoints(); } } /** {@inheritDoc} */ @Override public void onDisconnectButtonClicked() { disconnectDebugger(); } /** {@inheritDoc} */ @Override public void onStepIntoButtonClicked() { breakpointManager.removeCurrentBreakpoint(); service.stepInto(debuggerInfo.getId(), new AsyncRequestCallback<Void>() { @Override protected void onSuccess(Void result) { resetStates(); } @Override protected void onFailure(Throwable exception) { Log.error(DebuggerPresenter.class, exception); } }); } /** {@inheritDoc} */ @Override public void onStepOverButtonClicked() { breakpointManager.removeCurrentBreakpoint(); service.stepOver(debuggerInfo.getId(), new AsyncRequestCallback<Void>() { @Override protected void onSuccess(Void result) { resetStates(); } @Override protected void onFailure(Throwable exception) { Log.error(DebuggerPresenter.class, exception); } }); } /** {@inheritDoc} */ @Override public void onStepReturnButtonClicked() { breakpointManager.removeCurrentBreakpoint(); service.stepReturn(debuggerInfo.getId(), new AsyncRequestCallback<Void>() { @Override protected void onSuccess(Void result) { resetStates(); } @Override protected void onFailure(Throwable exception) { Log.error(DebuggerPresenter.class, exception); } }); } /** {@inheritDoc} */ @Override public void onChangeValueButtonClicked() { if (selectedVariable == null) { return; } changeValuePresenter.showDialog(debuggerInfo, selectedVariable.getVariable(), new AsyncCallback<String>() { @Override public void onSuccess(String s) { getStackFrameDump(); } @Override public void onFailure(Throwable throwable) { Log.error(DebuggerPresenter.class, throwable); } }); } /** {@inheritDoc} */ @Override public void onEvaluateExpressionButtonClicked() { evaluateExpressionPresenter.showDialog(debuggerInfo); } /** {@inheritDoc} */ @Override public void onExpandVariablesTree() { List<DebuggerVariable> rootVariables = selectedVariable.getVariables(); if (rootVariables.size() == 0) { service.getValue(debuggerInfo.getId(), selectedVariable.getVariable(), new AsyncRequestCallback<Value>(dtoUnmarshallerFactory.newUnmarshaller(Value.class)) { @Override protected void onSuccess(Value result) { List<Variable> variables = result.getVariables(); List<DebuggerVariable> debuggerVariables = getDebuggerVariables(variables); view.setVariablesIntoSelectedVariable(debuggerVariables); view.updateSelectedVariable(); } @Override protected void onFailure(Throwable exception) { notificationManager .notify(constant.failedToGetVariableValueTitle(), exception.getMessage(), FAIL, true); } }); } } /** {@inheritDoc} */ @Override public void onSelectedVariableElement(@NotNull DebuggerVariable variable) { this.selectedVariable = variable; updateChangeValueButtonEnableState(); } /** Update enable state for 'Change value' button. */ private void updateChangeValueButtonEnableState() { view.setEnableChangeValueButtonEnable(selectedVariable != null); } private void resetStates() { variables.clear(); view.setVariables(variables); selectedVariable = null; updateChangeValueButtonEnableState(); } private void showAndUpdateView() { view.setVMName(debuggerInfo.getVmName() + " " + debuggerInfo.getVmVersion()); updateChangeValueButtonEnableState(); boolean isCurrentBreakpointExists = breakpointManager.getCurrentBreakpoint() != null; if (isCurrentBreakpointExists) { getStackFrameDump(); } changeButtonsEnableState(isCurrentBreakpointExists); view.setEnableDisconnectButton(isDebuggerConnected()); view.setEnableRemoveAllBreakpointsButton(!breakpointManager.getBreakpointList().isEmpty()); if (partStack == null || !partStack.containsPart(this)) { workspaceAgent.openPart(this, PartStackType.INFORMATION); } } /** * Attached debugger via special host and port for current project. * * @param host * host which need to connect to debugger * @param port * port which need to connect to debugger */ public void attachDebugger(@NotNull final String host, @Min(1) final int port) { final String address = host + ':' + port; final StatusNotification notification = notificationManager.notify(constant.debuggerConnectingTitle(address), PROGRESS, true); service.connect(host, port, new AsyncRequestCallback<DebuggerInfo>(dtoUnmarshallerFactory.newUnmarshaller(DebuggerInfo.class)) { @Override public void onSuccess(DebuggerInfo result) { debuggerInfo = result; preserveDebugInfo(); notification.setTitle(constant.debuggerConnectedTitle()); notification.setContent(constant.debuggerConnectedDescription(address)); notification.setStatus(SUCCESS); onDebuggerConnected(); } @Override protected void onFailure(Throwable exception) { notification.setTitle(constant.failedToConnectToRemoteDebuggerDescription(address)); notification.setStatus(FAIL); notification.setBalloon(true); } }); } private void onDebuggerConnected() { startCheckingEvents(); eventBus.fireEvent(createConnectedStateEvent(DebuggerPresenter.this)); showAndUpdateView(); partStack.setActivePart(this); } private void disconnectDebugger() { if (isDebuggerConnected()) { stopCheckingDebugEvents(); service.disconnect(debuggerInfo.getId(), new AsyncRequestCallback<Void>() { @Override protected void onSuccess(Void result) { onDebuggerDisconnected(); } @Override protected void onFailure(Throwable exception) { onDebuggerDisconnected(); Log.error(DebuggerPresenter.class, exception); } }); } } private void startCheckingEvents() { debuggerEventsChannel = JavaRuntimeExtension.EVENTS_CHANNEL + debuggerInfo.getId(); try { messageBus.subscribe(debuggerEventsChannel, debuggerEventsHandler); } catch (WebSocketException e) { Log.error(DebuggerPresenter.class, e); } try { debuggerDisconnectedChannel = JavaRuntimeExtension.DISCONNECT_CHANNEL + debuggerInfo.getId(); messageBus.subscribe(debuggerDisconnectedChannel, debuggerDisconnectedHandler); } catch (WebSocketException e) { Log.error(DebuggerPresenter.class, e); } } private void stopCheckingDebugEvents() { try { if (messageBus.isHandlerSubscribed(debuggerEventsHandler, debuggerEventsChannel)) { messageBus.unsubscribe(debuggerEventsChannel, debuggerEventsHandler); } if (messageBus.isHandlerSubscribed(debuggerDisconnectedHandler, debuggerDisconnectedChannel)) { messageBus.unsubscribe(debuggerDisconnectedChannel, debuggerDisconnectedHandler); } } catch (WebSocketException e) { Log.error(DebuggerPresenter.class, e); } } /** Perform some action after disconnecting a debugger. */ private void onDebuggerDisconnected() { notificationManager.notify(constant.debuggerDisconnectedTitle(), constant.debuggerDisconnectedDescription(debuggerInfo.getHost() + ':' + debuggerInfo.getPort()), SUCCESS, false); invalidateDebugInfo(); preserveDebugInfo(); variables.clear(); view.setVariables(variables); view.setExecutionPoint(true, null); showAndUpdateView(); eventBus.fireEvent(createDisconnectedStateEvent(this)); } /** * Updates breakpoints list. * The main idea is to display FQN instead of file path. */ private void updateBreakPoints() { List<Breakpoint> breakpoints = breakpointManager.getBreakpointList(); List<Breakpoint> breakpoints2Display = new ArrayList<Breakpoint>(breakpoints.size()); for (Breakpoint breakpoint : breakpoints) { FqnResolver resolver = resolverFactory.getResolver(breakpoint.getFile().getMediaType()); breakpoints2Display.add(new Breakpoint(breakpoint.getType(), breakpoint.getLineNumber(), resolver == null ? breakpoint.getPath() : resolver .resolveFqn( breakpoint.getFile()), breakpoint.getFile(), breakpoint.getMessage(), breakpoint.isActive())); } view.setEnableRemoveAllBreakpointsButton(!breakpoints.isEmpty()); view.setBreakpoints(breakpoints2Display); showAndUpdateView(); } /** {@inheritDoc} */ @Override public void addBreakpoint(@NotNull final VirtualFile file, final int lineNumber, final AsyncCallback<Breakpoint> callback) { if (isDebuggerConnected()) { Location location = dtoFactory.createDto(Location.class); location.setLineNumber(lineNumber + 1); final FqnResolver resolver = resolverFactory.getResolver(file.getMediaType()); if (resolver != null) { location.setClassName(resolver.resolveFqn(file)); } else { Log.warn(DebuggerPresenter.class, "FqnResolver is not found"); } BreakPoint breakPoint = dtoFactory.createDto(BreakPoint.class); breakPoint.setLocation(location); breakPoint.setEnabled(true); service.addBreakpoint(debuggerInfo.getId(), breakPoint, new AsyncRequestCallback<Void>() { @Override protected void onSuccess(Void result) { if (resolver != null) { Breakpoint breakpoint = new Breakpoint(Breakpoint.Type.BREAKPOINT, lineNumber, file.getPath(), file, true); callback.onSuccess(breakpoint); updateBreakPoints(); } } @Override protected void onFailure(Throwable exception) { callback.onFailure(exception); updateBreakPoints(); } }); } else { callback.onFailure(new IllegalStateException("Debugger not attached")); updateBreakPoints(); } } /** {@inheritDoc} */ @Override public void deleteBreakpoint(@NotNull VirtualFile file, int lineNumber, final AsyncCallback<Void> callback) { if (isDebuggerConnected()) { Location location = dtoFactory.createDto(Location.class); location.setLineNumber(lineNumber + 1); FqnResolver resolver = resolverFactory.getResolver(file.getMediaType()); if (resolver != null) { location.setClassName(resolver.resolveFqn(file)); } else { Log.warn(DebuggerPresenter.class, "FqnResolver is not found"); } BreakPoint point = dtoFactory.createDto(BreakPoint.class); point.setLocation(location); point.setEnabled(true); service.deleteBreakpoint(debuggerInfo.getId(), point, new AsyncRequestCallback<Void>() { @Override protected void onSuccess(Void result) { callback.onSuccess(null); updateBreakPoints(); } @Override protected void onFailure(Throwable exception) { callback.onFailure(exception); updateBreakPoints(); } }); } else { callback.onFailure(new IllegalStateException("Debugger not attached")); updateBreakPoints(); } } /** {@inheritDoc} */ @Override public void onFqnResolverAdded(FqnResolver fqnResolver) { if (!breakpointManager.getBreakpointList().isEmpty()) { updateBreakPoints(); } } /** * Preserves debug information into the local storage. */ protected void preserveDebugInfo() { LocalStorage localStorage = localStorageProvider.get(); if (localStorage == null) { return; } String data; if (!isDebuggerConnected()) { data = ""; } else { data = dtoFactory.toJson(debuggerInfo); } localStorage.setItem(LOCAL_STORAGE_DEBUGGER_KEY, data); } /** * Loads debug information from the local storage. */ protected DebuggerInfo loadDebugInfo() { LocalStorage localStorage = localStorageProvider.get(); if (localStorage == null) { return EmptyDebuggerInfo.INSTANCE; } String data = localStorage.getItem(LOCAL_STORAGE_DEBUGGER_KEY); if (data == null || data.isEmpty()) { return EmptyDebuggerInfo.INSTANCE; } return dtoFactory.createDtoFromJson(data, DebuggerInfo.class); } private boolean isDebuggerConnected() { return debuggerInfo != null && debuggerInfo != EmptyDebuggerInfo.INSTANCE; } private void invalidateDebugInfo() { debuggerInfo = EmptyDebuggerInfo.INSTANCE; } }