package bluej.debugmgr;

import bluej.BlueJTheme;
import bluej.Config;
import bluej.collect.DataCollector;
import bluej.debugger.Debugger;
import bluej.debugger.DebuggerClass;
import bluej.debugger.DebuggerField;
import bluej.debugger.DebuggerObject;
import bluej.debugger.DebuggerThread;
import bluej.debugger.SourceLocation;
import bluej.debugger.VarDisplayInfo;
import bluej.pkgmgr.Project;
import bluej.pkgmgr.Project.DebuggerThreadDetails;
import bluej.prefmgr.PrefMgr;
import bluej.utility.JavaNames;
import bluej.utility.javafx.FXAbstractAction;
import bluej.utility.javafx.JavaFXUtil;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.TilePane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.SVGPath;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
import javafx.stage.Window;
import threadchecker.OnThread;
import threadchecker.Tag;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

import com.sun.jdi.VMDisconnectedException;


| Window for controlling the debugger. | | There are two modes; one which displays a list of all threads (and the user can select a thread | to control/inspect) and another where only a single thread is displayed. | | @author Michael Kolling | public class ExecControls { private static final String stackTitle = Config.getString("debugger.execControls.stackTitle"); private static final String staticTitle = Config.getString("debugger.execControls.staticTitle"); private static final String instanceTitle = Config.getString("debugger.execControls.instanceTitle"); private static final String localTitle = Config.getString("debugger.execControls.localTitle"); private static final String threadTitle = Config.getString("debugger.execControls.threadTitle"); private static final String haltButtonText = Config.getString("debugger.execControls.haltButtonText"); private static final String stepButtonText = Config.getString("debugger.execControls.stepButtonText"); private static final String stepIntoButtonText = Config.getString("debugger.execControls.stepIntoButtonText"); private static final String continueButtonText = Config.getString("debugger.execControls.continueButtonText"); private static final String terminateButtonText = Config.getString("debugger.execControls.terminateButtonText"); @OnThread(Tag.FX) private Stage window; @OnThread(Tag.FXPlatform) private BorderPane fxContent; private ComboBox<DebuggerThreadDetails> threadList; private ListView<SourceLocation> stackList; private ListView<VarDisplayInfo> staticList, localList, instanceList; private Button stopButton, stepButton, stepIntoButton, continueButton, terminateButton; private final Project project; private boolean autoSelectionEvent = false;
| Fields from these classes (key from map) are only shown if they are in the corresponding whitelist | of fields (corresponding value from map) | private Map<String, Set<String>> restrictedClasses = Collections.emptyMap(); private final SimpleBooleanProperty showingProperty = new SimpleBooleanProperty(false); private final BooleanProperty hideSystemThreads = new SimpleBooleanProperty(true); private final SimpleBooleanProperty cannotStepOrContinue = new SimpleBooleanProperty(true); private final SimpleBooleanProperty cannotHalt = new SimpleBooleanProperty(true); private DebuggerThreadDetails selectedThread;
| Create a window to view and interact with a debug VM. The window optionally shows a thread pane | from which the user can select a thread to control; otherwise the thread selection is controlled | programmatically. | | @param project the project this window is associated with | @param debugger the debugger this window is debugging | @param debuggerThreads an observable list of all threads that should be displayed by the debugger, | or null if the thread pane should not be displayed. | public ExecControls(Project project, Debugger debugger, ObservableList<DebuggerThreadDetails> debuggerThreads) { if (project == null || debugger == null) { throw new NullPointerException("project or debugger null in ExecControls"); } this.project = project; this.window = new Stage(); window.setTitle(Config.getApplicationName() + ": " + Config.getString("debugger.execControls.windowTitle")); BlueJTheme.setWindowIconFX(window); createWindowContent(debuggerThreads); TilePane buttons = new TilePane(Orientation.HORIZONTAL, stopButton, stepButton, stepIntoButton, continueButton, terminateButton); buttons.setPrefColumns(buttons.getChildren().size()); JavaFXUtil.addStyleClass(buttons, "debugger-buttons"); this.fxContent = new BorderPane(); BorderPane vars = new BorderPane(); vars.setTop(labelled(staticList, staticTitle)); SplitPane varSplit = new SplitPane(labelled(instanceList, instanceTitle), labelled(localList, localTitle)); varSplit.setOrientation(Orientation.VERTICAL); vars.setCenter(varSplit); BorderPane lhsPane; if (debuggerThreads != null) { lhsPane = new BorderPane(labelled(stackList, stackTitle), labelled(threadList, threadTitle), null, null, null); JavaFXUtil.addStyleClass(threadList, "debugger-thread-combo"); } else { lhsPane = new BorderPane(labelled(stackList, stackTitle), null, null, null, null); } JavaFXUtil.addStyleClass(lhsPane, "debugger-thread-and-stack"); fxContent.setTop(makeMenuBar()); fxContent.setCenter(new SplitPane(lhsPane, vars)); fxContent.setBottom(buttons); JavaFXUtil.addStyleClass(fxContent, "debugger"); Scene scene = new Scene(fxContent); Config.addDebuggerStylesheets(scene); window.setScene(scene); Config.loadAndTrackPositionAndSize(window, "bluej.debugger"); window.setOnShown(e -> { DataCollector.debuggerChangeVisible(project, true); showingProperty.set(true); }); window.setOnHidden(e -> { DataCollector.debuggerChangeVisible(project, false); showingProperty.set(false); }); JavaFXUtil.addChangeListenerPlatform(showingProperty, show -> { if (show && !window.isShowing()) { window.show(); } else if (!show && window.isShowing()) { window.hide(); } }); } private static Node labelled(Node content, String title) { Label titleLabel = new Label(title); JavaFXUtil.addStyleClass(titleLabel, "debugger-section-title"); BorderPane borderPane = new BorderPane(content, titleLabel, null, null, null); JavaFXUtil.addStyleClass(borderPane, "debugger-section"); return borderPane; }
| Sets the restricted classes - classes for which only some fields should be displayed. | | @param restrictedClasses a map of class name to a set of white-listed fields. | public void setRestrictedClasses(Map<String, Set<String>> restrictedClasses) { this.restrictedClasses = restrictedClasses; } public Map> getRestrictedClasses() { HashMap<String, Set<String>> copy = new HashMap<String, Set<String>>(); for (Map.Entry<String, Set<String>> e : restrictedClasses.entrySet()) { copy.put(e.getKey(), new HashSet<String>(e.getValue())); } return copy; }
| Make sure that a particular thread is displayed and the details are up-to-date. | Note that if the controls window is invisible this will not show it. | | @param dt the thread to highlight in the thread | tree and whose status we want to display. | public void selectThread(final DebuggerThread dt) { if (threadList != null) { if (dt.isKnownSystemThread()) { hideSystemThreads.set(false); } DebuggerThreadDetails details = threadList.getItems().stream() .filter(d -> d.isThread(dt)) .findFirst().orElse(null); if (details != null) { threadList.getSelectionModel().select(details); } } else if (selectedThread == null || ! dt.sameThread(selectedThread.getThread())) { selectedThreadChanged(new DebuggerThreadDetails(dt)); } }
| Update the details displayed for the given thread (if they are currently displayed). | public void updateThreadDetails(DebuggerThread dt) { if (selectedThread != null && selectedThread.isThread(dt)) { if (threadList == null) { selectedThread.update(); } setThreadDetails(selectedThread); } } private void selectedThreadChanged(DebuggerThreadDetails dt) { if (dt == null) { selectedThread = null; cannotHalt.set(true); cannotStepOrContinue.set(true); stackList.getItems().clear(); } else { selectedThread = dt; setThreadDetails(dt); } }
| Display the details for the currently selected thread. | These details include showing the threads stack, and displaying | the details for the top stack frame. | private void setThreadDetails(DebuggerThreadDetails dt) { List<SourceLocation> stack = new ArrayList<>(dt.getThread().getStack()); List<SourceLocation> filtered = Arrays.asList(getFilteredStack(stack)); boolean isSuspended = dt.isSuspended(); cannotHalt.set(isSuspended); cannotStepOrContinue.set(!isSuspended); stackList.getItems().setAll(filtered); if (filtered.size() > 0) { autoSelectionEvent = true; stackList.getSelectionModel().select(0); autoSelectionEvent = false; } } public static SourceLocation [] getFilteredStack(List<SourceLocation> stack) { int first = -1; int i; for (i = 0; i < stack.size(); i++) { SourceLocation loc = stack.get(i); String className = loc.getClassName(); if (className.startsWith("bluej.runtime.") && !className.equals(bluej.runtime.BJInputStream.class.getCanonicalName())) { break; } if (JavaNames.getBase(className).startsWith("__SHELL")) { break; } if (Config.isGreenfoot() && className.startsWith("greenfoot.core.Simulation")) { break; } if (first == -1 && loc.getFileName() != null) { first = i; } } if (first == -1 || i == 0) { return new SourceLocation[0]; } SourceLocation[] filtered = new SourceLocation[i - first]; for (int j = first; j < i; j++) { filtered[j - first] = stack.get(j); } return filtered; }
| Clear the display of thread details (stack and variables). | private void clearThreadDetails() { stackList.getItems().clear(); staticList.getItems().clear(); instanceList.getItems().clear(); localList.getItems().clear(); }
| Make a stack frame in the stack display the selected stack frame. | This will cause this frame's details (local variables, etc.) to be | displayed, as well as the current source position being marked. | private void stackFrameSelectionChanged(DebuggerThread selectedThread, int index) { if (index >= 0) { setStackFrameDetails(selectedThread, index); selectedThread.setSelectedFrame(index); if (! autoSelectionEvent) { project.showSource(selectedThread, selectedThread.getClass(index), selectedThread.getClassSourceName(index), selectedThread.getLineNumber(index)); } } }
| Display the detail information (current object fields and local var's) | for a specific stack frame. | private void setStackFrameDetails(DebuggerThread selectedThread, int frameNo) { try { DebuggerClass currentClass = selectedThread.getCurrentClass(frameNo); DebuggerObject currentObject = selectedThread.getCurrentObject(frameNo); if (currentClass != null) { List<DebuggerField> fields = currentClass.getStaticFields(); List<VarDisplayInfo> listData = new ArrayList<>(fields.size()); for (DebuggerField field : fields) { String declaringClass = field.getDeclaringClassName(); Set<String> whiteList = restrictedClasses.get(declaringClass); if (whiteList == null || whiteList.contains(field.getName())) { listData.add(new VarDisplayInfo(field)); } } staticList.getItems().setAll(listData); } if (currentObject != null && !currentObject.isNullObject()) { List<DebuggerField> fields = currentObject.getFields(); List<VarDisplayInfo> listData = new ArrayList<>(fields.size()); for (DebuggerField field : fields) { if (! Modifier.isStatic(field.getModifiers())) { String declaringClass = field.getDeclaringClassName(); Set<String> whiteList = restrictedClasses.get(declaringClass); if (whiteList == null || whiteList.contains(field.getName())) { listData.add(new VarDisplayInfo(field)); } } } instanceList.getItems().setAll(listData); } else { instanceList.getItems().clear(); } localList.getItems().setAll(selectedThread.getLocalVariables(frameNo)); } catch (VMDisconnectedException vmde) { } }
| Create and arrange the GUI components. | @param debuggerThreads | private void createWindowContent(ObservableList<DebuggerThreadDetails> debuggerThreads) { stopButton = new StopAction().makeButton(); stepButton = new StepAction().makeButton(); stepIntoButton = new StepIntoAction().makeButton(); continueButton = new ContinueAction().makeButton(); terminateButton = new TerminateAction().makeButton(); stepButton.disableProperty().bind(cannotStepOrContinue); stepIntoButton.disableProperty().bind(cannotStepOrContinue); continueButton.disableProperty().bind(cannotStepOrContinue); stopButton.disableProperty().bind(cannotHalt); staticList = makeVarListView(); JavaFXUtil.addStyleClass(staticList, "debugger-static-var-list"); instanceList = makeVarListView(); localList = makeVarListView(); stackList = new ListView<>(); stackList.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); JavaFXUtil.addStyleClass(stackList, "debugger-stack"); stackList.styleProperty().bind(PrefMgr.getEditorFontCSS(false)); JavaFXUtil.addChangeListenerPlatform(stackList.getSelectionModel().selectedIndexProperty(), index -> stackFrameSelectionChanged((getSelectedThreadDetails() == null ? null : getSelectedThreadDetails().getThread()), index.intValue())); Label placeholder = new Label(removeHTML(Config.getString("debugger.threadRunning"))); placeholder.setTextAlignment(TextAlignment.CENTER); stackList.setPlaceholder(placeholder); if (debuggerThreads != null) { FilteredList<DebuggerThreadDetails> filteredThreads = new FilteredList<>(debuggerThreads, this::showThread); threadList = new ComboBox<>(filteredThreads); JavaFXUtil.addChangeListenerPlatform(hideSystemThreads, sys -> { filteredThreads.setPredicate(null); filteredThreads.setPredicate(this::showThread); }); JavaFXUtil.addChangeListenerPlatform(threadList.getSelectionModel().selectedItemProperty(), this::selectedThreadChanged); } } private static String removeHTML(String label) { return label.replace("<html>", "").replace("<center>", "").replace("<br>", "\n").replace("</html>", ""); } private boolean showThread(DebuggerThreadDetails thread) { if (hideSystemThreads.get()) return !thread.getThread().isKnownSystemThread(); else{ return true; } } private ListView makeVarListView() { ListView<VarDisplayInfo> listView = new ListView<>(); listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); listView.setCellFactory(lv -> { return new VarDisplayCell(project, window); }); return listView; }
| Create the debugger's menubar, all menus and items. | private MenuBar makeMenuBar() { MenuBar menubar = new MenuBar(); menubar.setUseSystemMenuBar(true); Menu menu = new Menu(Config.getString("terminal.options")); if (!Config.isGreenfoot()) { MenuItem systemThreadItem = JavaFXUtil.makeCheckMenuItem(Config.getString("debugger.hideSystemThreads"), hideSystemThreads, null); menu.getItems().add(systemThreadItem); menu.getItems().add(new SeparatorMenuItem()); } menu.getItems().add(JavaFXUtil.makeMenuItem(Config.getString("close"), this::hide, new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN))); menubar.getMenus().add(menu); return menubar; } public void show() { window.show(); window.toFront(); } public void hide() { window.hide(); } public DebuggerThreadDetails getSelectedThreadDetails() { if (threadList != null) { return threadList.getSelectionModel().getSelectedItem(); } return selectedThread; } public BooleanProperty showingProperty() { return showingProperty; }
| Action to halt the selected thread. | private class StopAction extends FXAbstractAction { public StopAction() { super(haltButtonText, Config.makeStopIcon(true)); } public void actionPerformed(boolean viaContextMenu) { DebuggerThreadDetails details = getSelectedThreadDetails(); if (details == null) return; clearThreadDetails(); if (!details.isSuspended()) { details.getThread().halt(); } } }
| Action to step through the code. | private class StepAction extends FXAbstractAction { public StepAction() { super(stepButtonText, makeStepIcon()); } public void actionPerformed(boolean viaContextMenu) { DebuggerThreadDetails details = getSelectedThreadDetails(); if (details == null) return; clearThreadDetails(); project.removeStepMarks(); if (details.isSuspended()) { details.getThread().step(); } project.updateInspectors(); } } private static Node makeStepIcon() { Polygon arrowShape = makeScaledUpArrow(false); JavaFXUtil.addStyleClass(arrowShape, "step-icon-arrow"); Rectangle bar = new Rectangle(28, 6); JavaFXUtil.addStyleClass(bar, "step-icon-bar"); VBox vBox = new VBox(arrowShape, bar); JavaFXUtil.addStyleClass(vBox, "step-icon"); return vBox; } private static Polygon makeScaledUpArrow(boolean shortTail) { Polygon arrowShape = Config.makeArrowShape(shortTail); JavaFXUtil.scalePolygonPoints(arrowShape, 1.5, true); return arrowShape; } private static Node makeContinueIcon() { Polygon arrowShape1 = makeScaledUpArrow(true); Polygon arrowShape2 = makeScaledUpArrow(true); Polygon arrowShape3 = makeScaledUpArrow(true); JavaFXUtil.addStyleClass(arrowShape1, "continue-icon-arrow"); JavaFXUtil.addStyleClass(arrowShape2, "continue-icon-arrow"); JavaFXUtil.addStyleClass(arrowShape3, "continue-icon-arrow"); arrowShape1.setOpacity(0.2); arrowShape2.setOpacity(0.5); Pane pane = new Pane(arrowShape1, arrowShape2, arrowShape3); arrowShape2.setLayoutX(2.0); arrowShape2.setLayoutY(6.0); arrowShape3.setLayoutX(4.0); arrowShape3.setLayoutY(12.0); return pane; }
| Action to "step into" the code. |*/ private class StepIntoAction extends FXAbstractAction { public StepIntoAction() { super(stepIntoButtonText, makeStepIntoIcon()); } | |public void actionPerformed(boolean viaContextMenu) | |{ | |DebuggerThreadDetails details = getSelectedThreadDetails(); | |if (details == null) | |return; | |clearThreadDetails(); | |project.removeStepMarks(); | |if (details.isSuspended()) { | |details.getThread().stepInto(); | |} | |} | |} | |private static Node makeStepIntoIcon() | |{ | |SVGPath path = new SVGPath(); | |// See http://jxnblk.com/paths/?d=M2%2016%20Q24%208%2038%2016%20L40%2010%20L48%2026%20L32%2034%20L34%2028%20Q22%2022%206%2028%20Z | |path.setContent("M2 16 Q24 8 38 16 L40 10 L48 26 L32 34 L34 28 Q22 22 6 28 Z"); path.setScaleX(0.75); path.setScaleY(0.85); JavaFXUtil.addStyleClass(path, "step-into-icon"); return new Group(path); } /** * Action to continue a halted thread. */ private class ContinueAction extends FXAbstractAction { public ContinueAction() { | |super(continueButtonText, makeContinueIcon()); | |} | |public void actionPerformed(boolean viaContextMenu) | |{ | |DebuggerThreadDetails details = getSelectedThreadDetails(); | |if (details == null) | |return; | |clearThreadDetails(); | |project.removeStepMarks(); | |if (details.isSuspended()) { | |details.getThread().cont(); | |DataCollector.debuggerContinue(project, details.getThread().getName()); | |} | |} | |} | |/** | Action to terminate the program, restart the VM. | private class TerminateAction extends FXAbstractAction { public TerminateAction() { super(terminateButtonText, makeTerminateIcon()); } public void actionPerformed(boolean viaContextMenu) { try { clearThreadDetails(); project.restartVM(); DataCollector.debuggerTerminate(project); } catch (IllegalStateException ise) { } } } private static Node makeTerminateIcon() { Polygon s = new Polygon( 5, 0, 15, 10, 25, 0, 30, 5, 20, 15, 30, 25, 25, 30, 15, 20, 5, 30, 0, 25, 10, 15, 0, 5 ); JavaFXUtil.addStyleClass(s, "terminate-icon"); return s; }
| A cell in a list view which has a variable's type, name and value. (And optionally, access modifier) | private static class VarDisplayCell extends javafx.scene.control.ListCell<VarDisplayInfo> { private final Label access = new Label(); private final Label type = new Label(); private final Label name = new Label(); private final Label value = new Label(); private final BooleanProperty nonEmpty = new SimpleBooleanProperty(); private static final Image objectImage = Config.getImageAsFXImage("image.eval.object"); private final SimpleObjectProperty<Supplier<DebuggerObject>> fetchObject = new SimpleObjectProperty<>(null); public VarDisplayCell(Project project, Window window) { ImageView objectImageView = new ImageView(objectImage); JavaFXUtil.addStyleClass(objectImageView, "debugger-var-object-ref"); objectImageView.visibleProperty().bind(fetchObject.isNotNull()); objectImageView.managedProperty().bind(objectImageView.visibleProperty()); objectImageView.setOnMouseClicked(e -> { if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 1) inspect(project, window, objectImageView); }); HBox hBox = new HBox(access, type, name, new Label("="), objectImageView, this.value); hBox.visibleProperty().bind(nonEmpty); hBox.styleProperty().bind(PrefMgr.getEditorFontCSS(false)); JavaFXUtil.addStyleClass(hBox, "debugger-var-cell"); JavaFXUtil.addStyleClass(access, "debugger-var-access"); JavaFXUtil.addStyleClass(type, "debugger-var-type"); JavaFXUtil.addStyleClass(name, "debugger-var-name"); JavaFXUtil.addStyleClass(value, "debugger-var-value"); setGraphic(hBox); hBox.setOnMouseClicked(e -> { if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) { inspect(project, window, objectImageView); } }); } @OnThread(Tag.FXPlatform) private void inspect(Project project, Window window, Node sourceNode) { if (fetchObject.get() != null) { project.getInspectorInstance(fetchObject.get().get(), null, null, null, window, sourceNode); } } @Override @OnThread(value = Tag.FXPlatform, ignoreParent = true) protected void updateItem(VarDisplayInfo item, boolean empty) { super.updateItem(item, empty); nonEmpty.set(!empty); if (empty) { access.setText(""); type.setText(""); name.setText(""); value.setText(""); fetchObject.set(null); } else { access.setText(item.getAccess()); type.setText(item.getType()); name.setText(item.getName()); value.setText(item.getValue()); fetchObject.set(item.getFetchObject()); } } } }
top, use, map, class ExecControls

.   ExecControls
.   labelled
.   setRestrictedClasses
.   getRestrictedClasses
.   selectThread
.   updateThreadDetails
.   selectedThreadChanged
.   setThreadDetails
.   getFilteredStack
.   clearThreadDetails
.   stackFrameSelectionChanged
.   setStackFrameDetails
.   createWindowContent
.   removeHTML
.   showThread
.   makeVarListView
.   makeMenuBar
.   show
.   hide
.   getSelectedThreadDetails
.   showingProperty

top, use, map, class StopAction

.   StopAction
.   actionPerformed

top, use, map, class StepAction

.   StepAction
.   actionPerformed
.   makeStepIcon
.   makeScaledUpArrow
.   makeContinueIcon

top, use, map, class TerminateAction

.   TerminateAction
.   actionPerformed
.   makeTerminateIcon
.   VarDisplayCell
.   inspect
.   updateItem




905 neLoCode + 71 LoComm