├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── documentation ├── README.md └── pictures │ └── VD-architecture.svg ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── no │ │ └── hvl │ │ └── tk │ │ └── visual │ │ └── debugger │ │ ├── DebugProcessListener.java │ │ ├── SharedState.java │ │ ├── debugging │ │ ├── stackframe │ │ │ ├── IStackFrame.java │ │ │ ├── StackFrameAnalyzer.java │ │ │ ├── StackFrameProxyImplAdapter.java │ │ │ ├── StackFrameSessionListener.java │ │ │ ├── StackFrameSessionListenerHelper.java │ │ │ └── exceptions │ │ │ │ └── StackFrameAnalyzerException.java │ │ └── visualization │ │ │ ├── DebuggingInfoVisualizer.java │ │ │ ├── DebuggingInfoVisualizerBase.java │ │ │ ├── PlantUmlDebuggingVisualizer.java │ │ │ ├── WebSocketDebuggingVisualizer.java │ │ │ └── cef │ │ │ └── SimpleDownloadHandler.java │ │ ├── domain │ │ ├── ODAttributeValue.java │ │ ├── ODLink.java │ │ ├── ODObject.java │ │ ├── ODPrimitiveRootValue.java │ │ ├── ObjectDiagram.java │ │ ├── ObjectDiagramBuilder.java │ │ └── PrimitiveTypes.java │ │ ├── server │ │ ├── ServerConstants.java │ │ ├── UIServerStarter.java │ │ ├── VisualDebuggingAPIServerStarter.java │ │ └── endpoint │ │ │ ├── UIConfig.java │ │ │ ├── VisualDebuggingAPIEndpoint.java │ │ │ └── message │ │ │ ├── DebuggingMessageType.java │ │ │ └── DebuggingWSMessage.java │ │ ├── settings │ │ ├── DebuggingVisualizerOption.java │ │ ├── PluginSettingsState.java │ │ ├── VisualDebuggerSettingsComponent.java │ │ └── VisualDebuggerSettingsConfigurable.java │ │ ├── ui │ │ ├── CopyPlantUMLDialog.java │ │ ├── VisualDebuggerIcons.java │ │ ├── VisualDebuggerNotifications.java │ │ └── actions │ │ │ ├── browser │ │ │ └── OpenBrowserAction.java │ │ │ ├── settings │ │ │ └── OpenSettingsAction.java │ │ │ └── stop │ │ │ └── StopVisualDebuggerAction.java │ │ └── util │ │ ├── ClassloaderUtil.java │ │ ├── DiagramToJSONConverter.java │ │ └── Executable.java └── resources │ ├── META-INF │ ├── plugin.xml │ └── pluginIcon.svg │ ├── icons │ ├── browser.svg │ ├── browser_dark.svg │ ├── icon.svg │ ├── settings.svg │ ├── settings_dark.svg │ ├── stop.svg │ └── stop_dark.svg │ └── ui │ ├── app.css │ ├── app.js │ ├── app.js.map │ ├── bpmn.eot │ ├── bpmn.svg │ ├── bpmn.ttf │ ├── bpmn.woff │ ├── bpmn.woff2 │ ├── fa.eot │ ├── fa.svg │ ├── fa.ttf │ ├── fa.woff │ ├── fa.woff2 │ ├── favicon.ico │ ├── index.html │ ├── od.eot │ ├── od.svg │ ├── od.ttf │ ├── od.woff │ └── od.woff2 └── test └── java └── no └── hvl └── tk └── visual └── debugger ├── debugging ├── stackframe │ ├── StackFrameAnalyzerTest.java │ └── mocks │ │ ├── ArrayReferenceMock.java │ │ ├── EntryObjectReferenceMock.java │ │ ├── FieldMock.java │ │ ├── InterfaceTypeMock.java │ │ ├── LocalVariableMock.java │ │ ├── MethodMock.java │ │ ├── ObjectReferenceMock.java │ │ ├── ReferenceTypeMock.java │ │ ├── StackFrameMock.java │ │ ├── StackFrameMockHelper.java │ │ ├── StringReferenceMock.java │ │ ├── TypeMock.java │ │ └── value │ │ ├── BooleanValueMock.java │ │ ├── ByteValueMock.java │ │ ├── CharValueMock.java │ │ ├── DoubleValueMock.java │ │ ├── FloatValueMock.java │ │ ├── IntegerValueMock.java │ │ ├── LongValueMock.java │ │ └── ShortValueMock.java └── visualization │ └── PlantUmlDebuggingVisualizerTest.java ├── domain ├── ODAttributeValueTest.java ├── ODLinkTest.java └── ODPrimitiveRootValueTest.java ├── manueltests ├── DebuggingScenariosTest.java ├── bst │ ├── BSTNode.java │ └── BSTNodeTest.java ├── holders │ ├── CollectionHolder.java │ └── PrimitiveCollectionHolder.java └── partsList │ ├── PartsListTest.java │ └── domain │ ├── Component.java │ ├── Material.java │ ├── Product.java │ ├── QuantifiedComponent.java │ └── exception │ └── CycleException.java ├── server ├── UIServerStarterTest.java └── endpoint │ └── message │ └── TypedWebsocketMessageTest.java ├── settings ├── DebuggingVisualizerOptionTest.java └── VisualDebuggerSettingsComponentTest.java └── util └── DiagramToJSONConverterTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | src/main/resources/ui/** linguist-generated -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | pull_request: 5 | types: [ opened, synchronize, reopened ] 6 | workflow_dispatch: 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 15 | - name: Set up JDK 21 16 | uses: actions/setup-java@v4 17 | with: 18 | distribution: 'temurin' 19 | java-version: 21 20 | cache: 'gradle' 21 | - name: Cache SonarCloud packages 22 | uses: actions/cache@v4 23 | with: 24 | path: ~/.sonar/cache 25 | key: ${{ runner.os }}-sonar 26 | restore-keys: ${{ runner.os }}-sonar 27 | - name: Grant execute permission for gradlew 28 | run: chmod +x gradlew 29 | - name: Build and analyze 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 32 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 33 | run: ./gradlew check sonar --info 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /.gradle/ 3 | /build/ 4 | /build/classes/java/main/ 5 | /bin/ 6 | .idea/ 7 | .intellijPlatform 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Test & Analyze](https://github.com/timKraeuter/VisualDebugger/workflows/Build/badge.svg) 2 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=timKraeuter_VisualDebugger&metric=alert_status)](https://sonarcloud.io/dashboard?id=timKraeuter_VisualDebugger) 3 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=timKraeuter_VisualDebugger&metric=ncloc)](https://sonarcloud.io/dashboard?id=timKraeuter_VisualDebugger) 4 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=timKraeuter_VisualDebugger&metric=coverage)](https://sonarcloud.io/dashboard?id=timKraeuter_VisualDebugger) 5 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=timKraeuter_VisualDebugger&metric=bugs)](https://sonarcloud.io/summary/new_code?id=timKraeuter_VisualDebugger) 6 | [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=timKraeuter_VisualDebugger&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=timKraeuter_VisualDebugger) 7 | [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=timKraeuter_VisualDebugger&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=timKraeuter_VisualDebugger) 8 | 9 | # Visual Debugger IntelliJ Plugin 10 | 11 | This is the repository for the [Visual Debugger IntelliJ plugin](https://plugins.jetbrains.com/plugin/16851-visual-debugger). 12 | - A **demo** is available on [YouTube](https://www.youtube.com/watch?v=LsAMTnLxWJw). 13 | - A short plugin documentation for developers can be found [here](./documentation/README.md). 14 | - The browser-based UI for the plugin can be found [here](https://github.com/timKraeuter/object-diagram-modeler/tree/master/debugger). 15 | 16 | Preprints of my *research papers* about the Visual Debugger can be found [here](https://github.com/timKraeuter/ICSME-2022/blob/main/visual-debugger.pdf) (ICSME-2022) and [here](https://github.com/timKraeuter/ICSE-2024/blob/main/paper.pdf) (IDE@ICSE-2024). 17 | 18 | # Features 19 | 20 | 1. The debugger **highlights changes** by computing a diff using [object-diagram-js-differ](https://github.com/timKraeuter/object-diagram-js-differ): 21 | 22 | ![PNG showing a diff](https://github.com/timKraeuter/object-diagram-js/blob/master/documentation/diff.png) 23 | 24 | 3. The debugger saves the **debugging history** such that a user can step back in the UI: 25 | 26 | ![Gif showing the history feature](https://github.com/timKraeuter/object-diagram-js/blob/master/documentation/steps.gif) 27 | 28 | # Citation 29 | [![DOI](https://zenodo.org/badge/359495483.svg)](https://zenodo.org/doi/10.5281/zenodo.10018177) 30 | 31 | Using the DOI above, you can cite the Visual Debugger tool in your publications. 32 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.intellij.platform' version '2.5.0' 4 | id 'org.sonarqube' version '5.1.0.4882' 5 | id 'jacoco' 6 | id 'net.ltgt.errorprone' version '4.0.1' 7 | id 'com.diffplug.spotless' version '6.25.0' 8 | } 9 | 10 | group 'no.hvl.tk' 11 | version '2.3.6' 12 | 13 | java { 14 | sourceCompatibility = JavaVersion.VERSION_21 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | intellijPlatform { 20 | defaultRepositories() 21 | } 22 | } 23 | 24 | dependencies { 25 | intellijPlatform { 26 | instrumentationTools() 27 | intellijIdeaCommunity('2024.2') 28 | bundledPlugin('com.intellij.java') 29 | } 30 | 31 | // https://mvnrepository.com/artifact/net.sourceforge.plantuml/plantuml 32 | implementation group: 'net.sourceforge.plantuml', name: 'plantuml', version: '1.2023.10' 33 | // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 34 | implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' 35 | 36 | // Websocket server and server container dependencies 37 | // https://mvnrepository.com/artifact/jakarta.websocket/jakarta.websocket-api 38 | compileOnly group: 'jakarta.websocket', name: 'jakarta.websocket-api', version: '2.1.1' 39 | // https://mvnrepository.com/artifact/org.glassfish.tyrus/tyrus-server 40 | implementation group: 'org.glassfish.tyrus', name: 'tyrus-server', version: '2.2.0' 41 | // https://mvnrepository.com/artifact/org.glassfish.tyrus/tyrus-container-grizzly-server 42 | implementation group: 'org.glassfish.tyrus', name: 'tyrus-container-grizzly-server', version: '2.2.0' 43 | 44 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' 45 | testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.0' 46 | testImplementation 'org.hamcrest:hamcrest:3.0' 47 | testImplementation 'org.mockito:mockito-core:5.7.0' 48 | 49 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' 50 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.1' 51 | 52 | errorprone('com.google.errorprone:error_prone_core:2.16') 53 | } 54 | 55 | patchPluginXml { 56 | changeNotes = '''Update to 2025.2''' 57 | untilBuild.set("252.*") 58 | } 59 | 60 | test { 61 | useJUnitPlatform() 62 | jacoco { 63 | includeNoLocationClasses = true 64 | excludes = ['jdk.internal.*'] 65 | } 66 | } 67 | 68 | tasks.runIde { 69 | jvmArgs('--add-exports', 'java.base/jdk.internal.vm=ALL-UNNAMED') 70 | } 71 | 72 | tasks.named('sonar').configure { 73 | dependsOn jacocoTestReport 74 | } 75 | 76 | spotless { 77 | java { 78 | googleJavaFormat() 79 | formatAnnotations() 80 | } 81 | } 82 | 83 | jacocoTestReport { 84 | classDirectories.setFrom(instrumentCode) 85 | reports { 86 | xml.required = true 87 | } 88 | } 89 | 90 | jacocoTestCoverageVerification { 91 | classDirectories.setFrom(instrumentCode) 92 | } 93 | 94 | sonar { 95 | properties { 96 | property 'sonar.projectKey', 'timKraeuter_VisualDebugger' 97 | property 'sonar.organization', 'timkraeuter' 98 | property 'sonar.host.url', 'https://sonarcloud.io' 99 | } 100 | } 101 | 102 | tasks.compileTestJava { 103 | // Ignore manuel tests. 104 | options.errorprone.excludedPaths = '.*/manueltests/.*' 105 | } -------------------------------------------------------------------------------- /documentation/README.md: -------------------------------------------------------------------------------- 1 | # Short documentation 2 | 3 | This file contains a short documentation of 4 | the [visual debugger plugin](https://plugins.jetbrains.com/plugin/16851-visual-debugger). 5 | 6 | ## General 7 | 8 | The plugin uses the IntelliJ Debugger API to listen to debugging events. The class `DebugProcessListener` listens to new 9 | debugging sessions being started. Then it adds a session listener (`DebugSessionListener`), which is notified whenever 10 | debugging stops. We can then access the debugging variables and create a visual representation as an object diagram. The 11 | visualization can be embedded in the IDE using the embedded visualizer or shown in a browser using the browser 12 | visualizer. 13 | 14 | ## Visualization 15 | 16 | The **embedded visualizer** uses [PlantUML](https://plantuml.com/) to visualize the debugging variables. We use 17 | the [PlantUML Smetana project](https://plantuml.com/smetana02) such that a local installation of DOT is not required. 18 | Since PlantUML generates images, no user interaction is possible. 19 | 20 | Consequently, we have implemented a **browser visualizer** that supports user interaction, i.e., exploring the generated object diagram similar to the exploration in the variables view in the IDE. 21 | The browser visualization is based on the object-diagram modeler ([source](https://github.com/timKraeuter/object-diagram-js), [demo](https://timkraeuter.com/object-diagram-js/)). 22 | 23 | ## Browser visualizer 24 | 25 | The browser visualizer makes use of WebSocket to update the connected clients/browsers whenever the debugging variables 26 | change. An overview of the architecture is shown in the following picture. 27 | 28 | ![Architecture picture showing the plugin connecting to the browser using WebSocket](./pictures/VD-architecture.svg) 29 | 30 | The browser initially connects to the plugin running inside IntelliJ IDEA, which leads to the creation of a WebSocket 31 | session. Using the obtained session, the plugin will update the connected client about any changes to the debugging 32 | variables. One can interact with the object diagram in the browser visualization by double-clicking on objects to load 33 | their immediate children. This communication also uses the established WebSocket connection. 34 | 35 | The user interface implementation is decoupled from the IDEA, such that it can be reused for debugging visualization, 36 | for example, together with Eclipse IDE. The source code for the UI can be found [here](https://github.com/timKraeuter/object-diagram-modeler/tree/master/debugger). 37 | 38 | ### Visual Debugging API 39 | 40 | The object diagram is transferred using JSON. 41 | You can find an implementation of the Visual Debugging API in the class `VisualDebuggingAPIEndpoint`. 42 | From the point of view of the UI, the following messages are transferred. 43 | 44 | #### Incoming WebSocket messages 45 | 46 | The UI expects incoming messages to have a specific JSON format. It has a type (`nextDebugStep`, `loadChildren`, or `config`) and a 47 | content which JSON conforming to the ObjectDiagram class. 48 | 49 | ```json 50 | { 51 | "type": "nextDebugStep", 52 | "content": "JSON data here...", 53 | "fileName": "PartsListTest", 54 | "line": "12" 55 | } 56 | ``` 57 | 58 | `nextDebugStep` indicates that new debugging data is available. The UI will use the content to draw a new object diagram 59 | discarding all previous information. 60 | 61 | `loadChildren` is the response to a call of the UI to load more details for a given object. The response should only 62 | contain the object and its children. The UI will merge this information with the currently shown object diagram. 63 | The fields `fileName` and `line` are not needed. 64 | 65 | `config` is expected after a connection has been initialized or the configuration has changed. 66 | It contains how many debugging steps should be saved in the history of the UI using the field `savedDebugSteps` inside `content`. 67 | Furthermore, it contains if additions and changes should be highlighted in a boolean field `coloredDiff` (see class `UIConfig`). 68 | 69 | #### Outgoing WebSocket messages 70 | 71 | The UI will send a plain string containing an object id to load more information for this object. The response should 72 | use the type `loadChildren` and be structured as described above. 73 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'VisualDebugger' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/DebugProcessListener.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger; 2 | 3 | import com.intellij.xdebugger.XDebugProcess; 4 | import com.intellij.xdebugger.XDebugSession; 5 | import com.intellij.xdebugger.XDebuggerManagerListener; 6 | import no.hvl.tk.visual.debugger.debugging.stackframe.StackFrameSessionListener; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class DebugProcessListener implements XDebuggerManagerListener { 10 | 11 | @Override 12 | public void processStarted(@NotNull final XDebugProcess debugProcess) { 13 | final XDebugSession debugSession = debugProcess.getSession(); 14 | debugSession.addSessionListener(new StackFrameSessionListener(debugProcess)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/SharedState.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger; 2 | 3 | import jakarta.websocket.Session; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | import no.hvl.tk.visual.debugger.debugging.stackframe.StackFrameSessionListener; 7 | import org.glassfish.grizzly.http.server.HttpServer; 8 | import org.glassfish.tyrus.server.Server; 9 | 10 | public class SharedState { 11 | 12 | private static final Set manuallyExploredObjects = new HashSet<>(); 13 | 14 | private SharedState() {} 15 | 16 | // UI / Debug API related 17 | private static HttpServer uiServer; 18 | private static Server debugAPIServer; 19 | 20 | /** All currently connected websocket client which will get updated. */ 21 | private static final Set websocketClients = new HashSet<>(); 22 | 23 | /** Last diagram JSON for newly connecting clients. */ 24 | private static String lastDiagramJSON = ""; 25 | 26 | private static String debugFileName; 27 | private static Integer debugLine; 28 | 29 | private static boolean debuggingActive = false; 30 | 31 | private static boolean embeddedBrowserActive = false; 32 | private static StackFrameSessionListener debugSessionListener; 33 | 34 | /** Last plant UML diagram input needed for the print function. */ 35 | private static String lastPlantUMLDiagram = ""; 36 | 37 | public static String getLastPlantUMLDiagram() { 38 | return lastPlantUMLDiagram; 39 | } 40 | 41 | public static void setLastPlantUMLDiagram(final String diagram) { 42 | lastPlantUMLDiagram = diagram; 43 | } 44 | 45 | public static boolean isDebuggingActive() { 46 | return debuggingActive; 47 | } 48 | 49 | public static void setDebuggingActive(final boolean debuggingActive) { 50 | SharedState.debuggingActive = debuggingActive; 51 | } 52 | 53 | public static StackFrameSessionListener getDebugListener() { 54 | return debugSessionListener; 55 | } 56 | 57 | public static void setDebugListener(final StackFrameSessionListener debugSessionListener) { 58 | SharedState.debugSessionListener = debugSessionListener; 59 | } 60 | 61 | public static Server getDebugAPIServer() { 62 | return debugAPIServer; 63 | } 64 | 65 | public static void setDebugAPIServer(final Server debugAPIServer) { 66 | SharedState.debugAPIServer = debugAPIServer; 67 | } 68 | 69 | public static Set getWebsocketClients() { 70 | return websocketClients; 71 | } 72 | 73 | public static void addWebsocketClient(final Session clientSession) { 74 | websocketClients.add(clientSession); 75 | } 76 | 77 | public static void removeWebsocketClient(final Session clientSession) { 78 | websocketClients.remove(clientSession); 79 | } 80 | 81 | public static String getLastDiagramJSON() { 82 | return lastDiagramJSON; 83 | } 84 | 85 | public static void setLastDiagramJSON(final String diagramJSON) { 86 | SharedState.lastDiagramJSON = diagramJSON; 87 | } 88 | 89 | public static void setUIServer(final HttpServer server) { 90 | SharedState.uiServer = server; 91 | } 92 | 93 | public static HttpServer getUiServer() { 94 | return uiServer; 95 | } 96 | 97 | public static String getDebugFileName() { 98 | return debugFileName; 99 | } 100 | 101 | public static Integer getDebugLine() { 102 | return debugLine; 103 | } 104 | 105 | public static void setDebugFileName(String lastDebugFileName) { 106 | SharedState.debugFileName = lastDebugFileName; 107 | } 108 | 109 | public static void setDebugLine(Integer lastDebugLine) { 110 | SharedState.debugLine = lastDebugLine; 111 | } 112 | 113 | public static Set getManuallyExploredObjects() { 114 | return manuallyExploredObjects; 115 | } 116 | 117 | public static boolean isEmbeddedBrowserActive() { 118 | return embeddedBrowserActive; 119 | } 120 | 121 | public static void setEmbeddedBrowserActive(boolean embeddedBrowserActive) { 122 | SharedState.embeddedBrowserActive = embeddedBrowserActive; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/stackframe/IStackFrame.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe; 2 | 3 | import com.intellij.debugger.engine.evaluation.EvaluateException; 4 | import com.intellij.debugger.jdi.LocalVariableProxyImpl; 5 | import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; 6 | import com.sun.jdi.ObjectReference; 7 | import com.sun.jdi.Value; 8 | import java.util.List; 9 | 10 | public interface IStackFrame { 11 | 12 | ObjectReference thisObject() throws EvaluateException; 13 | 14 | List visibleVariables() throws EvaluateException; 15 | 16 | Value getValue(LocalVariableProxyImpl localVariable) throws EvaluateException; 17 | 18 | ThreadReferenceProxyImpl threadProxy(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/stackframe/StackFrameProxyImplAdapter.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe; 2 | 3 | import com.intellij.debugger.engine.evaluation.EvaluateException; 4 | import com.intellij.debugger.jdi.LocalVariableProxyImpl; 5 | import com.intellij.debugger.jdi.StackFrameProxyImpl; 6 | import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; 7 | import com.sun.jdi.ObjectReference; 8 | import com.sun.jdi.Value; 9 | import java.util.List; 10 | 11 | public class StackFrameProxyImplAdapter implements IStackFrame { 12 | 13 | private final StackFrameProxyImpl stackFrameProxy; 14 | 15 | public StackFrameProxyImplAdapter(StackFrameProxyImpl stackFrameProxy) { 16 | this.stackFrameProxy = stackFrameProxy; 17 | } 18 | 19 | @Override 20 | public ObjectReference thisObject() throws EvaluateException { 21 | return stackFrameProxy.thisObject(); 22 | } 23 | 24 | @Override 25 | public List visibleVariables() throws EvaluateException { 26 | return stackFrameProxy.visibleVariables(); 27 | } 28 | 29 | @Override 30 | public Value getValue(LocalVariableProxyImpl localVariable) throws EvaluateException { 31 | return stackFrameProxy.getValue(localVariable); 32 | } 33 | 34 | @Override 35 | public ThreadReferenceProxyImpl threadProxy() { 36 | return stackFrameProxy.threadProxy(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/stackframe/StackFrameSessionListener.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe; 2 | 3 | import com.intellij.debugger.engine.JavaStackFrame; 4 | import com.intellij.debugger.jdi.StackFrameProxyImpl; 5 | import com.intellij.execution.process.ProcessEvent; 6 | import com.intellij.execution.process.ProcessListener; 7 | import com.intellij.execution.ui.RunnerLayoutUi; 8 | import com.intellij.openapi.actionSystem.ActionManager; 9 | import com.intellij.openapi.actionSystem.DefaultActionGroup; 10 | import com.intellij.openapi.diagnostic.Logger; 11 | import com.intellij.openapi.ui.SimpleToolWindowPanel; 12 | import com.intellij.openapi.util.Key; 13 | import com.intellij.util.ui.UIUtil; 14 | import com.intellij.xdebugger.XDebugProcess; 15 | import com.intellij.xdebugger.XDebugSession; 16 | import com.intellij.xdebugger.XDebugSessionListener; 17 | import java.awt.*; 18 | import javax.swing.*; 19 | import no.hvl.tk.visual.debugger.SharedState; 20 | import no.hvl.tk.visual.debugger.debugging.stackframe.exceptions.StackFrameAnalyzerException; 21 | import no.hvl.tk.visual.debugger.debugging.visualization.DebuggingInfoVisualizer; 22 | import no.hvl.tk.visual.debugger.debugging.visualization.PlantUmlDebuggingVisualizer; 23 | import no.hvl.tk.visual.debugger.debugging.visualization.WebSocketDebuggingVisualizer; 24 | import no.hvl.tk.visual.debugger.settings.PluginSettingsState; 25 | import no.hvl.tk.visual.debugger.ui.VisualDebuggerIcons; 26 | import org.jetbrains.annotations.NotNull; 27 | 28 | public class StackFrameSessionListener implements XDebugSessionListener { 29 | 30 | private static final Logger LOGGER = Logger.getInstance(StackFrameSessionListener.class); 31 | 32 | // UI constants 33 | private static final String CONTENT_ID = "no.hvl.tk.VisualDebugger"; 34 | private static final String TOOLBAR_ACTION = 35 | "VisualDebugger.VisualizerToolbar"; // has to match with plugin.xml 36 | 37 | private JPanel userInterface; 38 | 39 | private final XDebugSession debugSession; 40 | private DebuggingInfoVisualizer debuggingVisualizer; 41 | 42 | public StackFrameSessionListener(@NotNull XDebugProcess debugProcess) { 43 | this.debugSession = debugProcess.getSession(); 44 | debugProcess 45 | .getProcessHandler() 46 | .addProcessListener( 47 | new ProcessListener() { 48 | @Override 49 | public void startNotified(@NotNull ProcessEvent event) { 50 | StackFrameSessionListener.this.initUIIfNeeded(); 51 | } 52 | 53 | @Override 54 | public void processTerminated(@NotNull ProcessEvent event) { 55 | SharedState.setLastDiagramJSON(""); 56 | } 57 | 58 | @Override 59 | public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { 60 | // not relevant 61 | } 62 | }); 63 | SharedState.setDebugListener(this); 64 | } 65 | 66 | @Override 67 | public void sessionStopped() { 68 | this.debuggingVisualizer.sessionStopped(); 69 | } 70 | 71 | @Override 72 | public void sessionPaused() { 73 | this.startVisualDebugging(); 74 | } 75 | 76 | private void startVisualDebugging() { 77 | if (!SharedState.isDebuggingActive()) { 78 | return; 79 | } 80 | StackFrameProxyImpl stackFrame = getStackFrameProxy(); 81 | 82 | StackFrameAnalyzer stackFrameAnalyzer = 83 | new StackFrameAnalyzer( 84 | new StackFrameProxyImplAdapter(stackFrame), 85 | PluginSettingsState.getInstance().getVisualisationDepth(), 86 | SharedState.getManuallyExploredObjects(), 87 | PluginSettingsState.getInstance().isShowNullValues()); 88 | 89 | if (debugSession.getCurrentPosition() != null) { 90 | String fileName = debugSession.getCurrentPosition().getFile().getNameWithoutExtension(); 91 | int line = debugSession.getCurrentPosition().getLine() + 1; 92 | debuggingVisualizer.addMetadata(fileName, line, stackFrameAnalyzer); 93 | } 94 | 95 | this.debuggingVisualizer.doVisualization(stackFrameAnalyzer.analyze()); 96 | } 97 | 98 | @NotNull private StackFrameProxyImpl getStackFrameProxy() { 99 | JavaStackFrame currentStackFrame = (JavaStackFrame) debugSession.getCurrentStackFrame(); 100 | if (currentStackFrame == null) { 101 | throw new StackFrameAnalyzerException("Current stack frame could not be found!"); 102 | } 103 | 104 | return currentStackFrame.getStackFrameProxy(); 105 | } 106 | 107 | private void initUIIfNeeded() { 108 | if (this.userInterface != null) { 109 | return; 110 | } 111 | this.userInterface = new JPanel(); 112 | userInterface.setLayout(new BorderLayout()); 113 | this.getOrCreateDebuggingInfoVisualizer(); // make sure visualizer is initialized 114 | if (!SharedState.isDebuggingActive()) { 115 | this.resetUIAndAddActivateDebuggingButton(); 116 | } else { 117 | this.debuggingVisualizer.debuggingActivated(); 118 | } 119 | final var uiContainer = new SimpleToolWindowPanel(false, true); 120 | 121 | final var actionManager = ActionManager.getInstance(); 122 | final var actionToolbar = 123 | actionManager.createActionToolbar( 124 | TOOLBAR_ACTION, (DefaultActionGroup) actionManager.getAction(TOOLBAR_ACTION), false); 125 | actionToolbar.setTargetComponent(this.userInterface); 126 | uiContainer.setToolbar(actionToolbar.getComponent()); 127 | uiContainer.setContent(this.userInterface); 128 | 129 | final RunnerLayoutUi ui = this.debugSession.getUI(); 130 | final var content = 131 | ui.createContent( 132 | CONTENT_ID, uiContainer, "Visual Debugger", VisualDebuggerIcons.VD_ICON, null); 133 | content.setCloseable(false); 134 | UIUtil.invokeLaterIfNeeded(() -> ui.addContent(content)); 135 | LOGGER.debug("UI initialized!"); 136 | } 137 | 138 | public void resetUIAndAddActivateDebuggingButton() { 139 | this.userInterface.removeAll(); 140 | SharedState.setEmbeddedBrowserActive(false); 141 | userInterface.setLayout(new BorderLayout()); 142 | 143 | final var activateButton = new JButton("Activate visual debugger"); 144 | activateButton.addActionListener( 145 | actionEvent -> { 146 | SharedState.setDebuggingActive(true); 147 | this.userInterface.remove(activateButton); 148 | this.debuggingVisualizer.debuggingActivated(); 149 | this.userInterface.revalidate(); 150 | }); 151 | this.userInterface.add(activateButton, BorderLayout.NORTH); 152 | 153 | this.userInterface.revalidate(); 154 | this.userInterface.repaint(); 155 | } 156 | 157 | @NotNull public DebuggingInfoVisualizer getOrCreateDebuggingInfoVisualizer() { 158 | if (this.debuggingVisualizer == null) { 159 | switch (PluginSettingsState.getInstance().getVisualizerOption()) { 160 | case WEB_UI -> 161 | this.debuggingVisualizer = new WebSocketDebuggingVisualizer(this.userInterface); 162 | case EMBEDDED -> 163 | this.debuggingVisualizer = new PlantUmlDebuggingVisualizer(this.userInterface); 164 | default -> { 165 | LOGGER.warn("Unrecognized debugging visualizer chosen. Defaulting to web visualizer!"); 166 | this.debuggingVisualizer = new WebSocketDebuggingVisualizer(this.userInterface); 167 | } 168 | } 169 | } 170 | return this.debuggingVisualizer; 171 | } 172 | 173 | public void reprintDiagram() { 174 | this.debuggingVisualizer.reprintDiagram(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/stackframe/StackFrameSessionListenerHelper.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe; 2 | 3 | import com.sun.jdi.*; 4 | import java.util.*; 5 | 6 | public class StackFrameSessionListenerHelper { 7 | private static final String[] INTERNAL_PACKAGES = { 8 | "java.", 9 | "javax.", 10 | "sun.", 11 | "jdk.", 12 | "com.sun.", 13 | "com.intellij.", 14 | "org.junit.", 15 | "jh61b.junit.", 16 | "jh61b.", 17 | }; 18 | 19 | private StackFrameSessionListenerHelper() {} 20 | 21 | static Iterator getIterator(ThreadReference thread, ObjectReference obj) { 22 | ObjectReference i = (ObjectReference) invokeSimple(thread, obj, "iterator"); 23 | return new Iterator<>() { 24 | @Override 25 | public boolean hasNext() { 26 | BooleanValue hasNext = (BooleanValue) invokeSimple(thread, i, "hasNext"); 27 | if (hasNext == null) { 28 | return false; 29 | } 30 | return hasNext.value(); 31 | } 32 | 33 | @Override 34 | public Value next() { 35 | return invokeSimple(thread, i, "next"); 36 | } 37 | }; 38 | } 39 | 40 | static Value invokeSimple(ThreadReference thread, ObjectReference r, String name) { 41 | try { 42 | return r.invokeMethod( 43 | thread, r.referenceType().methodsByName(name).get(0), Collections.emptyList(), 0); 44 | } catch (Exception e) { 45 | return null; 46 | } 47 | } 48 | 49 | static boolean implementsInterface(ObjectReference obj, String iface) { 50 | if (obj.referenceType() instanceof ClassType classType) { 51 | Queue queue = new ArrayDeque<>(classType.interfaces()); 52 | while (!queue.isEmpty()) { 53 | InterfaceType t = queue.poll(); 54 | if (t.name().equals(iface)) { 55 | return true; 56 | } 57 | queue.addAll(t.superinterfaces()); 58 | } 59 | } 60 | return false; 61 | } 62 | 63 | // input format: [package.]ClassName:lineno or [package.]ClassName 64 | static boolean isInternalPackage(final String name) { 65 | return Arrays.stream(INTERNAL_PACKAGES).anyMatch(name::startsWith); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/stackframe/exceptions/StackFrameAnalyzerException.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.exceptions; 2 | 3 | import com.intellij.debugger.engine.evaluation.EvaluateException; 4 | 5 | public class StackFrameAnalyzerException extends RuntimeException { 6 | public StackFrameAnalyzerException(String message) { 7 | super(message); 8 | } 9 | 10 | public StackFrameAnalyzerException(EvaluateException e) { 11 | super(e); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/visualization/DebuggingInfoVisualizer.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.visualization; 2 | 3 | import no.hvl.tk.visual.debugger.debugging.stackframe.StackFrameAnalyzer; 4 | import no.hvl.tk.visual.debugger.domain.ObjectDiagram; 5 | 6 | public interface DebuggingInfoVisualizer { 7 | 8 | void doVisualization(ObjectDiagram diagram); 9 | 10 | void reprintDiagram(); 11 | 12 | void addMetadata(String fileName, Integer line, StackFrameAnalyzer stackFrame); 13 | 14 | ObjectDiagram getObjectWithChildren(String objectId); 15 | 16 | void debuggingActivated(); 17 | 18 | void debuggingDeactivated(); 19 | 20 | void sessionStopped(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/visualization/DebuggingInfoVisualizerBase.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.visualization; 2 | 3 | import no.hvl.tk.visual.debugger.SharedState; 4 | import no.hvl.tk.visual.debugger.debugging.stackframe.StackFrameAnalyzer; 5 | import no.hvl.tk.visual.debugger.domain.*; 6 | 7 | public abstract class DebuggingInfoVisualizerBase implements DebuggingInfoVisualizer { 8 | 9 | private ObjectDiagram diagram; 10 | private StackFrameAnalyzer analyzer; 11 | 12 | protected DebuggingInfoVisualizerBase() { 13 | this.diagram = new ObjectDiagram(); 14 | } 15 | 16 | @Override 17 | public void addMetadata(String fileName, Integer line, StackFrameAnalyzer stackFrameAnalyzer) { 18 | SharedState.setDebugLine(line); 19 | SharedState.setDebugFileName(fileName); 20 | this.analyzer = stackFrameAnalyzer; 21 | } 22 | 23 | @Override 24 | public void reprintDiagram() { 25 | this.doVisualizationFurther(diagram); 26 | } 27 | 28 | protected abstract void doVisualizationFurther(ObjectDiagram diagram); 29 | 30 | @Override 31 | public void sessionStopped() { 32 | SharedState.getManuallyExploredObjects().clear(); 33 | } 34 | 35 | @Override 36 | public ObjectDiagram getObjectWithChildren(String objectID) { 37 | SharedState.getManuallyExploredObjects().add(objectID); 38 | return analyzer.getChildren(objectID); 39 | } 40 | 41 | @Override 42 | public void doVisualization(ObjectDiagram diagram) { 43 | this.diagram = diagram; 44 | this.doVisualizationFurther(diagram); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/visualization/PlantUmlDebuggingVisualizer.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.visualization; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.ui.components.JBScrollPane; 5 | import java.awt.*; 6 | import java.io.ByteArrayInputStream; 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | import java.util.*; 10 | import java.util.List; 11 | import javax.imageio.ImageIO; 12 | import javax.swing.*; 13 | import net.sourceforge.plantuml.FileFormat; 14 | import net.sourceforge.plantuml.FileFormatOption; 15 | import net.sourceforge.plantuml.SourceStringReader; 16 | import no.hvl.tk.visual.debugger.SharedState; 17 | import no.hvl.tk.visual.debugger.domain.ODAttributeValue; 18 | import no.hvl.tk.visual.debugger.domain.ODLink; 19 | import no.hvl.tk.visual.debugger.domain.ODObject; 20 | import no.hvl.tk.visual.debugger.domain.ObjectDiagram; 21 | import no.hvl.tk.visual.debugger.ui.CopyPlantUMLDialog; 22 | 23 | public class PlantUmlDebuggingVisualizer extends DebuggingInfoVisualizerBase { 24 | private static final Logger LOGGER = Logger.getInstance(PlantUmlDebuggingVisualizer.class); 25 | private static final String NULL = "null"; 26 | private static final String KEY = "key"; 27 | private static final String VALUE = "value"; 28 | 29 | private final JPanel pluginUI; 30 | private JLabel imgLabel; 31 | 32 | public PlantUmlDebuggingVisualizer(final JPanel jPanel) { 33 | this.pluginUI = jPanel; 34 | } 35 | 36 | @Override 37 | public void doVisualizationFurther(ObjectDiagram diagram) { 38 | final var plantUMLString = PlantUmlDebuggingVisualizer.toPlantUMLString(diagram); 39 | SharedState.setLastPlantUMLDiagram(plantUMLString); 40 | try { 41 | final byte[] pngData = PlantUmlDebuggingVisualizer.toImage(plantUMLString, FileFormat.PNG); 42 | this.addImageToUI(pngData); 43 | } catch (final IOException e) { 44 | LOGGER.error(e); 45 | } 46 | } 47 | 48 | @Override 49 | public void debuggingActivated() { 50 | final var printDiagram = new JButton("Copy diagram"); 51 | printDiagram.addActionListener(e -> new CopyPlantUMLDialog().show()); 52 | this.pluginUI.add(printDiagram, BorderLayout.SOUTH); 53 | } 54 | 55 | @Override 56 | public void debuggingDeactivated() { 57 | // NOOP 58 | } 59 | 60 | private void addImageToUI(final byte[] pngData) throws IOException { 61 | final var input = new ByteArrayInputStream(pngData); 62 | final var imageIcon = new ImageIcon(ImageIO.read(input)); 63 | 64 | if (this.imgLabel == null) { 65 | this.createImageAndAddToUI(imageIcon); 66 | } else { 67 | this.imgLabel.setIcon(imageIcon); 68 | } 69 | this.pluginUI.revalidate(); 70 | } 71 | 72 | private void createImageAndAddToUI(final ImageIcon imageIcon) { 73 | this.imgLabel = new JLabel(imageIcon); 74 | final var scrollPane = new JBScrollPane(this.imgLabel); 75 | this.pluginUI.add(scrollPane); 76 | } 77 | 78 | static String toPlantUMLString(final ObjectDiagram objectDiagram) { 79 | final var stringBuilder = new StringBuilder(); 80 | stringBuilder.append("@startuml\n"); 81 | // Use this so we are not dependent on a Graphviz/Dot installation on the host machine. 82 | stringBuilder.append("!pragma layout smetana\n"); 83 | 84 | // Sort objects so the visualisation does not change when there are no objects changes. 85 | final List sortedObjects = objectDiagram.getObjects().stream().sorted().toList(); 86 | 87 | final Set mapLinks = new HashSet<>(); 88 | // Add objects with attributes and collect links. They have to be added after objects. 89 | PlantUmlDebuggingVisualizer.addObjectsToDiagramAndCollectLinks( 90 | stringBuilder, sortedObjects, mapLinks); 91 | 92 | // Add links. 93 | PlantUmlDebuggingVisualizer.addLinksToDiagram( 94 | stringBuilder, objectDiagram.getLinks(), mapLinks); 95 | 96 | // Add primitive root values if there are any. 97 | if (!objectDiagram.getPrimitiveRootValues().isEmpty()) { 98 | PlantUmlDebuggingVisualizer.addPrimitiveRootValuesToDiagram(objectDiagram, stringBuilder); 99 | } 100 | 101 | stringBuilder.append("@enduml\n"); 102 | return stringBuilder.toString(); 103 | } 104 | 105 | private static void addPrimitiveRootValuesToDiagram( 106 | final ObjectDiagram objectDiagram, final StringBuilder stringBuilder) { 107 | stringBuilder.append( 108 | String.format("object \"%s\" as %s", "PrimitiveVariables", "primitiveVariables")); 109 | stringBuilder.append(" {\n"); 110 | objectDiagram.getPrimitiveRootValues().stream() 111 | .sorted() 112 | .forEach( 113 | primitiveRootValue -> 114 | stringBuilder.append( 115 | String.format( 116 | "%s=%s%n", primitiveRootValue.variableName(), primitiveRootValue.value()))); 117 | stringBuilder.append("}\n"); 118 | } 119 | 120 | private static void addLinksToDiagram( 121 | final StringBuilder stringBuilder, final Set links, final Set mapLinks) { 122 | links.stream() 123 | .sorted() 124 | // Ignore links already visualized in maps. 125 | .filter(odLink -> !mapLinks.contains(odLink)) 126 | .forEach( 127 | link -> 128 | stringBuilder.append( 129 | String.format( 130 | "%s --> %s : %s%n", 131 | link.getFrom().hashCode(), link.getTo().hashCode(), link.getType()))); 132 | } 133 | 134 | private static void addObjectsToDiagramAndCollectLinks( 135 | final StringBuilder stringBuilder, 136 | final List sortedObjects, 137 | final Set mapLinks) { 138 | final HashSet ignoredObjects = new HashSet<>(); 139 | for (final ODObject object : sortedObjects) { 140 | if (ignoredObjects.contains(object)) { 141 | continue; 142 | } 143 | // Primitive maps are visualised differently 144 | if (PlantUmlDebuggingVisualizer.isPrimitiveJavaMap(object)) { 145 | PlantUmlDebuggingVisualizer.doPrimitiveMapVisualisation( 146 | stringBuilder, ignoredObjects, object); 147 | // Links are not allowed to be shown later on. 148 | mapLinks.addAll(object.getLinks()); 149 | continue; 150 | } 151 | 152 | // Add the object 153 | stringBuilder.append( 154 | String.format( 155 | "object \"%s:%s\" as %s", 156 | object.getVariableName(), 157 | PlantUmlDebuggingVisualizer.shortenTypeName(object.getType()), 158 | object.hashCode())); 159 | 160 | // Add object attributes 161 | if (!object.getAttributeValues().isEmpty()) { 162 | stringBuilder.append(" {\n"); 163 | object.getAttributeValues().stream() 164 | // Sort so that objects with the same type have the same order of attributes 165 | .sorted(Comparator.comparing(ODAttributeValue::getName)) 166 | .forEach( 167 | odAttributeValue -> 168 | stringBuilder.append( 169 | String.format( 170 | "%s=%s%n", odAttributeValue.getName(), odAttributeValue.getValue()))); 171 | stringBuilder.append("}\n"); 172 | } else { 173 | stringBuilder.append("\n"); 174 | } 175 | } 176 | } 177 | 178 | private static void doPrimitiveMapVisualisation( 179 | final StringBuilder stringBuilder, 180 | final HashSet ignoredObjects, 181 | final ODObject object) { 182 | stringBuilder.append( 183 | String.format( 184 | "map \"%s:%s\" as %s", 185 | object.getVariableName(), 186 | PlantUmlDebuggingVisualizer.shortenTypeName(object.getType()), 187 | object.hashCode())); 188 | stringBuilder.append(" {\n"); 189 | 190 | object.getLinks().stream() 191 | .sorted() 192 | .forEach( 193 | odLink -> { 194 | final ODObject mapNode = odLink.getTo(); 195 | ignoredObjects.add(mapNode); // Dont visualize the node as an object anymore! 196 | 197 | final Optional key = mapNode.getAttributeByName(KEY); 198 | final Optional value = mapNode.getAttributeByName(VALUE); 199 | if (key.isPresent() || value.isPresent()) { 200 | stringBuilder.append( 201 | String.format( 202 | "%s => %s%n", 203 | key.isPresent() ? key.get().getValue() : NULL, 204 | value.isPresent() ? value.get().getValue() : NULL)); 205 | } 206 | }); 207 | 208 | stringBuilder.append("}\n"); 209 | } 210 | 211 | private static boolean isPrimitiveJavaMap(final ODObject object) { 212 | return PlantUmlDebuggingVisualizer.isMap(object) 213 | && PlantUmlDebuggingVisualizer.isPrimitive(object); 214 | } 215 | 216 | private static boolean isPrimitive(final ODObject object) { 217 | // Nodes attached to the link must not have any more links. 218 | // Key and value are then attributes i.e. primitive. 219 | return !object.getLinks().isEmpty() 220 | && object.getLinks().stream().anyMatch(odLink -> odLink.getTo().getLinks().isEmpty()); 221 | } 222 | 223 | private static boolean isMap(final ODObject object) { 224 | return object.getType().startsWith("java.util") && object.getType().endsWith("Map"); 225 | } 226 | 227 | private static Object shortenTypeName(final String type) { 228 | return type.substring(type.lastIndexOf(".") + 1); 229 | } 230 | 231 | public static byte[] toImage(final String plantUMLDescription, final FileFormat format) 232 | throws IOException { 233 | final var reader = new SourceStringReader(plantUMLDescription); 234 | try (final var outputStream = new ByteArrayOutputStream()) { 235 | reader.outputImage(outputStream, new FileFormatOption(format)); 236 | return outputStream.toByteArray(); 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/visualization/WebSocketDebuggingVisualizer.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.visualization; 2 | 3 | import com.intellij.ide.BrowserUtil; 4 | import com.intellij.openapi.diagnostic.Logger; 5 | import com.intellij.ui.jcef.JBCefBrowser; 6 | import java.awt.BorderLayout; 7 | import javax.swing.*; 8 | import no.hvl.tk.visual.debugger.SharedState; 9 | import no.hvl.tk.visual.debugger.debugging.visualization.cef.SimpleDownloadHandler; 10 | import no.hvl.tk.visual.debugger.domain.ObjectDiagram; 11 | import no.hvl.tk.visual.debugger.server.ServerConstants; 12 | import no.hvl.tk.visual.debugger.server.UIServerStarter; 13 | import no.hvl.tk.visual.debugger.server.VisualDebuggingAPIServerStarter; 14 | import no.hvl.tk.visual.debugger.server.endpoint.message.DebuggingMessageType; 15 | import no.hvl.tk.visual.debugger.server.endpoint.message.DebuggingWSMessage; 16 | import no.hvl.tk.visual.debugger.util.ClassloaderUtil; 17 | import no.hvl.tk.visual.debugger.util.DiagramToJSONConverter; 18 | import org.glassfish.grizzly.http.server.HttpServer; 19 | import org.glassfish.tyrus.server.Server; 20 | 21 | /** Sends visualization information through websocket. */ 22 | public class WebSocketDebuggingVisualizer extends DebuggingInfoVisualizerBase { 23 | 24 | private static final Logger LOGGER = Logger.getInstance(WebSocketDebuggingVisualizer.class); 25 | 26 | private JBCefBrowser browser; 27 | private final JPanel debugUI; 28 | 29 | public WebSocketDebuggingVisualizer(final JPanel userInterface) { 30 | this.debugUI = userInterface; 31 | } 32 | 33 | @Override 34 | public void doVisualizationFurther(ObjectDiagram diagram) { 35 | if (SharedState.getDebugAPIServer() == null) { 36 | return; 37 | } 38 | final String diagramJSON = DiagramToJSONConverter.toJSON(diagram); 39 | SharedState.setLastDiagramJSON(diagramJSON); 40 | 41 | final String message = 42 | new DebuggingWSMessage( 43 | DebuggingMessageType.NEXT_DEBUG_STEP, 44 | diagramJSON, 45 | SharedState.getDebugFileName(), 46 | SharedState.getDebugLine()) 47 | .serialize(); 48 | SharedState.getWebsocketClients() 49 | .forEach( 50 | clientSession -> 51 | // If one client fails no more messages are sent. We should change this. 52 | VisualDebuggingAPIServerStarter.sendMessageToClient(clientSession, message)); 53 | } 54 | 55 | @Override 56 | public void debuggingActivated() { 57 | WebSocketDebuggingVisualizer.startDebugAPIServerIfNeeded(); 58 | WebSocketDebuggingVisualizer.startUIServerIfNeeded(); 59 | 60 | initUI(); 61 | } 62 | 63 | private void initUI() { 64 | final var launchEmbeddedBrowserButton = new JButton("Launch embedded browser (experimental)"); 65 | final var launchBrowserButton = 66 | new JButton(String.format("Launch browser (%s)", ServerConstants.UI_SERVER_URL)); 67 | launchEmbeddedBrowserButton.addActionListener( 68 | e -> { 69 | this.debugUI.remove(launchEmbeddedBrowserButton); 70 | this.debugUI.remove(launchBrowserButton); 71 | launchEmbeddedBrowser(); 72 | }); 73 | launchBrowserButton.addActionListener(e -> BrowserUtil.browse(ServerConstants.UI_SERVER_URL)); 74 | if (SharedState.isEmbeddedBrowserActive()) { 75 | launchEmbeddedBrowser(); 76 | } else { 77 | this.debugUI.add(launchEmbeddedBrowserButton, BorderLayout.NORTH); 78 | this.debugUI.add(launchBrowserButton, BorderLayout.SOUTH); 79 | } 80 | } 81 | 82 | private void launchEmbeddedBrowser() { 83 | if (browser == null) { 84 | browser = new JBCefBrowser(); 85 | browser.getJBCefClient().getCefClient().addDownloadHandler(new SimpleDownloadHandler()); 86 | browser.setPageBackgroundColor("white"); 87 | } 88 | browser.loadURL(ServerConstants.UI_SERVER_URL_EMBEDDED); 89 | debugUI.add(browser.getComponent(), 0); 90 | SharedState.setEmbeddedBrowserActive(true); 91 | this.debugUI.revalidate(); 92 | } 93 | 94 | private static void startDebugAPIServerIfNeeded() { 95 | ClassloaderUtil.runWithContextClassloader( 96 | () -> { 97 | if (SharedState.getDebugAPIServer() == null) { 98 | final Server server = VisualDebuggingAPIServerStarter.runNewServer(); 99 | SharedState.setDebugAPIServer(server); 100 | } 101 | return null; // needed because of generic method. 102 | }); 103 | } 104 | 105 | private static void startUIServerIfNeeded() { 106 | ClassloaderUtil.runWithContextClassloader( 107 | () -> { 108 | if (SharedState.getUiServer() == null) { 109 | final HttpServer server = UIServerStarter.runNewServer(); 110 | SharedState.setUIServer(server); 111 | } 112 | return null; // needed because of generic method. 113 | }); 114 | } 115 | 116 | @Override 117 | public void debuggingDeactivated() { 118 | stopUIServerIfNeeded(); 119 | stopDebugAPIServerIfNeeded(); 120 | } 121 | 122 | private static void stopUIServerIfNeeded() { 123 | final HttpServer server = SharedState.getUiServer(); 124 | if (server != null) { 125 | server.shutdownNow(); 126 | LOGGER.info("UI server stopped."); 127 | SharedState.setUIServer(null); 128 | } 129 | } 130 | 131 | private static void stopDebugAPIServerIfNeeded() { 132 | final Server server = SharedState.getDebugAPIServer(); 133 | if (server != null) { 134 | server.stop(); 135 | LOGGER.info("Debug API server stopped."); 136 | SharedState.setDebugAPIServer(null); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/debugging/visualization/cef/SimpleDownloadHandler.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.visualization.cef; 2 | 3 | import no.hvl.tk.visual.debugger.ui.VisualDebuggerNotifications; 4 | import org.cef.browser.CefBrowser; 5 | import org.cef.callback.CefBeforeDownloadCallback; 6 | import org.cef.callback.CefDownloadItem; 7 | import org.cef.handler.CefDownloadHandlerAdapter; 8 | 9 | public class SimpleDownloadHandler extends CefDownloadHandlerAdapter { 10 | 11 | @Override 12 | public void onBeforeDownload( 13 | CefBrowser browser, 14 | CefDownloadItem downloadItem, 15 | String suggestedName, 16 | CefBeforeDownloadCallback callback) { 17 | VisualDebuggerNotifications.notifyDownloadStarted(suggestedName); 18 | callback.Continue("", true); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/domain/ODAttributeValue.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.google.common.base.Objects; 5 | import java.util.StringJoiner; 6 | 7 | /** Represents the attribute value of an object in an object diagram. */ 8 | public class ODAttributeValue { 9 | 10 | @JsonProperty private final String name; 11 | @JsonProperty private final String type; 12 | @JsonProperty private final String value; 13 | 14 | public ODAttributeValue(final String attributeName, final String type, final String value) { 15 | this.name = attributeName; 16 | this.type = type; 17 | this.value = value; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return new StringJoiner(", ", "Attribute:[", "]") 23 | .add("name='" + this.name + "'") 24 | .add("type='" + this.type + "'") 25 | .add("value='" + this.value + "'") 26 | .toString(); 27 | } 28 | 29 | public String getName() { 30 | return this.name; 31 | } 32 | 33 | public String getValue() { 34 | return this.value; 35 | } 36 | 37 | @Override 38 | public boolean equals(final Object o) { 39 | if (this == o) { 40 | return true; 41 | } 42 | if (!(o instanceof final ODAttributeValue that)) { 43 | return false; 44 | } 45 | return Objects.equal(this.name, that.name) 46 | && Objects.equal(this.type, that.type) 47 | && Objects.equal(this.value, that.value); 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return Objects.hashCode(this.name, this.type, this.value); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/domain/ODLink.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.JsonIdentityReference; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 7 | import com.google.common.base.Objects; 8 | import java.util.StringJoiner; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | /** Represents a link between two objects in an object diagram. */ 12 | @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") 13 | public class ODLink implements Comparable { 14 | 15 | /** Name of the association this link is typed in. */ 16 | @JsonProperty private final String type; 17 | 18 | @JsonIdentityReference(alwaysAsId = true) 19 | @JsonProperty 20 | private final ODObject from; 21 | 22 | @JsonIdentityReference(alwaysAsId = true) 23 | @JsonProperty 24 | private final ODObject to; 25 | 26 | public ODLink(final ODObject from, final ODObject to, final String type) { 27 | this.from = from; 28 | this.to = to; 29 | this.type = type; 30 | } 31 | 32 | public String getType() { 33 | return this.type; 34 | } 35 | 36 | public ODObject getFrom() { 37 | return this.from; 38 | } 39 | 40 | public ODObject getTo() { 41 | return this.to; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return new StringJoiner(", ", "Link[", "]") 47 | .add("type='" + this.type + "'") 48 | .add("from=" + this.from.getVariableName()) 49 | .add("to=" + this.to.getVariableName()) 50 | .toString(); 51 | } 52 | 53 | @Override 54 | public int compareTo(@NotNull final ODLink odLink) { 55 | final int fromComparison = this.from.compareTo(odLink.from); 56 | if (fromComparison != 0) { 57 | return fromComparison; 58 | } 59 | final int toComparison = this.to.compareTo(odLink.to); 60 | if (toComparison != 0) { 61 | return toComparison; 62 | } 63 | return this.type.compareTo(odLink.type); 64 | } 65 | 66 | @Override 67 | public boolean equals(final Object o) { 68 | if (this == o) { 69 | return true; 70 | } 71 | if (!(o instanceof final ODLink odLink)) { 72 | return false; 73 | } 74 | return Objects.equal(this.type, odLink.type) 75 | && Objects.equal(this.from, odLink.from) 76 | && Objects.equal(this.to, odLink.to); 77 | } 78 | 79 | @Override 80 | public int hashCode() { 81 | return Objects.hashCode(this.type, this.from, this.to); 82 | } 83 | 84 | @JsonProperty 85 | public String getId() { 86 | return "Link_" 87 | + this.from.getId() 88 | + "_to_" 89 | + this.to.getId() 90 | + "_type_" 91 | // Ids are not allowed to contain "$". 92 | + this.type.replace('$', '_'); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/domain/ODObject.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.JsonIdentityReference; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 7 | import java.util.*; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | /** Represents an object in an object diagram. */ 11 | @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") 12 | public class ODObject implements Comparable { 13 | 14 | public static final String OBJECT_ID_PREFIX = "Object_"; 15 | private final long id; 16 | 17 | @JsonProperty private final String type; 18 | @JsonProperty private final String variableName; 19 | 20 | /** All attributes of this object. */ 21 | @JsonProperty private final List attributeValues; 22 | 23 | /** All links coming from this object. */ 24 | @JsonIdentityReference(alwaysAsId = true) 25 | @JsonProperty 26 | private final Set links; 27 | 28 | public ODObject(final long id, final String type, final String variableName) { 29 | this.id = id; 30 | this.type = type; 31 | this.variableName = variableName; 32 | this.attributeValues = new ArrayList<>(); 33 | this.links = new HashSet<>(); 34 | } 35 | 36 | /** Returns a sorted list of the objects attributes. */ 37 | public List getAttributeValues() { 38 | this.attributeValues.sort(Comparator.comparing(ODAttributeValue::getName)); 39 | return attributeValues; 40 | } 41 | 42 | /** Returns a read-only set of this objects links. */ 43 | public Set getLinks() { 44 | return Collections.unmodifiableSet(this.links); 45 | } 46 | 47 | public void addLink(final ODLink linkToAdd) { 48 | this.links.add(linkToAdd); 49 | } 50 | 51 | public void addAttribute(final ODAttributeValue attributeToAdd) { 52 | this.attributeValues.add(attributeToAdd); 53 | } 54 | 55 | public String getVariableName() { 56 | return this.variableName; 57 | } 58 | 59 | public String getType() { 60 | return this.type; 61 | } 62 | 63 | public Optional getAttributeByName(final String attributeName) { 64 | return this.attributeValues.stream() 65 | .filter(odAttributeValue -> odAttributeValue.getName().equals(attributeName)) 66 | .findFirst(); 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return new StringJoiner(", ", "Object[", "]") 72 | .add("type='" + this.type + "'") 73 | .add("variableName='" + this.variableName + "'") 74 | .add("attributeValues=" + this.attributeValues) 75 | .add("links=" + this.links) 76 | .toString(); 77 | } 78 | 79 | @Override 80 | public int compareTo(@NotNull final ODObject object) { 81 | return Long.compare(this.id, object.id); 82 | } 83 | 84 | @Override 85 | public boolean equals(final Object o) { 86 | if (this == o) { 87 | return true; 88 | } 89 | if (!(o instanceof final ODObject odObject)) { 90 | return false; 91 | } 92 | return this.id == odObject.id; 93 | } 94 | 95 | @Override 96 | public int hashCode() { 97 | return Long.hashCode(this.id); 98 | } 99 | 100 | @JsonProperty 101 | public String getId() { 102 | return OBJECT_ID_PREFIX + this.id; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/domain/ODPrimitiveRootValue.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public record ODPrimitiveRootValue( 8 | @JsonProperty String variableName, @JsonProperty String type, @JsonProperty String value) 9 | implements Comparable { 10 | 11 | @Override 12 | public int compareTo(@NotNull final ODPrimitiveRootValue other) { 13 | final int varNameComparison = this.variableName().compareTo(other.variableName()); 14 | if (varNameComparison != 0) { 15 | return varNameComparison; 16 | } 17 | // Null-safe since values could be null. 18 | final int valueComparison = StringUtils.compare(this.value(), other.value()); 19 | if (valueComparison != 0) { 20 | return valueComparison; 21 | } 22 | return this.type().compareTo(other.type()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/domain/ObjectDiagram.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import java.util.Collections; 6 | import java.util.LinkedHashSet; 7 | import java.util.Set; 8 | 9 | @JsonInclude(JsonInclude.Include.NON_EMPTY) 10 | public class ObjectDiagram { 11 | @JsonProperty private final Set objects; 12 | 13 | @JsonProperty private final Set links; 14 | 15 | @JsonProperty private final Set primitiveRootValues; 16 | 17 | public ObjectDiagram() { 18 | this.objects = new LinkedHashSet<>(); 19 | this.links = new LinkedHashSet<>(); 20 | this.primitiveRootValues = new LinkedHashSet<>(); 21 | } 22 | 23 | public Set getObjects() { 24 | return Collections.unmodifiableSet(this.objects); 25 | } 26 | 27 | public Set getLinks() { 28 | return links; 29 | } 30 | 31 | public Set getPrimitiveRootValues() { 32 | return Collections.unmodifiableSet(this.primitiveRootValues); 33 | } 34 | 35 | public void addObject(final ODObject obj) { 36 | this.objects.add(obj); 37 | } 38 | 39 | public void addLink(final ODLink link) { 40 | this.links.add(link); 41 | } 42 | 43 | public void addPrimitiveRootValue(final ODPrimitiveRootValue primitiveRootValue) { 44 | this.primitiveRootValues.add(primitiveRootValue); 45 | } 46 | 47 | public boolean isEmpty() { 48 | return this.objects.isEmpty() && this.links.isEmpty() && this.primitiveRootValues.isEmpty(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/domain/ObjectDiagramBuilder.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import com.intellij.openapi.util.Pair; 4 | import com.sun.jdi.ObjectReference; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class ObjectDiagramBuilder { 9 | 10 | private final Map> objectRefMap; 11 | 12 | private final ObjectDiagram diagram; 13 | 14 | public ObjectDiagramBuilder() { 15 | this.objectRefMap = new HashMap<>(); 16 | this.diagram = new ObjectDiagram(); 17 | } 18 | 19 | public Map> getObjectRefMap() { 20 | return objectRefMap; 21 | } 22 | 23 | public ObjectDiagramBuilder addObject(final ODObject object, ObjectReference objectReference) { 24 | this.diagram.addObject(object); 25 | this.objectRefMap.put(object.getId(), Pair.create(object, objectReference)); 26 | return this; 27 | } 28 | 29 | public ObjectDiagramBuilder addAttributeToObject( 30 | final ODObject object, 31 | final String fieldName, 32 | final String fieldValue, 33 | final String fieldType) { 34 | object.addAttribute(new ODAttributeValue(fieldName, fieldType, fieldValue)); 35 | return this; 36 | } 37 | 38 | public ObjectDiagramBuilder addLinkToObject( 39 | final ODObject from, final ODObject to, final String linkType) { 40 | final ODLink linkToAdd = new ODLink(from, to, linkType); 41 | from.addLink(linkToAdd); 42 | this.diagram.addLink(linkToAdd); 43 | return this; 44 | } 45 | 46 | public ObjectDiagramBuilder addPrimitiveRootValue( 47 | final String variableName, final String type, final String value) { 48 | this.diagram.addPrimitiveRootValue(new ODPrimitiveRootValue(variableName, type, value)); 49 | return this; 50 | } 51 | 52 | public ObjectDiagram build() { 53 | return this.diagram; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/domain/PrimitiveTypes.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | public enum PrimitiveTypes { 7 | BYTE("byte", "java.lang.Byte"), 8 | SHORT("short", "java.lang.Short"), 9 | INT("int", "java.lang.Integer"), 10 | LONG("long", "java.lang.Long"), 11 | FLOAT("float", "java.lang.Float"), 12 | DOUBLE("double", "java.lang.Double"), 13 | CHAR("char", "java.lang.Character"), 14 | BOOLEAN("boolean", "java.lang.Boolean"), 15 | STRING("java.lang.String", ""); 16 | 17 | private final String firstTypeName; 18 | private final String secondTypeName; 19 | 20 | PrimitiveTypes(final String typeName, final java.lang.String secondName) { 21 | this.firstTypeName = typeName; 22 | this.secondTypeName = secondName; 23 | } 24 | 25 | public static boolean isBoxedPrimitiveType(String typeName) { 26 | Set typeNames = new HashSet<>(); 27 | for (final PrimitiveTypes primitiveType : PrimitiveTypes.values()) { 28 | if (!primitiveType.secondTypeName.isEmpty()) { 29 | typeNames.add(primitiveType.secondTypeName); 30 | } 31 | } 32 | return typeNames.contains(typeName); 33 | } 34 | 35 | public static boolean isNonBoxedPrimitiveType(String typeName) { 36 | Set typeNames = new HashSet<>(); 37 | for (final PrimitiveTypes primitiveType : PrimitiveTypes.values()) { 38 | typeNames.add(primitiveType.firstTypeName); 39 | } 40 | return typeNames.contains(typeName); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/server/ServerConstants.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.server; 2 | 3 | public class ServerConstants { 4 | public static final String HOST_NAME = "localhost"; 5 | public static final int VISUAL_DEBUGGING_API_SERVER_PORT = 8071; 6 | 7 | public static final String STATIC_RESOURCE_PATH = "/ui/"; 8 | public static final int UI_SERVER_PORT = 8070; 9 | 10 | public static final String UI_SERVER_URL = 11 | String.format("http://%s:%s", ServerConstants.HOST_NAME, ServerConstants.UI_SERVER_PORT); 12 | public static final String UI_SERVER_URL_EMBEDDED = 13 | String.format( 14 | "http://%s:%s?embedded=true", ServerConstants.HOST_NAME, ServerConstants.UI_SERVER_PORT); 15 | 16 | private ServerConstants() { 17 | // Only constants in this class 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/server/UIServerStarter.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.server; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import org.glassfish.grizzly.http.server.CLStaticHttpHandler; 5 | import org.glassfish.grizzly.http.server.HttpServer; 6 | import org.glassfish.grizzly.http.server.NetworkListener; 7 | 8 | public class UIServerStarter { 9 | private static final Logger LOGGER = Logger.getInstance(UIServerStarter.class); 10 | 11 | private UIServerStarter() { 12 | // Only helper methods. 13 | } 14 | 15 | public static HttpServer runNewServer() { 16 | final HttpServer server = new HttpServer(); 17 | final NetworkListener networkListener = 18 | new NetworkListener("UI", ServerConstants.HOST_NAME, ServerConstants.UI_SERVER_PORT); 19 | server.addListener(networkListener); 20 | 21 | server 22 | .getServerConfiguration() 23 | .addHttpHandler( 24 | new CLStaticHttpHandler( 25 | UIServerStarter.class.getClassLoader(), ServerConstants.STATIC_RESOURCE_PATH), 26 | "/"); 27 | try { 28 | server.start(); 29 | LOGGER.info("UI server started successfully."); 30 | return server; 31 | } catch (final Exception e) { 32 | LOGGER.error(e); 33 | return null; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/server/VisualDebuggingAPIServerStarter.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.server; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import jakarta.websocket.Session; 5 | import java.io.IOException; 6 | import java.util.HashMap; 7 | import no.hvl.tk.visual.debugger.server.endpoint.UIConfig; 8 | import no.hvl.tk.visual.debugger.server.endpoint.VisualDebuggingAPIEndpoint; 9 | import no.hvl.tk.visual.debugger.server.endpoint.message.DebuggingMessageType; 10 | import no.hvl.tk.visual.debugger.server.endpoint.message.DebuggingWSMessage; 11 | import no.hvl.tk.visual.debugger.settings.PluginSettingsState; 12 | import org.glassfish.tyrus.server.Server; 13 | 14 | /** 15 | * This class can start a websocket server which runs an API which provides the client with live 16 | * debug data. See {@link VisualDebuggingAPIEndpoint} for the Endpoint. 17 | */ 18 | public class VisualDebuggingAPIServerStarter { 19 | private static final Logger LOGGER = Logger.getInstance(VisualDebuggingAPIServerStarter.class); 20 | 21 | private VisualDebuggingAPIServerStarter() { 22 | // Only helper methods. 23 | } 24 | 25 | public static Server runNewServer() { 26 | final Server server = 27 | new Server( 28 | ServerConstants.HOST_NAME, 29 | ServerConstants.VISUAL_DEBUGGING_API_SERVER_PORT, 30 | "", 31 | new HashMap<>(), 32 | VisualDebuggingAPIEndpoint.class); 33 | try { 34 | server.start(); 35 | LOGGER.info("Debug API server started successfully."); 36 | return server; 37 | } catch (final Exception e) { 38 | LOGGER.error(e); 39 | return null; 40 | } 41 | } 42 | 43 | /** 44 | * Sends the given message to the given client, if the client is not null. 45 | * 46 | * @param client client. 47 | * @param message message for the client. 48 | */ 49 | public static void sendMessageToClient(final Session client, final String message) { 50 | if (client != null) { 51 | try { 52 | client.getBasicRemote().sendText(message); 53 | } catch (final IOException e) { 54 | LOGGER.error(e); 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Sends the configuration to the client. 61 | * 62 | * @param client client 63 | */ 64 | public static void sendUIConfig(Session client) { 65 | UIConfig uiConfig = PluginSettingsState.getInstance().getUIConfig(); 66 | final DebuggingWSMessage configMessage = 67 | new DebuggingWSMessage(DebuggingMessageType.CONFIG, uiConfig.serialize()); 68 | 69 | sendMessageToClient(client, configMessage.serialize()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/server/endpoint/UIConfig.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.server.endpoint; 2 | 3 | import static no.hvl.tk.visual.debugger.util.DiagramToJSONConverter.getMapper; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.intellij.openapi.diagnostic.Logger; 7 | 8 | public record UIConfig(Integer savedDebugSteps, boolean coloredDiff) { 9 | private static final Logger LOGGER = Logger.getInstance(UIConfig.class); 10 | 11 | public String serialize() { 12 | try { 13 | return getMapper().writeValueAsString(this); 14 | } catch (final JsonProcessingException e) { 15 | LOGGER.error(e); 16 | return "JsonProcessingException"; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/server/endpoint/VisualDebuggingAPIEndpoint.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.server.endpoint; 2 | 3 | import static no.hvl.tk.visual.debugger.server.VisualDebuggingAPIServerStarter.sendUIConfig; 4 | 5 | import com.intellij.openapi.diagnostic.Logger; 6 | import jakarta.websocket.OnClose; 7 | import jakarta.websocket.OnMessage; 8 | import jakarta.websocket.OnOpen; 9 | import jakarta.websocket.Session; 10 | import jakarta.websocket.server.ServerEndpoint; 11 | import no.hvl.tk.visual.debugger.SharedState; 12 | import no.hvl.tk.visual.debugger.debugging.visualization.DebuggingInfoVisualizer; 13 | import no.hvl.tk.visual.debugger.domain.ObjectDiagram; 14 | import no.hvl.tk.visual.debugger.server.VisualDebuggingAPIServerStarter; 15 | import no.hvl.tk.visual.debugger.server.endpoint.message.DebuggingMessageType; 16 | import no.hvl.tk.visual.debugger.server.endpoint.message.DebuggingWSMessage; 17 | import no.hvl.tk.visual.debugger.util.DiagramToJSONConverter; 18 | 19 | @ServerEndpoint("/debug") 20 | public class VisualDebuggingAPIEndpoint { 21 | private static final Logger LOGGER = Logger.getInstance(VisualDebuggingAPIEndpoint.class); 22 | 23 | public VisualDebuggingAPIEndpoint() { 24 | // Needs public constructor. 25 | } 26 | 27 | @OnOpen 28 | public static void onOpen(final Session session) { 29 | LOGGER.info(String.format("Websocket session with id \"%s\" opened.", session.getId())); 30 | SharedState.addWebsocketClient(session); 31 | 32 | sendUIConfig(session); 33 | 34 | // Send the last diagram JSON to the newly connected client. 35 | final DebuggingWSMessage debugMessage = 36 | new DebuggingWSMessage( 37 | DebuggingMessageType.NEXT_DEBUG_STEP, 38 | SharedState.getLastDiagramJSON(), 39 | SharedState.getDebugFileName(), 40 | SharedState.getDebugLine()); 41 | VisualDebuggingAPIServerStarter.sendMessageToClient(session, debugMessage.serialize()); 42 | } 43 | 44 | @OnClose 45 | public static void onClose(final Session session) { 46 | LOGGER.info(String.format("Websocket session with id \"%s\" closed.", session.getId())); 47 | SharedState.removeWebsocketClient(session); 48 | } 49 | 50 | @OnMessage 51 | public static String handleTextMessage(final String objectId) { 52 | LOGGER.debug(String.format("New websocket message with content \"%s\" received.", objectId)); 53 | 54 | final DebuggingInfoVisualizer debuggingInfoVisualizer = 55 | SharedState.getDebugListener().getOrCreateDebuggingInfoVisualizer(); 56 | try { 57 | final ObjectDiagram diagram = debuggingInfoVisualizer.getObjectWithChildren(objectId); 58 | return new DebuggingWSMessage( 59 | DebuggingMessageType.LOAD_CHILDREN, DiagramToJSONConverter.toJSON(diagram)) 60 | .serialize(); 61 | } catch (NumberFormatException e) { 62 | return new DebuggingWSMessage( 63 | DebuggingMessageType.ERROR, 64 | String.format("Object id \"%s\" is not a number!", objectId)) 65 | .serialize(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/server/endpoint/message/DebuggingMessageType.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.server.endpoint.message; 2 | 3 | public enum DebuggingMessageType { 4 | CONFIG("config"), 5 | NEXT_DEBUG_STEP("nextDebugStep"), 6 | LOAD_CHILDREN("loadChildren"), 7 | ERROR("error"); 8 | 9 | private final String type; 10 | 11 | DebuggingMessageType(final String type) { 12 | this.type = type; 13 | } 14 | 15 | public String getTypeString() { 16 | return this.type; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/server/endpoint/message/DebuggingWSMessage.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.server.endpoint.message; 2 | 3 | import static no.hvl.tk.visual.debugger.util.DiagramToJSONConverter.getMapper; 4 | 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.intellij.openapi.diagnostic.Logger; 7 | 8 | public class DebuggingWSMessage { 9 | 10 | private static final Logger LOGGER = Logger.getInstance(DebuggingWSMessage.class); 11 | 12 | private final DebuggingMessageType type; 13 | private final String content; 14 | 15 | private final String fileName; 16 | private final Integer line; 17 | 18 | public DebuggingWSMessage(final DebuggingMessageType type, final String content) { 19 | this(type, content, null, null); 20 | } 21 | 22 | public DebuggingWSMessage( 23 | DebuggingMessageType type, String content, String fileName, Integer line) { 24 | this.type = type; 25 | this.content = content; 26 | this.fileName = fileName; 27 | this.line = line; 28 | } 29 | 30 | // Getter needed for serialize 31 | public String getType() { 32 | return this.type.getTypeString(); 33 | } 34 | 35 | public String getContent() { 36 | return this.content; 37 | } 38 | 39 | public String getFileName() { 40 | return fileName; 41 | } 42 | 43 | public Integer getLine() { 44 | return line; 45 | } 46 | 47 | public String serialize() { 48 | try { 49 | return getMapper().writeValueAsString(this); 50 | } catch (final JsonProcessingException e) { 51 | LOGGER.error(e); 52 | return "JsonProcessingException"; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/settings/DebuggingVisualizerOption.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.settings; 2 | 3 | public enum DebuggingVisualizerOption { 4 | /** Web ui visualizer using javascript and the browser. */ 5 | WEB_UI, 6 | /** Embedded visualizer using plant uml. */ 7 | EMBEDDED; 8 | 9 | @Override 10 | public String toString() { 11 | return switch (this) { 12 | case WEB_UI -> "Browser visualizer"; 13 | case EMBEDDED -> "Embedded visualizer (no interaction)"; 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/settings/PluginSettingsState.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.settings; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.components.PersistentStateComponent; 5 | import com.intellij.openapi.components.State; 6 | import com.intellij.openapi.components.Storage; 7 | import com.intellij.util.xmlb.XmlSerializerUtil; 8 | import no.hvl.tk.visual.debugger.server.endpoint.UIConfig; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | /** Persistently saved settings of the plugin. */ 13 | @State( 14 | name = "no.hvl.tk.visual.debugger.settings.AppSettingsState", 15 | storages = {@Storage("visualDebuggerPluginSettings.xml")}) 16 | public class PluginSettingsState implements PersistentStateComponent { 17 | 18 | private DebuggingVisualizerOption visualizerOption = DebuggingVisualizerOption.WEB_UI; 19 | private Integer visualisationDepth = 0; 20 | private Integer savedDebugSteps = 3; 21 | 22 | private boolean coloredDiff = true; 23 | 24 | private boolean showNullValues = false; 25 | 26 | public static PluginSettingsState getInstance() { 27 | if (ApplicationManager.getApplication() == null) { 28 | return new PluginSettingsState(); 29 | } 30 | return ApplicationManager.getApplication().getService(PluginSettingsState.class); 31 | } 32 | 33 | @Nullable @Override 34 | public PluginSettingsState getState() { 35 | return this; 36 | } 37 | 38 | @Override 39 | public void loadState(@NotNull final PluginSettingsState state) { 40 | XmlSerializerUtil.copyBean(state, this); 41 | } 42 | 43 | public DebuggingVisualizerOption getVisualizerOption() { 44 | return this.visualizerOption; 45 | } 46 | 47 | public void setVisualizerOption(final DebuggingVisualizerOption visualizerOption) { 48 | this.visualizerOption = visualizerOption; 49 | } 50 | 51 | public Integer getVisualisationDepth() { 52 | return this.visualisationDepth; 53 | } 54 | 55 | public void setVisualisationDepth(final Integer visualisationDepth) { 56 | this.visualisationDepth = visualisationDepth; 57 | } 58 | 59 | public Integer getSavedDebugSteps() { 60 | return savedDebugSteps; 61 | } 62 | 63 | public void setSavedDebugSteps(Integer savedDebugSteps) { 64 | this.savedDebugSteps = savedDebugSteps; 65 | } 66 | 67 | public UIConfig getUIConfig() { 68 | return new UIConfig(this.savedDebugSteps, this.coloredDiff); 69 | } 70 | 71 | public boolean isColoredDiff() { 72 | return coloredDiff; 73 | } 74 | 75 | public void setColoredDiff(boolean coloredDiff) { 76 | this.coloredDiff = coloredDiff; 77 | } 78 | 79 | public boolean isShowNullValues() { 80 | return showNullValues; 81 | } 82 | 83 | public void setShowNullValues(boolean showNullValues) { 84 | this.showNullValues = showNullValues; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/settings/VisualDebuggerSettingsComponent.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.settings; 2 | 3 | import com.intellij.openapi.Disposable; 4 | import com.intellij.openapi.ui.ComboBox; 5 | import com.intellij.openapi.ui.ComponentValidator; 6 | import com.intellij.openapi.ui.ValidationInfo; 7 | import com.intellij.openapi.util.text.StringUtil; 8 | import com.intellij.ui.DocumentAdapter; 9 | import com.intellij.ui.components.JBCheckBox; 10 | import com.intellij.ui.components.JBLabel; 11 | import com.intellij.ui.components.JBTextField; 12 | import com.intellij.util.ui.FormBuilder; 13 | import javax.swing.*; 14 | import javax.swing.event.DocumentEvent; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | public class VisualDebuggerSettingsComponent { 19 | static final String NUMBER_GREATER_EQUALS_0 = "Must be a number greater or equal to 0."; 20 | 21 | private final JPanel myMainPanel; 22 | private final JBTextField visualizationDepthField = new JBTextField(); 23 | private final JBTextField savedDebugStepsField = new JBTextField(); 24 | private final JBCheckBox coloredDiffCheckBox = new JBCheckBox(); 25 | private final JBCheckBox showNullValuesCheckBox = new JBCheckBox(); 26 | private final ComboBox visualizerOptionsCombobox = 27 | new ComboBox<>(DebuggingVisualizerOption.values()); 28 | 29 | public VisualDebuggerSettingsComponent(final Disposable disposable) { 30 | this.myMainPanel = 31 | FormBuilder.createFormBuilder() 32 | .addLabeledComponent( 33 | new JBLabel("Choose visualizer: "), this.visualizerOptionsCombobox, 1, false) 34 | .addLabeledComponent( 35 | new JBLabel("Initial visualization depth: "), 36 | this.visualizationDepthField, 37 | 2, 38 | false) 39 | .addLabeledComponent( 40 | new JBLabel("Number of debug history steps: "), this.savedDebugStepsField, 3, false) 41 | .addLabeledComponent( 42 | new JBLabel("Color debug changes: "), this.coloredDiffCheckBox, 4, false) 43 | .addSeparator(5) 44 | .addLabeledComponent( 45 | new JBLabel("Show null values: "), this.showNullValuesCheckBox, 6, false) 46 | .addComponentFillVertically(new JPanel(), 0) 47 | .getPanel(); 48 | 49 | this.addInputFieldValidators(disposable); 50 | } 51 | 52 | private void addInputFieldValidators(final Disposable disposable) { 53 | new ComponentValidator(disposable) 54 | .withValidator( 55 | () -> validateNumberField(VisualDebuggerSettingsComponent.this.visualizationDepthField)) 56 | .installOn(this.visualizationDepthField); 57 | this.visualizationDepthField 58 | .getDocument() 59 | .addDocumentListener( 60 | new DocumentAdapter() { 61 | @Override 62 | protected void textChanged(@NotNull final DocumentEvent e) { 63 | ComponentValidator.getInstance( 64 | VisualDebuggerSettingsComponent.this.visualizationDepthField) 65 | .ifPresent(ComponentValidator::revalidate); 66 | } 67 | }); 68 | 69 | new ComponentValidator(disposable) 70 | .withValidator( 71 | () -> validateNumberField(VisualDebuggerSettingsComponent.this.savedDebugStepsField)) 72 | .installOn(this.savedDebugStepsField); 73 | this.savedDebugStepsField 74 | .getDocument() 75 | .addDocumentListener( 76 | new DocumentAdapter() { 77 | @Override 78 | protected void textChanged(@NotNull final DocumentEvent e) { 79 | ComponentValidator.getInstance( 80 | VisualDebuggerSettingsComponent.this.savedDebugStepsField) 81 | .ifPresent(ComponentValidator::revalidate); 82 | } 83 | }); 84 | } 85 | 86 | @Nullable static ValidationInfo validateNumberField(JBTextField depthField) { 87 | final String enteredDepth = depthField.getText(); 88 | if (StringUtil.isEmpty(enteredDepth) || !StringUtil.isNotNegativeNumber(enteredDepth)) { 89 | return new ValidationInfo( 90 | VisualDebuggerSettingsComponent.NUMBER_GREATER_EQUALS_0, depthField); 91 | } 92 | int depth = Integer.parseInt(enteredDepth); 93 | if (depth < 0) { 94 | return new ValidationInfo( 95 | VisualDebuggerSettingsComponent.NUMBER_GREATER_EQUALS_0, depthField); 96 | } 97 | // Means everything is ok. 98 | return null; 99 | } 100 | 101 | public JPanel getPanel() { 102 | return this.myMainPanel; 103 | } 104 | 105 | public JComponent getPreferredFocusedComponent() { 106 | return this.visualizationDepthField; 107 | } 108 | 109 | @NotNull public String getVisualizationDepthText() { 110 | return this.visualizationDepthField.getText(); 111 | } 112 | 113 | public void setVisualizationDepthText(@NotNull final String visualizationDepth) { 114 | this.visualizationDepthField.setText(visualizationDepth); 115 | } 116 | 117 | public DebuggingVisualizerOption getDebuggingVisualizerOptionChoice() { 118 | return this.visualizerOptionsCombobox.getItem(); 119 | } 120 | 121 | public void chooseDebuggingVisualizerOption(final DebuggingVisualizerOption option) { 122 | this.visualizerOptionsCombobox.setItem(option); 123 | } 124 | 125 | public void setSavedDebugStepsText(@NotNull final String loadingDepth) { 126 | this.savedDebugStepsField.setText(loadingDepth); 127 | } 128 | 129 | @NotNull public String getSavedDebugStepsText() { 130 | return savedDebugStepsField.getText(); 131 | } 132 | 133 | public void setColoredDiffValue(final boolean coloredDiffValue) { 134 | this.coloredDiffCheckBox.setSelected(coloredDiffValue); 135 | } 136 | 137 | public boolean getColoredDiffValue() { 138 | return coloredDiffCheckBox.isSelected(); 139 | } 140 | 141 | public void setShowNullValues(final boolean coloredDiffValue) { 142 | this.showNullValuesCheckBox.setSelected(coloredDiffValue); 143 | } 144 | 145 | public boolean getShowNullValues() { 146 | return showNullValuesCheckBox.isSelected(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/settings/VisualDebuggerSettingsConfigurable.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.settings; 2 | 3 | import com.intellij.openapi.Disposable; 4 | import com.intellij.openapi.options.SearchableConfigurable; 5 | import com.intellij.openapi.util.Disposer; 6 | import jakarta.websocket.Session; 7 | import javax.swing.*; 8 | import no.hvl.tk.visual.debugger.SharedState; 9 | import no.hvl.tk.visual.debugger.server.VisualDebuggingAPIServerStarter; 10 | import org.jetbrains.annotations.Nls; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | public class VisualDebuggerSettingsConfigurable implements SearchableConfigurable { 15 | 16 | private VisualDebuggerSettingsComponent settingsComponent; 17 | private @Nullable Disposable settingsDisposable = null; 18 | 19 | @Override 20 | public @NotNull String getId() { 21 | return "no.hvl.tk.visualDebugger.settings"; 22 | } 23 | 24 | @Nls(capitalization = Nls.Capitalization.Title) 25 | @Override 26 | public String getDisplayName() { 27 | return "Visual Debugger Settings"; 28 | } 29 | 30 | @Override 31 | public JComponent getPreferredFocusedComponent() { 32 | return this.settingsComponent.getPreferredFocusedComponent(); 33 | } 34 | 35 | @Nullable @Override 36 | public JComponent createComponent() { 37 | if (settingsDisposable != null) { 38 | Disposer.dispose(settingsDisposable); 39 | } 40 | settingsDisposable = Disposer.newDisposable(); 41 | this.settingsComponent = new VisualDebuggerSettingsComponent(settingsDisposable); 42 | return this.settingsComponent.getPanel(); 43 | } 44 | 45 | @Override 46 | public boolean isModified() { 47 | final PluginSettingsState settings = PluginSettingsState.getInstance(); 48 | return this.visualizerOptionChanged(settings) 49 | || isModified( 50 | settingsComponent.getVisualizationDepthText(), settings.getVisualisationDepth()) 51 | || isModified(settingsComponent.getSavedDebugStepsText(), settings.getSavedDebugSteps()) 52 | || settingsComponent.getColoredDiffValue() != settings.isColoredDiff() 53 | || settingsComponent.getShowNullValues() != settings.isShowNullValues(); 54 | } 55 | 56 | private boolean visualizerOptionChanged(final PluginSettingsState settings) { 57 | return settings.getVisualizerOption() 58 | != this.settingsComponent.getDebuggingVisualizerOptionChoice(); 59 | } 60 | 61 | private boolean isModified(String newDepthText, Integer currentDepth) { 62 | return !newDepthText.equals(currentDepth.toString()); 63 | } 64 | 65 | @Override 66 | public void apply() { 67 | final PluginSettingsState settings = PluginSettingsState.getInstance(); 68 | settings.setVisualizerOption(this.settingsComponent.getDebuggingVisualizerOptionChoice()); 69 | 70 | final int newDepth = Integer.parseInt(this.settingsComponent.getVisualizationDepthText()); 71 | VisualDebuggerSettingsConfigurable.changedDepthAndRestartDebuggerIfNeeded(settings, newDepth); 72 | 73 | final int newDebugSteps = Integer.parseInt(this.settingsComponent.getSavedDebugStepsText()); 74 | settings.setSavedDebugSteps(newDebugSteps); 75 | 76 | settings.setColoredDiff(settingsComponent.getColoredDiffValue()); 77 | 78 | settings.setShowNullValues(settingsComponent.getShowNullValues()); 79 | 80 | sendUpdatedConfig(); 81 | } 82 | 83 | private void sendUpdatedConfig() { 84 | for (Session client : SharedState.getWebsocketClients()) { 85 | VisualDebuggingAPIServerStarter.sendUIConfig(client); 86 | } 87 | } 88 | 89 | private static void changedDepthAndRestartDebuggerIfNeeded( 90 | final PluginSettingsState settings, final int newDepth) { 91 | if (newDepth != settings.getVisualisationDepth()) { 92 | settings.setVisualisationDepth(newDepth); 93 | if (SharedState.getDebugListener() != null) { 94 | SharedState.getDebugListener().reprintDiagram(); 95 | } 96 | } 97 | } 98 | 99 | @Override 100 | public void reset() { 101 | final PluginSettingsState settings = PluginSettingsState.getInstance(); 102 | this.settingsComponent.setVisualizationDepthText(settings.getVisualisationDepth().toString()); 103 | this.settingsComponent.setSavedDebugStepsText(settings.getSavedDebugSteps().toString()); 104 | this.settingsComponent.chooseDebuggingVisualizerOption(settings.getVisualizerOption()); 105 | this.settingsComponent.setColoredDiffValue(settings.isColoredDiff()); 106 | this.settingsComponent.setShowNullValues(settings.isShowNullValues()); 107 | } 108 | 109 | @Override 110 | public void disposeUIResources() { 111 | if (settingsDisposable == null) { 112 | return; 113 | } 114 | Disposer.dispose(settingsDisposable); 115 | settingsDisposable = null; 116 | 117 | this.settingsComponent = null; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/ui/CopyPlantUMLDialog.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.ui; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import java.awt.*; 6 | import java.awt.datatransfer.StringSelection; 7 | import java.io.IOException; 8 | import java.nio.charset.StandardCharsets; 9 | import javax.swing.*; 10 | import net.sourceforge.plantuml.FileFormat; 11 | import no.hvl.tk.visual.debugger.SharedState; 12 | import no.hvl.tk.visual.debugger.debugging.visualization.PlantUmlDebuggingVisualizer; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | public class CopyPlantUMLDialog extends DialogWrapper { 16 | private static final Logger LOGGER = Logger.getInstance(CopyPlantUMLDialog.class); 17 | 18 | public CopyPlantUMLDialog() { 19 | super(true); 20 | this.init(); 21 | this.setTitle("Copy PlantUML Diagram"); 22 | } 23 | 24 | @Override 25 | protected @Nullable JComponent createCenterPanel() { 26 | final var dialogPanel = new JPanel(new GridBagLayout()); 27 | 28 | // Show plantUML String. 29 | final var plantUMLStringLabel = new JLabel("PlantUML-Input:"); 30 | final var plantUMLStringButton = new JButton("Copy"); 31 | plantUMLStringButton.addActionListener( 32 | actionEvent -> CopyPlantUMLDialog.copyToClipBoard(SharedState.getLastPlantUMLDiagram())); 33 | plantUMLStringLabel.setLabelFor(plantUMLStringButton); 34 | 35 | dialogPanel.add(plantUMLStringLabel); 36 | dialogPanel.add(plantUMLStringButton); 37 | 38 | // Show plantUML svg data. 39 | final var svgLabel = new JLabel("PlantUML-SVG:"); 40 | final var c2 = new GridBagConstraints(); 41 | c2.gridy = 1; 42 | dialogPanel.add(svgLabel, c2); 43 | 44 | if (SharedState.getLastPlantUMLDiagram().isEmpty()) { 45 | final var noDiagramLabel = new JLabel("No diagram loaded."); 46 | svgLabel.setLabelFor(noDiagramLabel); 47 | dialogPanel.add(noDiagramLabel, c2); 48 | } else { 49 | final var svgCopyButton = new JButton("Copy"); 50 | svgCopyButton.addActionListener( 51 | actionEvent -> CopyPlantUMLDialog.copyToClipBoard(CopyPlantUMLDialog.getSVGData())); 52 | svgLabel.setLabelFor(svgCopyButton); 53 | dialogPanel.add(svgCopyButton, c2); 54 | } 55 | return dialogPanel; 56 | } 57 | 58 | private static void copyToClipBoard(final String content) { 59 | Toolkit.getDefaultToolkit() 60 | .getSystemClipboard() 61 | .setContents(new StringSelection(content), null); 62 | } 63 | 64 | private static String getSVGData() { 65 | try { 66 | return new String( 67 | PlantUmlDebuggingVisualizer.toImage(SharedState.getLastPlantUMLDiagram(), FileFormat.SVG), 68 | StandardCharsets.UTF_8); 69 | } catch (final IOException e) { 70 | LOGGER.error(e); 71 | } 72 | return "Error loading SVG-Data. Check IDE log."; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/ui/VisualDebuggerIcons.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.ui; 2 | 3 | import com.intellij.openapi.util.IconLoader; 4 | import javax.swing.Icon; 5 | 6 | public class VisualDebuggerIcons { 7 | private VisualDebuggerIcons() { 8 | // Only constants 9 | } 10 | 11 | public static final Icon VD_ICON = 12 | IconLoader.getIcon("icons/icon.svg", VisualDebuggerIcons.class); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/ui/VisualDebuggerNotifications.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.ui; 2 | 3 | import com.intellij.notification.NotificationGroup; 4 | import com.intellij.notification.NotificationGroupManager; 5 | import com.intellij.openapi.ui.MessageType; 6 | 7 | public class VisualDebuggerNotifications { 8 | 9 | private VisualDebuggerNotifications() {} 10 | 11 | private static final NotificationGroup NOTIFICATION_GROUP = 12 | NotificationGroupManager.getInstance().getNotificationGroup("VisualDebugger.NotifyDownload"); 13 | 14 | public static void notifyDownloadStarted(String suggestedName) { 15 | NOTIFICATION_GROUP 16 | .createNotification( 17 | String.format("Download of %s started.", suggestedName), MessageType.INFO) 18 | .setIcon(VisualDebuggerIcons.VD_ICON) 19 | .setImportant(false) 20 | .notify(null); 21 | } 22 | 23 | public static void notifyServerNotRunning() { 24 | NOTIFICATION_GROUP 25 | .createNotification( 26 | "Browser Visualizer is currently not running. You can start it in the Visual Debugger panel.", 27 | MessageType.INFO) 28 | .setIcon(VisualDebuggerIcons.VD_ICON) 29 | .setImportant(false) 30 | .notify(null); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/ui/actions/browser/OpenBrowserAction.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.ui.actions.browser; 2 | 3 | import com.intellij.ide.BrowserUtil; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import no.hvl.tk.visual.debugger.SharedState; 7 | import no.hvl.tk.visual.debugger.server.ServerConstants; 8 | import no.hvl.tk.visual.debugger.ui.VisualDebuggerNotifications; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class OpenBrowserAction extends AnAction { 12 | @Override 13 | public void actionPerformed(@NotNull final AnActionEvent e) { 14 | if (SharedState.getUiServer() == null || !SharedState.getUiServer().isStarted()) { 15 | VisualDebuggerNotifications.notifyServerNotRunning(); 16 | } 17 | BrowserUtil.browse(ServerConstants.UI_SERVER_URL); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/ui/actions/settings/OpenSettingsAction.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.ui.actions.settings; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.CommonDataKeys; 6 | import com.intellij.openapi.options.ShowSettingsUtil; 7 | import com.intellij.openapi.project.Project; 8 | import no.hvl.tk.visual.debugger.settings.VisualDebuggerSettingsConfigurable; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class OpenSettingsAction extends AnAction { 12 | 13 | @Override 14 | public void actionPerformed(@NotNull final AnActionEvent e) { 15 | final Project project = e.getData(CommonDataKeys.PROJECT); 16 | ShowSettingsUtil.getInstance() 17 | .showSettingsDialog(project, VisualDebuggerSettingsConfigurable.class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/ui/actions/stop/StopVisualDebuggerAction.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.ui.actions.stop; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import no.hvl.tk.visual.debugger.SharedState; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class StopVisualDebuggerAction extends AnAction { 9 | 10 | @Override 11 | public void actionPerformed(@NotNull final AnActionEvent e) { 12 | SharedState.setDebuggingActive(false); 13 | SharedState.getDebugListener().getOrCreateDebuggingInfoVisualizer().debuggingDeactivated(); 14 | SharedState.getDebugListener().resetUIAndAddActivateDebuggingButton(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/util/ClassloaderUtil.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.util; 2 | 3 | public class ClassloaderUtil { 4 | private ClassloaderUtil() { 5 | // only util methods 6 | } 7 | 8 | public static V runWithContextClassloader(final Executable executable) { 9 | final ClassLoader current = Thread.currentThread().getContextClassLoader(); 10 | try { 11 | Thread.currentThread().setContextClassLoader(executable.getClass().getClassLoader()); 12 | // code working with ServiceLoader here 13 | return executable.run(); 14 | } finally { 15 | Thread.currentThread().setContextClassLoader(current); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/util/DiagramToJSONConverter.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.util; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.intellij.openapi.diagnostic.Logger; 7 | import no.hvl.tk.visual.debugger.domain.ObjectDiagram; 8 | 9 | public class DiagramToJSONConverter { 10 | 11 | private static final Logger LOGGER = Logger.getInstance(DiagramToJSONConverter.class); 12 | 13 | private static final ObjectMapper mapper = createJacksonMapper(); 14 | 15 | private DiagramToJSONConverter() {} 16 | 17 | public static String toJSON(final ObjectDiagram objectDiagram) { 18 | return ClassloaderUtil.runWithContextClassloader(() -> convert(objectDiagram)); 19 | } 20 | 21 | private static String convert(final ObjectDiagram objectDiagram) { 22 | try { 23 | return mapper.writeValueAsString(objectDiagram); 24 | } catch (JsonProcessingException e) { 25 | LOGGER.error(e); 26 | } 27 | return ""; 28 | } 29 | 30 | private static ObjectMapper createJacksonMapper() { 31 | ObjectMapper objectMapper = new ObjectMapper(); 32 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 33 | return objectMapper; 34 | } 35 | 36 | public static ObjectMapper getMapper() { 37 | return mapper; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/no/hvl/tk/visual/debugger/util/Executable.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.util; 2 | 3 | public interface Executable { 4 | V run(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | no.hvl.tk.visualDebugger 3 | Visual Debugger 4 | Tim Kräuter 5 | 6 | 19 | 20 | 22 | com.intellij.modules.platform 23 | com.intellij.modules.java 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 39 | 46 | 53 | 54 | 55 | 56 | 57 | 60 | 62 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/icons/browser.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/icons/browser_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/icons/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/icons/settings_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/icons/stop.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/icons/stop_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/ui/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-button-hover: #fbdbc0; 3 | } 4 | 5 | html, 6 | body, 7 | #canvas { 8 | font-family: "IBM Plex Sans", sans-serif; 9 | height: 100%; 10 | padding: 0; 11 | margin: 0; 12 | } 13 | 14 | .bottom-buttons { 15 | bottom: 20px; 16 | left: 20px; 17 | } 18 | 19 | .top-right { 20 | position: fixed; 21 | font-weight: bold; 22 | top: 60px; 23 | right: 15px; 24 | } 25 | 26 | .previous-state { 27 | left: 35%; 28 | bottom: 20px; 29 | } 30 | 31 | .current-state { 32 | position: fixed; 33 | left: 49.5%; 34 | bottom: 25px; 35 | } 36 | 37 | .disabled { 38 | opacity: 0.4; 39 | } 40 | 41 | .ojs-buttons-active:hover, 42 | .previous-state button:hover, 43 | .next-state button:hover, 44 | .bottom-buttons a:hover { 45 | background-color: var(--color-button-hover); 46 | transition: background-color 0.2s linear; 47 | } 48 | 49 | .next-state { 50 | left: 65%; 51 | bottom: 20px; 52 | } 53 | 54 | .bottom-buttons a.active { 55 | opacity: 1; 56 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1); 57 | } 58 | 59 | /* screen interaction helpers & modal */ 60 | 61 | #io-dialog-main, 62 | #help-dialog-main { 63 | display: none; /*default*/ 64 | } 65 | 66 | #io-editing-tools-buttons { 67 | display: block; 68 | position: fixed; 69 | top: 0; 70 | right: 0; 71 | padding: 5px; 72 | margin: 0; 73 | } 74 | 75 | .icon-image::before { 76 | content: "\e807"; 77 | } 78 | 79 | .icon-download::before { 80 | content: "\e808"; 81 | } 82 | 83 | .icon-edit::before { 84 | content: "\e806"; 85 | } 86 | 87 | .icon-keyboard::before { 88 | content: "\e802"; 89 | } 90 | 91 | .icon-resize-full::before { 92 | content: "\e801"; 93 | } 94 | 95 | .icon-question::before { 96 | content: "\e803"; 97 | } 98 | 99 | .keybindings { 100 | display: grid; 101 | grid-template-columns: repeat(2, 1fr); 102 | grid-row-gap: 10px; 103 | align-items: center; 104 | } 105 | 106 | .binding { 107 | font-family: monospace; 108 | } 109 | 110 | .marker { 111 | color: white; 112 | border-radius: 50%; 113 | width: 25px; 114 | height: 25px; 115 | display: flex; 116 | justify-content: center; 117 | align-items: center; 118 | } 119 | 120 | .marker-added::before { 121 | content: "\e800"; 122 | } 123 | 124 | .marker.marker-added { 125 | background: #54b415; 126 | } 127 | 128 | .marker-changed::before { 129 | font-size: 0.85em; 130 | content: "\e806"; 131 | } 132 | 133 | .marker.marker-changed { 134 | background: #ef944e; 135 | } 136 | 137 | #help-dialog-content { 138 | width: 490px; 139 | } 140 | 141 | #help-dialog-content > ul { 142 | list-style-type: square; 143 | } 144 | 145 | /* margin-bottom but not on the last element */ 146 | #help-dialog-content > ul > li:not(:last-child) { 147 | margin-bottom: 5px; 148 | } 149 | -------------------------------------------------------------------------------- /src/main/resources/ui/bpmn.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/bpmn.eot -------------------------------------------------------------------------------- /src/main/resources/ui/bpmn.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/bpmn.ttf -------------------------------------------------------------------------------- /src/main/resources/ui/bpmn.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/bpmn.woff -------------------------------------------------------------------------------- /src/main/resources/ui/bpmn.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/bpmn.woff2 -------------------------------------------------------------------------------- /src/main/resources/ui/fa.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/fa.eot -------------------------------------------------------------------------------- /src/main/resources/ui/fa.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2023 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/ui/fa.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/fa.ttf -------------------------------------------------------------------------------- /src/main/resources/ui/fa.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/fa.woff -------------------------------------------------------------------------------- /src/main/resources/ui/fa.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/fa.woff2 -------------------------------------------------------------------------------- /src/main/resources/ui/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Visual Debugger 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |

Keyboard Shortcuts

14 |
15 |
Previous debug state
16 |
17 |
Next debug state
18 |
19 |
Zooming
20 |
ctrl + Scrolling
21 |
Scrolling
22 |
ctrl + ↑/↓/→/←
23 |
Fast scrolling
24 |
25 | ctrl + ⇧ + ↑/↓/→/← 26 |
27 |
28 |
29 |
30 |
31 |
32 |

Visual Debugger Help

33 |
    34 |
  • 35 | The visualizer will update when stepping forward in the source code. 36 |
  • 37 |
  • Double click an element to load its immediate children.
  • 38 |
  • 39 | Export an SVG or XML file for further analysis/editing if needed. 40 |
  • 41 |
  • 42 | Edit your object diagram by providing its XML file 43 | here. 49 |
  • 50 |
51 |
52 |
53 | 54 |
55 | 56 |
57 |
58 |
59 | 66 |
67 |
68 | 75 |
76 |
77 | 84 |
85 |
86 | 93 |
94 |
95 |
96 |
97 | 98 |
99 |
100 |
101 | 102 | 103 | 104 |
105 |
106 | 107 | 108 | 109 |
110 |
111 |
112 | 113 |
114 | 117 |
118 | 119 |
Debug Data
120 |
121 | 124 |
125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/main/resources/ui/od.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/od.eot -------------------------------------------------------------------------------- /src/main/resources/ui/od.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2021 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/ui/od.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/od.ttf -------------------------------------------------------------------------------- /src/main/resources/ui/od.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/od.woff -------------------------------------------------------------------------------- /src/main/resources/ui/od.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timKraeuter/VisualDebugger/cc57220e2aeadca302ae805c1b84de040a4d2641/src/main/resources/ui/od.woff2 -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/ArrayReferenceMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.*; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class ArrayReferenceMock implements ArrayReference { 9 | private static final String ARRAY = "Array"; 10 | private final long id; 11 | private final Value[] content; 12 | 13 | public ArrayReferenceMock(final List content) { 14 | this.content = content.toArray(new Value[0]); 15 | this.id = StringReferenceMock.idCounter.incrementAndGet(); 16 | } 17 | 18 | @Override 19 | public long uniqueID() { 20 | return this.id; 21 | } 22 | 23 | @Override 24 | public int length() { 25 | return this.content.length; 26 | } 27 | 28 | @Override 29 | public Value getValue(final int i) { 30 | return this.content[i]; 31 | } 32 | 33 | @Override 34 | public ReferenceType referenceType() { 35 | return new ReferenceTypeMock(ARRAY); 36 | } 37 | 38 | @Override 39 | public Type type() { 40 | return new TypeMock(ARRAY); 41 | } 42 | 43 | @Override 44 | public List getValues() { 45 | return Arrays.asList(this.content); 46 | } 47 | 48 | // Below is irrelevant. 49 | 50 | @Override 51 | public List getValues(final int i, final int i1) { 52 | return null; 53 | } 54 | 55 | @Override 56 | public void setValue(final int i, final Value value) { 57 | // Irrelevant 58 | } 59 | 60 | @Override 61 | public void setValues(final List list) { 62 | // Irrelevant 63 | } 64 | 65 | @Override 66 | public void setValues(final int i, final List list, final int i1, final int i2) { 67 | // Irrelevant 68 | } 69 | 70 | @Override 71 | public Value getValue(final Field field) { 72 | return null; 73 | } 74 | 75 | @Override 76 | public Map getValues(final List list) { 77 | return null; 78 | } 79 | 80 | @Override 81 | public void setValue(final Field field, final Value value) { 82 | // Irrelevant 83 | } 84 | 85 | @Override 86 | public Value invokeMethod( 87 | final ThreadReference threadReference, 88 | final Method method, 89 | final List list, 90 | final int i) { 91 | return null; 92 | } 93 | 94 | @Override 95 | public void disableCollection() { 96 | // Irrelevant 97 | } 98 | 99 | @Override 100 | public void enableCollection() { 101 | // Irrelevant 102 | } 103 | 104 | @Override 105 | public boolean isCollected() { 106 | return false; 107 | } 108 | 109 | @Override 110 | public List waitingThreads() { 111 | return null; 112 | } 113 | 114 | @Override 115 | public ThreadReference owningThread() { 116 | return null; 117 | } 118 | 119 | @Override 120 | public int entryCount() { 121 | return 0; 122 | } 123 | 124 | @Override 125 | public List referringObjects(final long l) { 126 | return null; 127 | } 128 | 129 | @Override 130 | public VirtualMachine virtualMachine() { 131 | return null; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/EntryObjectReferenceMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.*; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public class EntryObjectReferenceMock implements ObjectReference { 8 | 9 | private final long id; 10 | private final Value key; 11 | private final Value value; 12 | 13 | public EntryObjectReferenceMock(final Value key, final Value value) { 14 | this.id = StringReferenceMock.idCounter.incrementAndGet(); 15 | this.key = key; 16 | this.value = value; 17 | } 18 | 19 | @Override 20 | public long uniqueID() { 21 | return this.id; 22 | } 23 | 24 | @Override 25 | public Value invokeMethod( 26 | final ThreadReference threadReference, 27 | final Method method, 28 | final List list, 29 | final int i) { 30 | final String name = method.name(); 31 | if (name.equals("getKey")) { 32 | return this.key; 33 | } 34 | if (name.equals("getValue")) { 35 | return this.value; 36 | } 37 | return null; 38 | } 39 | 40 | // Below is irrelevant 41 | 42 | @Override 43 | public ReferenceType referenceType() { 44 | return new ReferenceTypeMock("Map.Entry"); 45 | } 46 | 47 | @Override 48 | public Value getValue(final Field field) { 49 | return null; 50 | } 51 | 52 | @Override 53 | public Map getValues(final List list) { 54 | return null; 55 | } 56 | 57 | @Override 58 | public void setValue(final Field field, final Value value) { 59 | // Irrelevant 60 | } 61 | 62 | @Override 63 | public void disableCollection() { 64 | // Irrelevant 65 | } 66 | 67 | @Override 68 | public void enableCollection() { 69 | // Irrelevant 70 | } 71 | 72 | @Override 73 | public boolean isCollected() { 74 | return false; 75 | } 76 | 77 | @Override 78 | public List waitingThreads() { 79 | return null; 80 | } 81 | 82 | @Override 83 | public ThreadReference owningThread() { 84 | return null; 85 | } 86 | 87 | @Override 88 | public int entryCount() { 89 | return 0; 90 | } 91 | 92 | @Override 93 | public List referringObjects(final long l) { 94 | return null; 95 | } 96 | 97 | @Override 98 | public Type type() { 99 | return null; 100 | } 101 | 102 | @Override 103 | public VirtualMachine virtualMachine() { 104 | return null; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/FieldMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.*; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class FieldMock implements Field { 7 | private final String fieldName; 8 | private final String typeName; 9 | 10 | public FieldMock(final String fieldName, final String typeName) { 11 | this.fieldName = fieldName; 12 | this.typeName = typeName; 13 | } 14 | 15 | @Override 16 | public String typeName() { 17 | return this.typeName; 18 | } 19 | 20 | @Override 21 | public Type type() throws ClassNotLoadedException { 22 | return new TypeMock(this.typeName); 23 | } 24 | 25 | @Override 26 | public String name() { 27 | return this.fieldName; 28 | } 29 | 30 | // Below is irrelevant. 31 | 32 | @Override 33 | public boolean isTransient() { 34 | return false; 35 | } 36 | 37 | @Override 38 | public boolean isVolatile() { 39 | return false; 40 | } 41 | 42 | @Override 43 | public boolean isEnumConstant() { 44 | return false; 45 | } 46 | 47 | @Override 48 | public String signature() { 49 | return null; 50 | } 51 | 52 | @Override 53 | public String genericSignature() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public ReferenceType declaringType() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public boolean isStatic() { 64 | return false; 65 | } 66 | 67 | @Override 68 | public boolean isFinal() { 69 | return false; 70 | } 71 | 72 | @Override 73 | public boolean isSynthetic() { 74 | return false; 75 | } 76 | 77 | @Override 78 | public int modifiers() { 79 | return 0; 80 | } 81 | 82 | @Override 83 | public boolean isPrivate() { 84 | return false; 85 | } 86 | 87 | @Override 88 | public boolean isPackagePrivate() { 89 | return false; 90 | } 91 | 92 | @Override 93 | public boolean isProtected() { 94 | return false; 95 | } 96 | 97 | @Override 98 | public boolean isPublic() { 99 | return false; 100 | } 101 | 102 | @Override 103 | public VirtualMachine virtualMachine() { 104 | return null; 105 | } 106 | 107 | @Override 108 | public int compareTo(@NotNull final Field field) { 109 | return 0; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/InterfaceTypeMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.*; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Map; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class InterfaceTypeMock implements InterfaceType { 10 | private final String typeName; 11 | 12 | public InterfaceTypeMock(final String typeName) { 13 | this.typeName = typeName; 14 | } 15 | 16 | @Override 17 | public String name() { 18 | return this.typeName; 19 | } 20 | 21 | // Below is irrelevant 22 | 23 | @Override 24 | public List superinterfaces() { 25 | return new ArrayList<>(); 26 | } 27 | 28 | @Override 29 | public List subinterfaces() { 30 | return null; 31 | } 32 | 33 | @Override 34 | public List implementors() { 35 | return null; 36 | } 37 | 38 | @Override 39 | public String signature() { 40 | return null; 41 | } 42 | 43 | @Override 44 | public String genericSignature() { 45 | return null; 46 | } 47 | 48 | @Override 49 | public ClassLoaderReference classLoader() { 50 | return null; 51 | } 52 | 53 | @Override 54 | public String sourceName() { 55 | return null; 56 | } 57 | 58 | @Override 59 | public List sourceNames(final String s) { 60 | return null; 61 | } 62 | 63 | @Override 64 | public List sourcePaths(final String s) { 65 | return null; 66 | } 67 | 68 | @Override 69 | public String sourceDebugExtension() { 70 | return null; 71 | } 72 | 73 | @Override 74 | public boolean isStatic() { 75 | return false; 76 | } 77 | 78 | @Override 79 | public boolean isAbstract() { 80 | return false; 81 | } 82 | 83 | @Override 84 | public boolean isFinal() { 85 | return false; 86 | } 87 | 88 | @Override 89 | public boolean isPrepared() { 90 | return false; 91 | } 92 | 93 | @Override 94 | public boolean isVerified() { 95 | return false; 96 | } 97 | 98 | @Override 99 | public boolean isInitialized() { 100 | return false; 101 | } 102 | 103 | @Override 104 | public boolean failedToInitialize() { 105 | return false; 106 | } 107 | 108 | @Override 109 | public List fields() { 110 | return null; 111 | } 112 | 113 | @Override 114 | public List visibleFields() { 115 | return null; 116 | } 117 | 118 | @Override 119 | public List allFields() { 120 | return null; 121 | } 122 | 123 | @Override 124 | public Field fieldByName(final String s) { 125 | return null; 126 | } 127 | 128 | @Override 129 | public List methods() { 130 | return null; 131 | } 132 | 133 | @Override 134 | public List visibleMethods() { 135 | return null; 136 | } 137 | 138 | @Override 139 | public List allMethods() { 140 | return null; 141 | } 142 | 143 | @Override 144 | public List methodsByName(final String s) { 145 | return null; 146 | } 147 | 148 | @Override 149 | public List methodsByName(final String s, final String s1) { 150 | return null; 151 | } 152 | 153 | @Override 154 | public List nestedTypes() { 155 | return null; 156 | } 157 | 158 | @Override 159 | public Value getValue(final Field field) { 160 | return null; 161 | } 162 | 163 | @Override 164 | public Map getValues(final List list) { 165 | return null; 166 | } 167 | 168 | @Override 169 | public ClassObjectReference classObject() { 170 | return null; 171 | } 172 | 173 | @Override 174 | public List allLineLocations() { 175 | return null; 176 | } 177 | 178 | @Override 179 | public List allLineLocations(final String s, final String s1) { 180 | return null; 181 | } 182 | 183 | @Override 184 | public List locationsOfLine(final int i) { 185 | return null; 186 | } 187 | 188 | @Override 189 | public List locationsOfLine(final String s, final String s1, final int i) { 190 | return null; 191 | } 192 | 193 | @Override 194 | public List availableStrata() { 195 | return null; 196 | } 197 | 198 | @Override 199 | public String defaultStratum() { 200 | return null; 201 | } 202 | 203 | @Override 204 | public List instances(final long l) { 205 | return null; 206 | } 207 | 208 | @Override 209 | public int majorVersion() { 210 | return 0; 211 | } 212 | 213 | @Override 214 | public int minorVersion() { 215 | return 0; 216 | } 217 | 218 | @Override 219 | public int constantPoolCount() { 220 | return 0; 221 | } 222 | 223 | @Override 224 | public byte[] constantPool() { 225 | return new byte[0]; 226 | } 227 | 228 | @Override 229 | public int modifiers() { 230 | return 0; 231 | } 232 | 233 | @Override 234 | public boolean isPrivate() { 235 | return false; 236 | } 237 | 238 | @Override 239 | public boolean isPackagePrivate() { 240 | return false; 241 | } 242 | 243 | @Override 244 | public boolean isProtected() { 245 | return false; 246 | } 247 | 248 | @Override 249 | public boolean isPublic() { 250 | return false; 251 | } 252 | 253 | @Override 254 | public VirtualMachine virtualMachine() { 255 | return null; 256 | } 257 | 258 | @Override 259 | public int compareTo(@NotNull final ReferenceType referenceType) { 260 | return 0; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/LocalVariableMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.*; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public class LocalVariableMock implements LocalVariable { 7 | 8 | private final String name; 9 | private final String typeName; 10 | 11 | public LocalVariableMock(String name, String typeName) { 12 | this.name = name; 13 | this.typeName = typeName; 14 | } 15 | 16 | @Override 17 | public String name() { 18 | return name; 19 | } 20 | 21 | @Override 22 | public String typeName() { 23 | return typeName; 24 | } 25 | 26 | @Override 27 | public Type type() throws ClassNotLoadedException { 28 | return null; 29 | } 30 | 31 | @Override 32 | public String signature() { 33 | return null; 34 | } 35 | 36 | @Override 37 | public String genericSignature() { 38 | return null; 39 | } 40 | 41 | @Override 42 | public boolean isVisible(StackFrame frame) { 43 | return false; 44 | } 45 | 46 | @Override 47 | public boolean isArgument() { 48 | return false; 49 | } 50 | 51 | @Override 52 | public VirtualMachine virtualMachine() { 53 | return null; 54 | } 55 | 56 | @Override 57 | public int compareTo(@NotNull LocalVariable o) { 58 | return 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/MethodMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.*; 4 | import java.util.List; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public record MethodMock(String name) implements Method { 8 | 9 | // Below is irrelevant 10 | 11 | @Override 12 | public String returnTypeName() { 13 | return null; 14 | } 15 | 16 | @Override 17 | public Type returnType() { 18 | return null; 19 | } 20 | 21 | @Override 22 | public List argumentTypeNames() { 23 | return null; 24 | } 25 | 26 | @Override 27 | public List argumentTypes() { 28 | return null; 29 | } 30 | 31 | @Override 32 | public boolean isAbstract() { 33 | return false; 34 | } 35 | 36 | @Override 37 | public boolean isSynchronized() { 38 | return false; 39 | } 40 | 41 | @Override 42 | public boolean isNative() { 43 | return false; 44 | } 45 | 46 | @Override 47 | public boolean isVarArgs() { 48 | return false; 49 | } 50 | 51 | @Override 52 | public boolean isBridge() { 53 | return false; 54 | } 55 | 56 | @Override 57 | public boolean isConstructor() { 58 | return false; 59 | } 60 | 61 | @Override 62 | public boolean isStaticInitializer() { 63 | return false; 64 | } 65 | 66 | @Override 67 | public boolean isObsolete() { 68 | return false; 69 | } 70 | 71 | @Override 72 | public List allLineLocations() { 73 | return null; 74 | } 75 | 76 | @Override 77 | public List allLineLocations(final String s, final String s1) { 78 | return null; 79 | } 80 | 81 | @Override 82 | public List locationsOfLine(final int i) { 83 | return null; 84 | } 85 | 86 | @Override 87 | public List locationsOfLine(final String s, final String s1, final int i) { 88 | return null; 89 | } 90 | 91 | @Override 92 | public Location locationOfCodeIndex(final long l) { 93 | return null; 94 | } 95 | 96 | @Override 97 | public List variables() { 98 | return null; 99 | } 100 | 101 | @Override 102 | public List variablesByName(final String s) { 103 | return null; 104 | } 105 | 106 | @Override 107 | public List arguments() { 108 | return null; 109 | } 110 | 111 | @Override 112 | public byte[] bytecodes() { 113 | return new byte[0]; 114 | } 115 | 116 | @Override 117 | public Location location() { 118 | return null; 119 | } 120 | 121 | @Override 122 | public String signature() { 123 | return null; 124 | } 125 | 126 | @Override 127 | public String genericSignature() { 128 | return null; 129 | } 130 | 131 | @Override 132 | public ReferenceType declaringType() { 133 | return null; 134 | } 135 | 136 | @Override 137 | public boolean isStatic() { 138 | return false; 139 | } 140 | 141 | @Override 142 | public boolean isFinal() { 143 | return false; 144 | } 145 | 146 | @Override 147 | public boolean isSynthetic() { 148 | return false; 149 | } 150 | 151 | @Override 152 | public int modifiers() { 153 | return 0; 154 | } 155 | 156 | @Override 157 | public boolean isPrivate() { 158 | return false; 159 | } 160 | 161 | @Override 162 | public boolean isPackagePrivate() { 163 | return false; 164 | } 165 | 166 | @Override 167 | public boolean isProtected() { 168 | return false; 169 | } 170 | 171 | @Override 172 | public boolean isPublic() { 173 | return false; 174 | } 175 | 176 | @Override 177 | public VirtualMachine virtualMachine() { 178 | return null; 179 | } 180 | 181 | @Override 182 | public int compareTo(@NotNull final Method method) { 183 | return 0; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/ObjectReferenceMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.*; 4 | import java.util.*; 5 | import no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value.BooleanValueMock; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class ObjectReferenceMock implements ObjectReference, Iterable { 9 | private final long id; 10 | private final ReferenceTypeMock type; 11 | private final HashMap fields; 12 | private Collection itSource = new HashSet<>(); 13 | private Map itMapSource = new HashMap<>(); 14 | 15 | private Iterator runningIt = null; 16 | private Iterator> runningMapIt = null; 17 | private boolean itShouldBeMap = false; 18 | 19 | public static ObjectReferenceMock create(final String typeName) { 20 | return new ObjectReferenceMock<>(typeName); 21 | } 22 | 23 | public static ObjectReferenceMock createCollectionObjectRefMock( 24 | final String typeName, final Collection content) { 25 | final ObjectReferenceMock aObjectReferenceMock = new ObjectReferenceMock<>(typeName); 26 | aObjectReferenceMock.referenceType().addInterface(new InterfaceTypeMock(typeName)); 27 | aObjectReferenceMock.setIteratorSource(content); 28 | return aObjectReferenceMock; 29 | } 30 | 31 | public static ObjectReferenceMock createMapObjectRefMock( 32 | final String typeName, final Map content) { 33 | final ObjectReferenceMock aObjectReferenceMock = new ObjectReferenceMock<>(typeName); 34 | aObjectReferenceMock.referenceType().addInterface(new InterfaceTypeMock(typeName)); 35 | aObjectReferenceMock.setItMapSource(content); 36 | return aObjectReferenceMock; 37 | } 38 | 39 | @NotNull @Override 40 | public Iterator iterator() { 41 | return this.itSource.iterator(); 42 | } 43 | 44 | private ObjectReferenceMock(final String typeName) { 45 | this.type = new ReferenceTypeMock(typeName); 46 | this.id = StringReferenceMock.idCounter.incrementAndGet(); 47 | this.fields = new HashMap<>(); 48 | } 49 | 50 | @Override 51 | public ReferenceTypeMock referenceType() { 52 | return this.type; 53 | } 54 | 55 | @Override 56 | public Value getValue(final Field field) { 57 | return this.fields.get(field); 58 | } 59 | 60 | @Override 61 | public Map getValues(final List fields) { 62 | return this.fields; 63 | } 64 | 65 | @Override 66 | public void setValue(final Field field, final Value value) { 67 | this.fields.put(field, value); 68 | } 69 | 70 | @Override 71 | public Type type() { 72 | return new TypeMock(this.type.name()); 73 | } 74 | 75 | private void setIteratorSource(final Collection collection) { 76 | this.itSource = collection; 77 | } 78 | 79 | // Below is irrelevant 80 | 81 | @Override 82 | public Value invokeMethod( 83 | final ThreadReference thread, 84 | final Method method, 85 | final List arguments, 86 | final int options) { 87 | if (method.name().equals("iterator")) { 88 | if (this.itShouldBeMap) { 89 | this.runningMapIt = this.itMapSource.entrySet().iterator(); 90 | } else { 91 | this.runningIt = this.itSource.iterator(); 92 | } 93 | return this; 94 | } 95 | if (method.name().equals("hasNext")) { 96 | if (this.itShouldBeMap) { 97 | return new BooleanValueMock(this.runningMapIt.hasNext()); 98 | } else { 99 | return new BooleanValueMock(this.runningIt.hasNext()); 100 | } 101 | } 102 | if (method.name().equals("next")) { 103 | if (this.itShouldBeMap) { 104 | final Map.Entry next = this.runningMapIt.next(); 105 | return new EntryObjectReferenceMock(next.getKey(), next.getValue()); 106 | } else { 107 | return this.runningIt.next(); 108 | } 109 | } 110 | if (method.name().equals("entrySet")) { 111 | this.itShouldBeMap = true; 112 | return this; 113 | } 114 | return null; 115 | } 116 | 117 | public void setItMapSource(final Map itMapSource) { 118 | this.itMapSource = itMapSource; 119 | } 120 | 121 | @Override 122 | public void disableCollection() { 123 | // Irrelevant 124 | } 125 | 126 | @Override 127 | public void enableCollection() { 128 | // Irrelevant 129 | } 130 | 131 | @Override 132 | public boolean isCollected() { 133 | return false; 134 | } 135 | 136 | @Override 137 | public long uniqueID() { 138 | return this.id; 139 | } 140 | 141 | @Override 142 | public List waitingThreads() { 143 | return null; 144 | } 145 | 146 | @Override 147 | public ThreadReference owningThread() { 148 | return null; 149 | } 150 | 151 | @Override 152 | public int entryCount() { 153 | return 0; 154 | } 155 | 156 | @Override 157 | public List referringObjects(final long maxReferrers) { 158 | return null; 159 | } 160 | 161 | @Override 162 | public VirtualMachine virtualMachine() { 163 | return null; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/ReferenceTypeMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.*; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Map; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | public class ReferenceTypeMock implements ReferenceType, ClassType { 12 | private final String name; 13 | private final List interfaces = new ArrayList<>(); 14 | 15 | public ReferenceTypeMock(final String name) { 16 | this.name = name; 17 | } 18 | 19 | public void addInterface(final InterfaceType type) { 20 | this.interfaces.add(type); 21 | } 22 | 23 | @Override 24 | public String name() { 25 | return this.name; 26 | } 27 | 28 | @Override 29 | public List allFields() { 30 | return new ArrayList<>(); 31 | } 32 | 33 | @Override 34 | public List visibleFields() { 35 | return null; 36 | } 37 | 38 | @Override 39 | public String signature() { 40 | return null; 41 | } 42 | 43 | @Override 44 | public String genericSignature() { 45 | return null; 46 | } 47 | 48 | @Override 49 | public ClassLoaderReference classLoader() { 50 | return null; 51 | } 52 | 53 | @Override 54 | public String sourceName() { 55 | return null; 56 | } 57 | 58 | @Override 59 | public List sourceNames(final String stratum) { 60 | return null; 61 | } 62 | 63 | @Override 64 | public List sourcePaths(final String stratum) { 65 | return null; 66 | } 67 | 68 | @Override 69 | public String sourceDebugExtension() { 70 | return null; 71 | } 72 | 73 | @Override 74 | public boolean isStatic() { 75 | return false; 76 | } 77 | 78 | @Override 79 | public boolean isAbstract() { 80 | return false; 81 | } 82 | 83 | @Override 84 | public boolean isFinal() { 85 | return false; 86 | } 87 | 88 | @Override 89 | public boolean isPrepared() { 90 | return false; 91 | } 92 | 93 | @Override 94 | public boolean isVerified() { 95 | return false; 96 | } 97 | 98 | @Override 99 | public boolean isInitialized() { 100 | return false; 101 | } 102 | 103 | @Override 104 | public boolean failedToInitialize() { 105 | return false; 106 | } 107 | 108 | @Override 109 | public List fields() { 110 | return null; 111 | } 112 | 113 | @Override 114 | public Field fieldByName(final String fieldName) { 115 | return null; 116 | } 117 | 118 | @Override 119 | public List methods() { 120 | return null; 121 | } 122 | 123 | @Override 124 | public List visibleMethods() { 125 | return null; 126 | } 127 | 128 | @Override 129 | public List allMethods() { 130 | return null; 131 | } 132 | 133 | @Override 134 | public List methodsByName(final String name) { 135 | return Collections.singletonList(new MethodMock(name)); 136 | } 137 | 138 | @Override 139 | public List methodsByName(final String name, final String signature) { 140 | return null; 141 | } 142 | 143 | @Override 144 | public List nestedTypes() { 145 | return null; 146 | } 147 | 148 | @Override 149 | public Value getValue(final Field field) { 150 | return null; 151 | } 152 | 153 | @Override 154 | public Map getValues(final List fields) { 155 | return null; 156 | } 157 | 158 | @Override 159 | public ClassObjectReference classObject() { 160 | return null; 161 | } 162 | 163 | @Override 164 | public List allLineLocations() { 165 | return null; 166 | } 167 | 168 | @Override 169 | public List allLineLocations(final String stratum, final String sourceName) { 170 | return null; 171 | } 172 | 173 | @Override 174 | public List locationsOfLine(final int lineNumber) { 175 | return null; 176 | } 177 | 178 | @Override 179 | public List locationsOfLine( 180 | final String stratum, final String sourceName, final int lineNumber) { 181 | return null; 182 | } 183 | 184 | @Override 185 | public List availableStrata() { 186 | return null; 187 | } 188 | 189 | @Override 190 | public String defaultStratum() { 191 | return null; 192 | } 193 | 194 | @Override 195 | public List instances(final long maxInstances) { 196 | return null; 197 | } 198 | 199 | @Override 200 | public int majorVersion() { 201 | return 0; 202 | } 203 | 204 | @Override 205 | public int minorVersion() { 206 | return 0; 207 | } 208 | 209 | @Override 210 | public int constantPoolCount() { 211 | return 0; 212 | } 213 | 214 | @Override 215 | public byte[] constantPool() { 216 | return new byte[0]; 217 | } 218 | 219 | @Override 220 | public int modifiers() { 221 | return 0; 222 | } 223 | 224 | @Override 225 | public boolean isPrivate() { 226 | return false; 227 | } 228 | 229 | @Override 230 | public boolean isPackagePrivate() { 231 | return false; 232 | } 233 | 234 | @Override 235 | public boolean isProtected() { 236 | return false; 237 | } 238 | 239 | @Override 240 | public boolean isPublic() { 241 | return false; 242 | } 243 | 244 | @Override 245 | public VirtualMachine virtualMachine() { 246 | return null; 247 | } 248 | 249 | @Override 250 | public int compareTo(@NotNull final ReferenceType o) { 251 | return 0; 252 | } 253 | 254 | @Nullable @Override 255 | public ClassType superclass() { 256 | return null; 257 | } 258 | 259 | @Override 260 | public List interfaces() { 261 | return this.interfaces; 262 | } 263 | 264 | @Override 265 | public List allInterfaces() { 266 | return null; 267 | } 268 | 269 | @Override 270 | public List subclasses() { 271 | return null; 272 | } 273 | 274 | @Override 275 | public boolean isEnum() { 276 | return false; 277 | } 278 | 279 | @Override 280 | public void setValue(final Field field, final Value value) { 281 | // Irrelevant 282 | } 283 | 284 | @Override 285 | public Value invokeMethod( 286 | final ThreadReference threadReference, 287 | final Method method, 288 | final List list, 289 | final int i) { 290 | return null; 291 | } 292 | 293 | @Override 294 | public ObjectReference newInstance( 295 | final ThreadReference threadReference, 296 | final Method method, 297 | final List list, 298 | final int i) { 299 | return null; 300 | } 301 | 302 | @Override 303 | public Method concreteMethodByName(final String s, final String s1) { 304 | return null; 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/StackFrameMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.intellij.debugger.jdi.LocalVariableProxyImpl; 4 | import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; 5 | import com.sun.jdi.*; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import no.hvl.tk.visual.debugger.debugging.stackframe.IStackFrame; 11 | import org.mockito.Mockito; 12 | 13 | public class StackFrameMock implements IStackFrame { 14 | 15 | private final ObjectReference thisObj; 16 | private final Map localVars = new HashMap<>(); 17 | 18 | public StackFrameMock(ObjectReference thisObj) { 19 | this.thisObj = thisObj; 20 | } 21 | 22 | @Override 23 | public ObjectReference thisObject() { 24 | return thisObj; 25 | } 26 | 27 | @Override 28 | public List visibleVariables() { 29 | return new ArrayList<>(localVars.keySet()); 30 | } 31 | 32 | @Override 33 | public Value getValue(LocalVariableProxyImpl localVariable) { 34 | return this.localVars.get(localVariable); 35 | } 36 | 37 | public void setValue(LocalVariable variable, Value value) { 38 | LocalVariableProxyImpl mock = Mockito.mock(LocalVariableProxyImpl.class); 39 | Mockito.when(mock.name()).thenReturn(variable.name()); 40 | Mockito.when(mock.typeName()).thenReturn(variable.typeName()); 41 | this.localVars.put(mock, value); 42 | } 43 | 44 | @Override 45 | public ThreadReferenceProxyImpl threadProxy() { 46 | return Mockito.mock(ThreadReferenceProxyImpl.class); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/StackFrameMockHelper.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.Value; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value.*; 8 | 9 | public class StackFrameMockHelper { 10 | public static void addLocalStringVariable( 11 | final StackFrameMock sf, final String variableName, final String value) { 12 | sf.setValue( 13 | new LocalVariableMock(variableName, "java.lang.String"), new StringReferenceMock(value)); 14 | } 15 | 16 | public static void addLocalCharVariable( 17 | final StackFrameMock sf, final String variableName, final char value) { 18 | sf.setValue(new LocalVariableMock(variableName, "java.lang.Char"), new CharValueMock(value)); 19 | } 20 | 21 | public static void addLocalBooleanVariable( 22 | final StackFrameMock sf, final String variableName, final boolean value) { 23 | sf.setValue( 24 | new LocalVariableMock(variableName, "java.lang.Boolean"), new BooleanValueMock(value)); 25 | } 26 | 27 | public static void addLocalByteVariable( 28 | final StackFrameMock sf, final String variableName, final byte value) { 29 | sf.setValue(new LocalVariableMock(variableName, "java.lang.Byte"), new ByteValueMock(value)); 30 | } 31 | 32 | public static void addLocalShortVariable( 33 | final StackFrameMock sf, final String variableName, final short value) { 34 | sf.setValue(new LocalVariableMock(variableName, "java.lang.Short"), new ShortValueMock(value)); 35 | } 36 | 37 | public static void addLocalIntegerVariable( 38 | final StackFrameMock sf, final String variableName, final int value) { 39 | sf.setValue( 40 | new LocalVariableMock(variableName, IntegerValueMock.TYPE_NAME), 41 | new IntegerValueMock(value)); 42 | } 43 | 44 | public static void addLocalLongVariable( 45 | final StackFrameMock sf, final String variableName, final long value) { 46 | sf.setValue(new LocalVariableMock(variableName, "java.lang.Long"), new LongValueMock(value)); 47 | } 48 | 49 | public static void addLocalFloatVariable( 50 | final StackFrameMock sf, final String variableName, final float value) { 51 | sf.setValue(new LocalVariableMock(variableName, "java.lang.Float"), new FloatValueMock(value)); 52 | } 53 | 54 | public static void addLocalDoubleVariable( 55 | final StackFrameMock sf, final String variableName, final double value) { 56 | sf.setValue( 57 | new LocalVariableMock(variableName, "java.lang.Double"), new DoubleValueMock(value)); 58 | } 59 | 60 | public static ObjectReferenceMock createObject( 61 | final StackFrameMock sf, final String typeName, final String variableName) { 62 | final ObjectReferenceMock objRefMock = ObjectReferenceMock.create(typeName); 63 | sf.setValue(new LocalVariableMock(variableName, typeName), objRefMock); 64 | return objRefMock; 65 | } 66 | 67 | public static void addAttributeToObject( 68 | final ObjectReferenceMock objRefMock, 69 | final String fieldName, 70 | final String fieldType, 71 | final Value fieldValue) { 72 | objRefMock.setValue(new FieldMock(fieldName, fieldType), fieldValue); 73 | } 74 | 75 | public static ObjectReferenceMock createChildObject( 76 | final ObjectReferenceMock father, final String fieldName, final String childType) { 77 | final ObjectReferenceMock child = ObjectReferenceMock.create(childType); 78 | father.setValue(new FieldMock(fieldName, childType), child); 79 | return child; 80 | } 81 | 82 | public static void createArray( 83 | final StackFrameMock sf, final String variableName, final List content) { 84 | final ArrayReferenceMock arrayMock = new ArrayReferenceMock(content); 85 | sf.setValue(new LocalVariableMock(variableName, "Array"), arrayMock); 86 | } 87 | 88 | public static void addChildObject( 89 | final ObjectReferenceMock obj, final String fieldName, final Value value) { 90 | obj.setValue(new FieldMock(fieldName, value.type().name()), value); 91 | } 92 | 93 | public static void createList( 94 | final StackFrameMock sf, final String variableName, final List content) { 95 | final String typeName = "java.util.List"; 96 | final ObjectReferenceMock setObjectReferenceMock = 97 | ObjectReferenceMock.createCollectionObjectRefMock(typeName, content); 98 | sf.setValue(new LocalVariableMock(variableName, "java.util.ArrayList"), setObjectReferenceMock); 99 | } 100 | 101 | public static void createSet( 102 | final StackFrameMock sf, final String variableName, final Set content) { 103 | final String typeName = "java.util.Set"; 104 | final ObjectReferenceMock setObjectReferenceMock = 105 | ObjectReferenceMock.createCollectionObjectRefMock(typeName, content); 106 | sf.setValue(new LocalVariableMock(variableName, "java.util.HashSet"), setObjectReferenceMock); 107 | } 108 | 109 | public static void createMap( 110 | final StackFrameMock sf, final String variableName, final Map content) { 111 | final String typeName = "java.util.Map"; 112 | final ObjectReferenceMock setObjectReferenceMock = 113 | ObjectReferenceMock.createMapObjectRefMock(typeName, content); 114 | sf.setValue(new LocalVariableMock(variableName, "java.util.HashMap"), setObjectReferenceMock); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/StringReferenceMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.*; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.concurrent.atomic.AtomicLong; 7 | 8 | public class StringReferenceMock implements StringReference { 9 | public static AtomicLong idCounter = new AtomicLong(0); 10 | 11 | private final long id; 12 | private final String value; 13 | private final ReferenceTypeMock type; 14 | 15 | public StringReferenceMock(String value) { 16 | this.value = value; 17 | this.type = new ReferenceTypeMock("java.lang.String"); 18 | this.id = idCounter.incrementAndGet(); 19 | } 20 | 21 | @Override 22 | public String value() { 23 | return value; 24 | } 25 | 26 | @Override 27 | public ReferenceType referenceType() { 28 | return type; 29 | } 30 | 31 | @Override 32 | public long uniqueID() { 33 | return id; 34 | } 35 | 36 | @Override 37 | public Value getValue(Field sig) { 38 | return this; 39 | } 40 | 41 | @Override 42 | public Map getValues(List fields) { 43 | return null; 44 | } 45 | 46 | @Override 47 | public void setValue(Field field, Value value) { 48 | // Irrelevant 49 | } 50 | 51 | @Override 52 | public Value invokeMethod( 53 | ThreadReference thread, Method method, List arguments, int options) { 54 | return null; 55 | } 56 | 57 | @Override 58 | public void disableCollection() { 59 | // Irrelevant 60 | } 61 | 62 | @Override 63 | public void enableCollection() { 64 | // Irrelevant 65 | } 66 | 67 | @Override 68 | public boolean isCollected() { 69 | return false; 70 | } 71 | 72 | @Override 73 | public List waitingThreads() { 74 | return null; 75 | } 76 | 77 | @Override 78 | public ThreadReference owningThread() { 79 | return null; 80 | } 81 | 82 | @Override 83 | public int entryCount() { 84 | return 0; 85 | } 86 | 87 | @Override 88 | public List referringObjects(long maxReferrers) { 89 | return null; 90 | } 91 | 92 | @Override 93 | public Type type() { 94 | return null; 95 | } 96 | 97 | @Override 98 | public VirtualMachine virtualMachine() { 99 | return null; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/TypeMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks; 2 | 3 | import com.sun.jdi.Type; 4 | import com.sun.jdi.VirtualMachine; 5 | 6 | public class TypeMock implements Type { 7 | 8 | private final String typeName; 9 | 10 | public TypeMock(final String typeName) { 11 | this.typeName = typeName; 12 | } 13 | 14 | @Override 15 | public String name() { 16 | return this.typeName; 17 | } 18 | 19 | @Override 20 | public String signature() { 21 | return null; 22 | } 23 | 24 | @Override 25 | public VirtualMachine virtualMachine() { 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/value/BooleanValueMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value; 2 | 3 | import com.sun.jdi.BooleanValue; 4 | import com.sun.jdi.Type; 5 | import com.sun.jdi.VirtualMachine; 6 | 7 | public record BooleanValueMock(boolean value) implements BooleanValue { 8 | 9 | @Override 10 | public boolean booleanValue() { 11 | return value; 12 | } 13 | 14 | // Below is irrelevant 15 | 16 | @Override 17 | public byte byteValue() { 18 | return 0; 19 | } 20 | 21 | @Override 22 | public char charValue() { 23 | return 0; 24 | } 25 | 26 | @Override 27 | public short shortValue() { 28 | return 0; 29 | } 30 | 31 | @Override 32 | public int intValue() { 33 | return 0; 34 | } 35 | 36 | @Override 37 | public long longValue() { 38 | return 0; 39 | } 40 | 41 | @Override 42 | public float floatValue() { 43 | return 0; 44 | } 45 | 46 | @Override 47 | public double doubleValue() { 48 | return 0; 49 | } 50 | 51 | @Override 52 | public Type type() { 53 | return null; 54 | } 55 | 56 | @Override 57 | public VirtualMachine virtualMachine() { 58 | return null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/value/ByteValueMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value; 2 | 3 | import com.sun.jdi.ByteValue; 4 | import com.sun.jdi.Type; 5 | import com.sun.jdi.VirtualMachine; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public record ByteValueMock(byte value) implements ByteValue { 9 | 10 | @Override 11 | public byte byteValue() { 12 | return value; 13 | } 14 | 15 | // Below is irrelevant 16 | 17 | @Override 18 | public boolean booleanValue() { 19 | return false; 20 | } 21 | 22 | @Override 23 | public char charValue() { 24 | return 0; 25 | } 26 | 27 | @Override 28 | public short shortValue() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public int intValue() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public long longValue() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public float floatValue() { 44 | return 0; 45 | } 46 | 47 | @Override 48 | public double doubleValue() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public Type type() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public VirtualMachine virtualMachine() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public int compareTo(@NotNull ByteValue o) { 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/value/CharValueMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value; 2 | 3 | import com.sun.jdi.CharValue; 4 | import com.sun.jdi.Type; 5 | import com.sun.jdi.VirtualMachine; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public record CharValueMock(char value) implements CharValue { 9 | 10 | // Below is irrelevant 11 | 12 | @Override 13 | public boolean booleanValue() { 14 | return false; 15 | } 16 | 17 | @Override 18 | public byte byteValue() { 19 | return 0; 20 | } 21 | 22 | @Override 23 | public char charValue() { 24 | return value; 25 | } 26 | 27 | @Override 28 | public short shortValue() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public int intValue() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public long longValue() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public float floatValue() { 44 | return 0; 45 | } 46 | 47 | @Override 48 | public double doubleValue() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public Type type() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public VirtualMachine virtualMachine() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public int compareTo(@NotNull CharValue o) { 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/value/DoubleValueMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value; 2 | 3 | import com.sun.jdi.DoubleValue; 4 | import com.sun.jdi.Type; 5 | import com.sun.jdi.VirtualMachine; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public record DoubleValueMock(double value) implements DoubleValue { 9 | 10 | // Below is irrelevant 11 | 12 | @Override 13 | public boolean booleanValue() { 14 | return false; 15 | } 16 | 17 | @Override 18 | public byte byteValue() { 19 | return 0; 20 | } 21 | 22 | @Override 23 | public char charValue() { 24 | return 0; 25 | } 26 | 27 | @Override 28 | public short shortValue() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public int intValue() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public long longValue() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public float floatValue() { 44 | return 0; 45 | } 46 | 47 | @Override 48 | public double doubleValue() { 49 | return value; 50 | } 51 | 52 | @Override 53 | public Type type() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public VirtualMachine virtualMachine() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public int compareTo(@NotNull DoubleValue o) { 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/value/FloatValueMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value; 2 | 3 | import com.sun.jdi.FloatValue; 4 | import com.sun.jdi.Type; 5 | import com.sun.jdi.VirtualMachine; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public record FloatValueMock(float value) implements FloatValue { 9 | 10 | // Below is irrelevant 11 | 12 | @Override 13 | public boolean booleanValue() { 14 | return false; 15 | } 16 | 17 | @Override 18 | public byte byteValue() { 19 | return 0; 20 | } 21 | 22 | @Override 23 | public char charValue() { 24 | return 0; 25 | } 26 | 27 | @Override 28 | public short shortValue() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public int intValue() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public long longValue() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public float floatValue() { 44 | return value; 45 | } 46 | 47 | @Override 48 | public double doubleValue() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public Type type() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public VirtualMachine virtualMachine() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public int compareTo(@NotNull FloatValue o) { 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/value/IntegerValueMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value; 2 | 3 | import com.sun.jdi.IntegerValue; 4 | import com.sun.jdi.Type; 5 | import com.sun.jdi.VirtualMachine; 6 | import no.hvl.tk.visual.debugger.debugging.stackframe.mocks.TypeMock; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public record IntegerValueMock(int value) implements IntegerValue { 10 | public static final String TYPE_NAME = "java.lang.Integer"; 11 | 12 | @Override 13 | public Type type() { 14 | return new TypeMock(TYPE_NAME); 15 | } 16 | 17 | // Below is irrelevant 18 | 19 | @Override 20 | public boolean booleanValue() { 21 | return false; 22 | } 23 | 24 | @Override 25 | public byte byteValue() { 26 | return 0; 27 | } 28 | 29 | @Override 30 | public char charValue() { 31 | return 0; 32 | } 33 | 34 | @Override 35 | public short shortValue() { 36 | return 0; 37 | } 38 | 39 | @Override 40 | public int intValue() { 41 | return this.value; 42 | } 43 | 44 | @Override 45 | public long longValue() { 46 | return 0; 47 | } 48 | 49 | @Override 50 | public float floatValue() { 51 | return 0; 52 | } 53 | 54 | @Override 55 | public double doubleValue() { 56 | return 0; 57 | } 58 | 59 | @Override 60 | public VirtualMachine virtualMachine() { 61 | return null; 62 | } 63 | 64 | @Override 65 | public int compareTo(@NotNull final IntegerValue o) { 66 | return 0; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/value/LongValueMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value; 2 | 3 | import com.sun.jdi.LongValue; 4 | import com.sun.jdi.Type; 5 | import com.sun.jdi.VirtualMachine; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public record LongValueMock(long value) implements LongValue { 9 | 10 | // Below is irrelevant 11 | 12 | @Override 13 | public boolean booleanValue() { 14 | return false; 15 | } 16 | 17 | @Override 18 | public byte byteValue() { 19 | return 0; 20 | } 21 | 22 | @Override 23 | public char charValue() { 24 | return 0; 25 | } 26 | 27 | @Override 28 | public short shortValue() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public int intValue() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public long longValue() { 39 | return value; 40 | } 41 | 42 | @Override 43 | public float floatValue() { 44 | return 0; 45 | } 46 | 47 | @Override 48 | public double doubleValue() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public Type type() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public VirtualMachine virtualMachine() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public int compareTo(@NotNull LongValue o) { 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/debugging/stackframe/mocks/value/ShortValueMock.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.debugging.stackframe.mocks.value; 2 | 3 | import com.sun.jdi.ShortValue; 4 | import com.sun.jdi.Type; 5 | import com.sun.jdi.VirtualMachine; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public record ShortValueMock(short value) implements ShortValue { 9 | 10 | @Override 11 | public short shortValue() { 12 | return value; 13 | } 14 | 15 | // Below is irrelevant 16 | 17 | @Override 18 | public boolean booleanValue() { 19 | return false; 20 | } 21 | 22 | @Override 23 | public byte byteValue() { 24 | return 0; 25 | } 26 | 27 | @Override 28 | public char charValue() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public int intValue() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public long longValue() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public float floatValue() { 44 | return 0; 45 | } 46 | 47 | @Override 48 | public double doubleValue() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public Type type() { 54 | return null; 55 | } 56 | 57 | @Override 58 | public VirtualMachine virtualMachine() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public int compareTo(@NotNull ShortValue o) { 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/domain/ODAttributeValueTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.CoreMatchers.not; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | class ODAttributeValueTest { 10 | 11 | @Test 12 | void testToString() { 13 | ODAttributeValue odAttributeValue = new ODAttributeValue("attributeName", "type", "value"); 14 | assertThat( 15 | odAttributeValue.toString(), 16 | is("Attribute:[name='attributeName', type='type', value='value']")); 17 | } 18 | 19 | @Test 20 | void testEqualsAndHashCode() { 21 | ODAttributeValue odAttributeValue = new ODAttributeValue("attributeName", "type", "value"); 22 | 23 | ODAttributeValue differentName = new ODAttributeValue("different", "type", "value"); 24 | ODAttributeValue differentType = new ODAttributeValue("attributeName", "different", "value"); 25 | ODAttributeValue differentValue = new ODAttributeValue("attributeName", "type", "different"); 26 | 27 | assertThat(odAttributeValue, is(odAttributeValue)); 28 | assertThat(odAttributeValue.hashCode(), is(odAttributeValue.hashCode())); 29 | 30 | assertThat(odAttributeValue, is(not("123"))); 31 | assertThat(odAttributeValue, is(not(differentName))); 32 | assertThat(odAttributeValue, is(not(differentType))); 33 | assertThat(odAttributeValue, is(not(differentValue))); 34 | assertThat(odAttributeValue.hashCode(), is(not(differentName.hashCode()))); 35 | assertThat(odAttributeValue.hashCode(), is(not(differentType.hashCode()))); 36 | assertThat(odAttributeValue.hashCode(), is(not(differentValue.hashCode()))); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/domain/ODLinkTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.CoreMatchers.not; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | class ODLinkTest { 10 | 11 | @Test 12 | void testToString() { 13 | ODObject odObject1 = new ODObject(1, "String", "person"); 14 | ODObject odObject2 = new ODObject(2, "String", "address"); 15 | ODLink odLink = new ODLink(odObject1, odObject2, "address"); 16 | assertThat(odLink.toString(), is("Link[type='address', from=person, to=address]")); 17 | } 18 | 19 | @Test 20 | void testEqualsAndHashCode() { 21 | ODObject odObject1 = new ODObject(1, "String", "person"); 22 | ODObject odObject2 = new ODObject(2, "String", "address"); 23 | ODLink odLink = new ODLink(odObject1, odObject2, "address"); 24 | 25 | ODLink differentObjects = new ODLink(odObject2, odObject1, "address"); 26 | ODLink differentType = new ODLink(odObject1, odObject2, "type"); 27 | 28 | assertThat(odLink, is(odLink)); 29 | assertThat(odLink.hashCode(), is(odLink.hashCode())); 30 | 31 | assertThat(odLink, is(not(differentObjects))); 32 | assertThat(odLink, is(not("123"))); 33 | assertThat(odLink, is(not(differentType))); 34 | assertThat(odLink.hashCode(), is(not(differentObjects.hashCode()))); 35 | assertThat(odLink.hashCode(), is(not(differentType.hashCode()))); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/domain/ODPrimitiveRootValueTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.domain; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.CoreMatchers.not; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | 7 | import com.google.common.collect.Lists; 8 | import com.google.common.collect.Sets; 9 | import java.util.List; 10 | import java.util.Set; 11 | import org.junit.jupiter.api.Test; 12 | 13 | class ODPrimitiveRootValueTest { 14 | 15 | @Test 16 | void testEqualsAndCompareTo() { 17 | final ODPrimitiveRootValue value1 = new ODPrimitiveRootValue("name", "type", "value"); 18 | assertThat(value1, is(value1)); 19 | assertThat(value1, is(not("123"))); 20 | 21 | final ODPrimitiveRootValue valueWithDifferentName = 22 | new ODPrimitiveRootValue("name1", value1.type(), value1.value()); 23 | final ODPrimitiveRootValue valueWithDifferentType = 24 | new ODPrimitiveRootValue(value1.variableName(), "type1", value1.value()); 25 | final ODPrimitiveRootValue valueWithDifferentValue = 26 | new ODPrimitiveRootValue(value1.variableName(), value1.type(), "value1"); 27 | 28 | assertThat(value1, is(not(valueWithDifferentName))); 29 | assertThat(value1, is(not(valueWithDifferentType))); 30 | assertThat(value1, is(not(valueWithDifferentValue))); 31 | 32 | // Test compareTo() by sorting. 33 | final Set values = 34 | Sets.newHashSet( 35 | value1, valueWithDifferentName, valueWithDifferentType, valueWithDifferentValue); 36 | final List sortedValues = values.stream().sorted().toList(); 37 | 38 | assertThat( 39 | sortedValues, 40 | is( 41 | Lists.newArrayList( 42 | value1, valueWithDifferentType, valueWithDifferentValue, valueWithDifferentName))); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/DebuggingScenariosTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests; 2 | 3 | import com.google.common.collect.Lists; 4 | import java.util.*; 5 | import no.hvl.tk.visual.debugger.manueltests.holders.CollectionHolder; 6 | import no.hvl.tk.visual.debugger.manueltests.holders.PrimitiveCollectionHolder; 7 | import no.hvl.tk.visual.debugger.manueltests.partsList.domain.Material; 8 | import no.hvl.tk.visual.debugger.manueltests.partsList.domain.Product; 9 | import org.apache.commons.compress.utils.Sets; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.junit.jupiter.api.Test; 12 | 13 | /** Manuel testing suite with different debugging scenarios. */ 14 | class DebuggingScenariosTest { 15 | 16 | /** Equivalent Unit-Test using a mocked stack frame in primitiveLocalVariablesTest(). */ 17 | @Test 18 | void testPrimitiveVariables() { 19 | final byte aByte1 = 0; 20 | final Byte aByte2 = 0; 21 | final short aShort1 = 1; 22 | final Short aShort2 = 2; 23 | final int anInt1 = 2; 24 | final Integer anInt2 = 2; 25 | final long aLong1 = 3L; 26 | final Long aLong2 = 3L; 27 | final float aFloat1 = 4.1F; 28 | final Float aFloat2 = 4.1F; 29 | final double aDouble1 = 5.1; 30 | final Double aDouble2 = 5.1; 31 | final char aChar = '6'; 32 | final Character aCharacter = '2'; 33 | final boolean aBoolean1 = true; 34 | final Boolean aBoolean2 = true; 35 | final String aString = "8"; 36 | final String nullString = null; 37 | final String[] stringArray = {"2", "3", "1", "a", "b", "c"}; 38 | final int[] intArray = {1, 2, 3, 4, 5, 6}; 39 | System.out.println("Put your breakpoint here"); 40 | } 41 | 42 | /** Equivalent Unit-Test using a mocked stack frame in objectWithAttributesTest(). */ 43 | @Test 44 | void testWithOneLayerObjectVariables() { 45 | final Material aMaterial = Material.create("stringValue", 42); 46 | System.out.println(aMaterial); 47 | System.out.println(aMaterial); 48 | } 49 | 50 | /** Equivalent Unit-Test using a mocked stack frame in multiLayerObjectTest(). */ 51 | @Test 52 | void testMultiLayerObjectVariables() { 53 | final String productName = "productName"; 54 | final Product product = Product.create(productName, 3); 55 | product.addPart(Material.create("mat1Name", 1), 1); 56 | product.addPart(Material.create("mat2Name", 2), 1); 57 | System.out.println("123"); 58 | System.out.println("123"); 59 | System.out.println("123"); 60 | System.out.println("123"); 61 | System.out.println("123"); 62 | } 63 | 64 | /** Equivalent Unit-Test using a mocked stack frame in primitiveSubCollectionTest(). */ 65 | @Test 66 | void primitiveArrayAsFieldTest() { 67 | final PrimitiveCollectionHolder primitiveArrayHolder = new PrimitiveCollectionHolder(); 68 | System.out.println(primitiveArrayHolder); 69 | System.out.println("123"); 70 | System.out.println("123"); 71 | System.out.println("123"); 72 | System.out.println("123"); 73 | } 74 | 75 | @Test 76 | void collectionsWithNullValuesTest() { 77 | final List stringList = Lists.newArrayList(null, "2", null); 78 | final Set intList = Sets.newHashSet(null, "2", null); 79 | final Map postalCodes = new HashMap<>(); 80 | postalCodes.put("Oslo", 1295); 81 | postalCodes.put("Bergen", 5052); 82 | postalCodes.put("Berlin", null); 83 | postalCodes.put(null, null); 84 | postalCodes.put(null, 30161); 85 | 86 | System.out.println("123"); 87 | System.out.println("123"); 88 | System.out.println("123"); 89 | } 90 | 91 | @Test 92 | void nonPrimitiveCollectionTest() { 93 | // Each list value ends up as an attribute at the moment. 94 | final CollectionHolder materialHolder = 95 | new CollectionHolder<>( 96 | Lists.newArrayList( 97 | Material.create("1", 10), Material.create("2", 20), Material.create("3", 30))); 98 | System.out.println(materialHolder); 99 | System.out.println(materialHolder); 100 | System.out.println(materialHolder); 101 | } 102 | 103 | @Test 104 | void primitiveCollectionAtRootTest() { 105 | // Lists work even with objects 106 | final List stringList = Lists.newArrayList("1", "2", "3"); 107 | final List intList = Lists.newArrayList(1, 2, 3); 108 | System.out.println(stringList); 109 | System.out.println(stringList); 110 | System.out.println(stringList); 111 | System.out.println(intList); 112 | System.out.println(intList); 113 | System.out.println(intList); 114 | } 115 | 116 | @Test 117 | void nonPrimitiveCollectionAtRootTest() { 118 | // Lists work even with objects 119 | final List materialsList = 120 | Lists.newArrayList( 121 | Material.create("1", 10), Material.create("2", 20), Material.create("3", 30)); 122 | System.out.println(materialsList); 123 | System.out.println(materialsList); 124 | System.out.println(materialsList); 125 | System.out.println(materialsList); 126 | // Lists work even with objects 127 | final Set materialsSet = 128 | Sets.newHashSet( 129 | Material.create("1", 10), Material.create("2", 20), Material.create("3", 30)); 130 | System.out.println(materialsSet); 131 | System.out.println(materialsSet); 132 | System.out.println(materialsSet); 133 | System.out.println(materialsSet); 134 | } 135 | 136 | @Test 137 | void primitiveMapVisualisationTest() { 138 | final Map postalCodes = new HashMap<>(); 139 | postalCodes.put("Oslo", 1295); 140 | postalCodes.put("Bergen", 5052); 141 | postalCodes.put("Berlin", 13585); 142 | postalCodes.put("Hanover", 30161); 143 | final Map doubleBooleanMap = new HashMap<>(); 144 | doubleBooleanMap.put(1.1, true); 145 | doubleBooleanMap.put(1.2, false); 146 | doubleBooleanMap.put(1.3, true); 147 | System.out.println(postalCodes); 148 | System.out.println(doubleBooleanMap); 149 | } 150 | 151 | @Test 152 | void nonPrimitiveMapVisualisationTest() { 153 | final Map inventory = new HashMap<>(); 154 | inventory.put(Material.create("Main support", 10), 3); 155 | inventory.put(Material.create("Hinge", 5), 5); 156 | inventory.put(Material.create("Wood screw D3,5 x 20mm", 1), 8); 157 | inventory.put(Material.create("Wood screw D4 x 45mm", 1), 13); 158 | System.out.println(inventory); 159 | System.out.println(inventory); 160 | System.out.println(inventory); 161 | } 162 | 163 | @Test 164 | void duplicateObjectsTest() { 165 | final Material mat1 = Material.create("123", 2); 166 | final List materials = Lists.newArrayList(mat1, mat1); 167 | System.out.println(materials); 168 | System.out.println(materials); 169 | System.out.println(materials); 170 | System.out.println(materials); 171 | System.out.println(materials); 172 | System.out.println(materials); 173 | System.out.println(materials); 174 | } 175 | 176 | @Test 177 | void testScrolling() { 178 | final Product prod1 = DebuggingScenariosTest.makeProductWith2Mats(); 179 | final Product prod2 = DebuggingScenariosTest.makeProductWith2Mats(); 180 | final Product prod3 = DebuggingScenariosTest.makeProductWith2Mats(); 181 | final Product prod4 = DebuggingScenariosTest.makeProductWith2Mats(); 182 | final Product prod5 = DebuggingScenariosTest.makeProductWith2Mats(); 183 | final Product prod6 = DebuggingScenariosTest.makeProductWith2Mats(); 184 | final Product prod7 = DebuggingScenariosTest.makeProductWith2Mats(); 185 | final Product prod8 = DebuggingScenariosTest.makeProductWith2Mats(); 186 | System.out.println(prod8); 187 | System.out.println(prod8); 188 | System.out.println(prod8); 189 | System.out.println(prod8); 190 | System.out.println(prod8); 191 | } 192 | 193 | @NotNull private static Product makeProductWith2Mats() { 194 | final Material mat1 = Material.create("123", 2); 195 | final Material mat2 = Material.create("123", 2); 196 | final Product product = Product.create("123", 1); 197 | product.addPart(mat1, 1); 198 | product.addPart(mat2, 2); 199 | return product; 200 | } 201 | 202 | private static class Cycle { 203 | public Cycle next; 204 | public String name; 205 | 206 | public Cycle() { 207 | this.name = "some name"; 208 | } 209 | 210 | public Cycle(final String name) { 211 | this.name = name; 212 | } 213 | } 214 | 215 | @Test 216 | void testObjectCycle() { 217 | final Cycle c1 = new Cycle(); 218 | final Cycle c2 = new Cycle(); 219 | c1.next = c2; 220 | c2.next = c1; 221 | 222 | System.out.println(c1); 223 | System.out.println(c1); 224 | System.out.println(c1); 225 | System.out.println(c1); 226 | } 227 | 228 | @Test 229 | void testTransitiveObjectCycle() { 230 | final Cycle c1 = new Cycle(); 231 | final Cycle c2 = new Cycle(); 232 | final Cycle c3 = new Cycle(); 233 | 234 | c1.next = c2; 235 | c2.next = c3; 236 | c3.next = c1; 237 | 238 | System.out.println(c1); 239 | System.out.println(c1); 240 | System.out.println(c1); 241 | System.out.println(c1); 242 | } 243 | 244 | @Test 245 | void testVisualisationDepth() { 246 | final Cycle root = new Cycle(); 247 | Cycle parent = root; 248 | for (int i = 1; i <= 100; i++) { 249 | final Cycle child = new Cycle(String.valueOf(i)); 250 | parent.next = child; 251 | parent = child; 252 | } 253 | System.out.println("Buh"); 254 | System.out.println("Buh"); 255 | System.out.println("Buh"); 256 | System.out.println("Buh"); 257 | } 258 | 259 | @Test 260 | void testManyObjects() { 261 | final Set products = new HashSet<>(); 262 | final int amountOfProducts = 100; // each product has two materials. 263 | // So x*3 objects and x*3 links + x links and one object for the root collection. 264 | for (int i = 0; i <= amountOfProducts; i++) { 265 | final Product prod = DebuggingScenariosTest.makeProductWith2Mats(); 266 | products.add(prod); 267 | } 268 | System.out.println(products.size()); 269 | } 270 | 271 | @SuppressWarnings("rawtypes") 272 | @Test 273 | void testAdvancedRecursion() { 274 | final SetRecursion setRecursion = new SetRecursion(); 275 | setRecursion.set.add(setRecursion); 276 | 277 | final Set set1 = new HashSet<>(); 278 | final Set set2 = new HashSet<>(); 279 | set1.add(set2); 280 | set2.add(set1); 281 | 282 | System.out.println("123"); 283 | System.out.println("123"); 284 | System.out.println("123"); 285 | System.out.println("123"); 286 | } 287 | 288 | static class SetRecursion { 289 | Set set = new HashSet<>(); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/bst/BSTNode.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.bst; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | public class BSTNode { 8 | private int value; 9 | private BSTNode left; 10 | private BSTNode right; 11 | 12 | public BSTNode(int value) { 13 | this.value = value; 14 | } 15 | 16 | public void insert(int newValue) { 17 | if (newValue > value) { 18 | if (this.right == null) { 19 | this.right = new BSTNode(newValue); 20 | return; 21 | } 22 | this.right.insert(newValue); 23 | return; 24 | } 25 | if (this.left == null) { 26 | this.left = new BSTNode(newValue); 27 | return; 28 | } 29 | this.left.insert(newValue); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | List values = new ArrayList<>(); 35 | this.addValuesInOrder(values); 36 | return values.stream().map(Object::toString).collect(Collectors.joining(", ")); 37 | } 38 | 39 | private void addValuesInOrder(List values) { 40 | if (left != null) { 41 | this.left.addValuesInOrder(values); 42 | } 43 | values.add(value); 44 | if (right != null) { 45 | this.right.addValuesInOrder(values); 46 | } 47 | } 48 | 49 | public void setValue(int newValue) { 50 | this.value = newValue; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/bst/BSTNodeTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.bst; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class BSTNodeTest { 8 | @Test 9 | void insertTest() { 10 | BSTNode root = new BSTNode(50); 11 | root.insert(30); 12 | root.insert(70); 13 | root.insert(60); 14 | root.insert(80); 15 | root.setValue(49); 16 | root.insert(99); 17 | String treeAsString = root.toString(); 18 | assertEquals("30, 49, 60, 70, 80, 99", treeAsString); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/holders/CollectionHolder.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.holders; 2 | 3 | import java.util.List; 4 | 5 | public class CollectionHolder { 6 | private final List list; 7 | 8 | public CollectionHolder(final List list) { 9 | this.list = list; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/holders/PrimitiveCollectionHolder.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.holders; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Sets; 5 | import java.util.ArrayList; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | public class PrimitiveCollectionHolder { 11 | private final int[] emptyArray = new int[0]; 12 | private final int[] array = new int[] {1, 2, 3}; 13 | private final List emptyList = new ArrayList<>(); 14 | private final List list = Lists.newArrayList(1, 2, 3); 15 | private final Set emptySet = new HashSet<>(); 16 | private final Set set = Sets.newHashSet(1, 2, 3); 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/partsList/PartsListTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.partsList; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import no.hvl.tk.visual.debugger.manueltests.partsList.domain.Material; 7 | import no.hvl.tk.visual.debugger.manueltests.partsList.domain.Product; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class PartsListTest { 11 | @Test 12 | void overallCostForFoldingWallTableTest() { 13 | // Arrange 14 | final Product folding_wall_table = Product.create("Folding wall table", 5); 15 | folding_wall_table.addPart(Material.create("Main support", 10), 1); 16 | folding_wall_table.addPart(Material.create("Hinge", 5), 4); 17 | folding_wall_table.addPart(Material.create("Wood screw D3,5 x 20mm", 1), 26); 18 | folding_wall_table.addPart(Material.create("Wood screw D4 x 45mm", 1), 10); 19 | 20 | // Act 21 | final int cost = folding_wall_table.getOverallCost(); 22 | 23 | // Assert 24 | assertThat(cost, is(71)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/partsList/domain/Component.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.partsList.domain; 2 | 3 | /** Represents a component in parts lists, which has a name and cost. */ 4 | public abstract class Component { 5 | private final String name; 6 | private final int cost; 7 | 8 | protected Component(final String name, final int cost) { 9 | this.name = name; 10 | this.cost = cost; 11 | } 12 | 13 | /** 14 | * Returns true if and only if the receiver directly or indirectly contains the given component. 15 | */ 16 | public abstract boolean contains(Component component); 17 | 18 | /** Get the overall cost of the component including material and assembly cost. */ 19 | public abstract int getOverallCost(); 20 | 21 | /** Get the name of the component. */ 22 | public String getName() { 23 | return this.name; 24 | } 25 | 26 | public int getComponentCost() { 27 | return cost; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return this.getName(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/partsList/domain/Material.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.partsList.domain; 2 | 3 | /** 4 | * Represent a material which can be part of a parts list. A material has an associated material 5 | * cost and name. 6 | */ 7 | public class Material extends Component { 8 | public static Material create(final String name, final int materialCost) { 9 | return new Material(name, materialCost); 10 | } 11 | 12 | private Material(final String name, final int materialCost) { 13 | super(name, materialCost); 14 | } 15 | 16 | @Override 17 | public boolean contains(final Component component) { 18 | return this.equals(component); 19 | } 20 | 21 | @Override 22 | public int getOverallCost() { 23 | return this.getComponentCost(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/partsList/domain/Product.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.partsList.domain; 2 | 3 | import java.util.HashSet; 4 | import java.util.Optional; 5 | import java.util.Set; 6 | import no.hvl.tk.visual.debugger.manueltests.partsList.domain.exception.CycleException; 7 | 8 | /** Represent a product in a parts lists, which has an assembly cost and a set of components. */ 9 | public class Product extends Component { 10 | 11 | public static Product create(final String name, final int assemblyCost) { 12 | return new Product(name, assemblyCost, new HashSet<>()); 13 | } 14 | 15 | private final Set components; 16 | 17 | protected Product( 18 | final String name, final int assemblyCost, final Set components) { 19 | super(name, assemblyCost); 20 | this.components = components; 21 | } 22 | 23 | /** 24 | * Adds the given number of components as a part to the receiver. 25 | * 26 | * @throws CycleException If adding the part would result in a cyclic parts list. 27 | */ 28 | public void addPart(final Component part, final int amount) { 29 | if (part.contains(this)) { 30 | throw new CycleException(); 31 | } 32 | final Optional componentIfExists = 33 | this.components.stream() 34 | .filter(component -> component.getComponent().equals(part)) 35 | .findFirst(); 36 | if (componentIfExists.isPresent()) { 37 | componentIfExists.get().addQuantity(amount); 38 | } else { 39 | this.components.add(QuantifiedComponent.create(amount, part)); 40 | } 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return this.getName(); 46 | } 47 | 48 | @Override 49 | public boolean contains(final Component component) { 50 | if (this.equals(component)) { 51 | return true; 52 | } 53 | return this.components.stream() 54 | .anyMatch(quantifiedComponent -> quantifiedComponent.contains(component)); 55 | } 56 | 57 | @Override 58 | public int getOverallCost() { 59 | int ret = this.getComponentCost(); 60 | for (final QuantifiedComponent current : this.components) { 61 | ret += current.getPrice(); 62 | } 63 | return ret; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/partsList/domain/QuantifiedComponent.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.partsList.domain; 2 | 3 | /** Quantifies a {@link Component}. */ 4 | public class QuantifiedComponent { 5 | 6 | private int quantity; 7 | private final Component component; 8 | 9 | public static QuantifiedComponent create(final int quantity, final Component component) { 10 | return new QuantifiedComponent(quantity, component); 11 | } 12 | 13 | private QuantifiedComponent(final int quantity, final Component component) { 14 | this.quantity = quantity; 15 | this.component = component; 16 | } 17 | 18 | public Component getComponent() { 19 | return this.component; 20 | } 21 | 22 | public int getQuantity() { 23 | return this.quantity; 24 | } 25 | 26 | private void setQuantity(final int quantity) { 27 | this.quantity = quantity; 28 | } 29 | 30 | public void addQuantity(final int quantity) { 31 | this.setQuantity(this.getQuantity() + quantity); 32 | } 33 | 34 | @Override 35 | public boolean equals(final Object argument) { 36 | return super.equals(argument); 37 | } 38 | 39 | public boolean contains(final Component part) { 40 | return this.getComponent().contains(part); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return String.format("%s x %s", this.getQuantity(), this.component.getName()); 46 | } 47 | 48 | public int getPrice() { 49 | return this.quantity * this.component.getOverallCost(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/manueltests/partsList/domain/exception/CycleException.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.manueltests.partsList.domain.exception; 2 | 3 | /** Indicates that a cycle would occur, if the component is added to the product. */ 4 | public class CycleException extends RuntimeException { 5 | 6 | public static final String CYCLES_ARE_NOT_ALLOWED_IN_A_PARTS_LIST = 7 | "Cycles are not allowed in a parts list!"; 8 | 9 | public CycleException() { 10 | super(CYCLES_ARE_NOT_ALLOWED_IN_A_PARTS_LIST); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/server/UIServerStarterTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.server; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | 7 | import jakarta.websocket.ClientEndpoint; 8 | import jakarta.websocket.Session; 9 | import java.io.IOException; 10 | import java.net.URI; 11 | import org.apache.http.HttpStatus; 12 | import org.apache.http.client.methods.CloseableHttpResponse; 13 | import org.apache.http.client.methods.HttpGet; 14 | import org.apache.http.entity.ContentType; 15 | import org.apache.http.impl.client.HttpClientBuilder; 16 | import org.glassfish.grizzly.http.server.HttpServer; 17 | import org.glassfish.tyrus.client.ClientManager; 18 | import org.glassfish.tyrus.server.Server; 19 | import org.junit.jupiter.api.Disabled; 20 | import org.junit.jupiter.api.Test; 21 | 22 | class ServerStarterTest { 23 | 24 | // Works but test is broken due to class 25 | // com.intellij.util.lang.ZipResourceFile$MyUrlConnection cannot be cast to class 26 | // java.net.JarURLConnection (com.intellij.util.lang.ZipResourceFile$MyUrlConnection is in 27 | // unnamed module of loader 'app'; java.net.JarURLConnection is in module java.base of 28 | // loader 'bootstrap') 29 | @Disabled 30 | @Test 31 | void startUIServerTest() throws IOException { 32 | HttpServer httpServer = null; 33 | try { 34 | httpServer = UIServerStarter.runNewServer(); 35 | 36 | HttpGet request = new HttpGet(ServerConstants.UI_SERVER_URL); 37 | 38 | CloseableHttpResponse response = HttpClientBuilder.create().build().execute(request); 39 | 40 | assertThat(response.getStatusLine().getStatusCode(), is(HttpStatus.SC_OK)); 41 | String mimeType = ContentType.getOrDefault(response.getEntity()).getMimeType(); 42 | assertThat(mimeType, is("text/html")); 43 | } finally { 44 | if (httpServer != null) { 45 | httpServer.shutdownNow(); 46 | } 47 | } 48 | } 49 | 50 | @Test 51 | void startAPIServerTest() throws Exception { 52 | final String API_SERVER_URL = 53 | String.format( 54 | "ws://%s:%s/debug", 55 | ServerConstants.HOST_NAME, ServerConstants.VISUAL_DEBUGGING_API_SERVER_PORT); 56 | Server websocketServer = null; 57 | try { 58 | websocketServer = VisualDebuggingAPIServerStarter.runNewServer(); 59 | 60 | final ClientManager websocketClient = ClientManager.createClient(); 61 | final Session session = 62 | websocketClient.connectToServer(MockWebsocketClient.class, new URI(API_SERVER_URL)); 63 | 64 | assertNotNull(session); 65 | } finally { 66 | if (websocketServer != null) { 67 | websocketServer.stop(); 68 | } 69 | } 70 | } 71 | 72 | @ClientEndpoint 73 | static class MockWebsocketClient {} 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/server/endpoint/message/TypedWebsocketMessageTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.server.endpoint.message; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | class TypedWebsocketMessageTest { 9 | 10 | private final String content = "content"; 11 | 12 | @Test 13 | void serializeLoadChildren() { 14 | String serializedMessage = 15 | new DebuggingWSMessage(DebuggingMessageType.LOAD_CHILDREN, content).serialize(); 16 | assertThat(serializedMessage, is("{\"type\":\"loadChildren\",\"content\":\"content\"}")); 17 | } 18 | 19 | @Test 20 | void serializeError() { 21 | String serializedMessage = 22 | new DebuggingWSMessage(DebuggingMessageType.ERROR, content).serialize(); 23 | assertThat(serializedMessage, is("{\"type\":\"error\",\"content\":\"content\"}")); 24 | } 25 | 26 | @Test 27 | void serializeNextDebugStep() { 28 | String serializedMessage = 29 | new DebuggingWSMessage(DebuggingMessageType.NEXT_DEBUG_STEP, content).serialize(); 30 | assertThat(serializedMessage, is("{\"type\":\"nextDebugStep\",\"content\":\"content\"}")); 31 | } 32 | 33 | @Test 34 | void serializeNextDebugStepWithMetadata() { 35 | String serializedMessage = 36 | new DebuggingWSMessage(DebuggingMessageType.NEXT_DEBUG_STEP, content, "Test", 1) 37 | .serialize(); 38 | assertThat( 39 | serializedMessage, 40 | is( 41 | "{\"type\":\"nextDebugStep\",\"content\":\"content\",\"fileName\":\"Test\",\"line\":1}")); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/settings/DebuggingVisualizerOptionTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.settings; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | class DebuggingVisualizerOptionTest { 9 | 10 | @Test 11 | void testToString() { 12 | assertThat( 13 | DebuggingVisualizerOption.EMBEDDED.toString(), is("Embedded visualizer (no interaction)")); 14 | assertThat(DebuggingVisualizerOption.WEB_UI.toString(), is("Browser visualizer")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/settings/VisualDebuggerSettingsComponentTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.settings; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | 7 | import com.intellij.openapi.ui.ValidationInfo; 8 | import com.intellij.ui.components.JBTextField; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.ValueSource; 11 | 12 | class VisualDebuggerSettingsComponentTest { 13 | 14 | @ParameterizedTest 15 | @ValueSource(strings = {"a", "", "-1", "1.1", "1,1"}) 16 | void validateDepthField(String input) { 17 | JBTextField textField = new JBTextField(input); 18 | ValidationInfo validationInfo = VisualDebuggerSettingsComponent.validateNumberField(textField); 19 | assertNotNull(validationInfo); 20 | assertThat(validationInfo.message, is(VisualDebuggerSettingsComponent.NUMBER_GREATER_EQUALS_0)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/no/hvl/tk/visual/debugger/util/DiagramToJSONConverterTest.java: -------------------------------------------------------------------------------- 1 | package no.hvl.tk.visual.debugger.util; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import no.hvl.tk.visual.debugger.domain.*; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class DiagramToJSONConverterTest { 10 | 11 | @Test 12 | void emptyDiagram() { 13 | final ObjectDiagram empty = new ObjectDiagram(); 14 | 15 | final String json = DiagramToJSONConverter.toJSON(empty); 16 | 17 | assertThat(json, is("{\"empty\":true}")); 18 | } 19 | 20 | @Test 21 | void primitiveRootValues() { 22 | final ObjectDiagram diagram = new ObjectDiagram(); 23 | diagram.addPrimitiveRootValue(new ODPrimitiveRootValue("varName1", "varType1", "varValue1")); 24 | diagram.addPrimitiveRootValue(new ODPrimitiveRootValue("varName2", "varType2", "varValue2")); 25 | 26 | final String json = DiagramToJSONConverter.toJSON(diagram); 27 | assertThat( 28 | json, 29 | is( 30 | """ 31 | { 32 | "primitiveRootValues" : [ 33 | { 34 | "variableName" : "varName1", 35 | "type" : "varType1", 36 | "value" : "varValue1" 37 | }, 38 | { 39 | "variableName" : "varName2", 40 | "type" : "varType2", 41 | "value" : "varValue2" 42 | } 43 | ], 44 | "empty" : false 45 | } 46 | """ 47 | .replace("\n", "") 48 | .replace(" ", ""))); 49 | } 50 | 51 | @Test 52 | void objectsWithAttributes() { 53 | final ObjectDiagram diagram = new ObjectDiagram(); 54 | final ODObject obj1 = new ODObject(1, "type", "varName"); 55 | obj1.addAttribute(new ODAttributeValue("attrName", "attrType", "attrValue")); 56 | diagram.addObject(obj1); 57 | 58 | final String json = DiagramToJSONConverter.toJSON(diagram); 59 | 60 | assertThat( 61 | json, 62 | is( 63 | """ 64 | { 65 | "objects" : [ 66 | { 67 | "id" : "Object_1", 68 | "type" : "type", 69 | "variableName" : "varName", 70 | "attributeValues" : [ 71 | { 72 | "name" : "attrName", 73 | "type" : "attrType", 74 | "value" : "attrValue" 75 | } 76 | ], 77 | "links" : [ ] 78 | } 79 | ], 80 | "empty" : false 81 | } 82 | """ 83 | .replace("\n", "") 84 | .replace(" ", ""))); 85 | } 86 | 87 | @Test 88 | void objectsAndLinks() { 89 | final ObjectDiagram diagram = new ObjectDiagram(); 90 | final ODObject obj1 = new ODObject(1, "type1", "varName1"); 91 | final ODObject obj2 = new ODObject(2, "type2", "varName2"); 92 | diagram.addObject(obj1); 93 | diagram.addObject(obj2); 94 | diagram.addLink(new ODLink(obj1, obj2, "friend")); 95 | diagram.addLink(new ODLink(obj2, obj1, "enemy")); 96 | 97 | final String json = DiagramToJSONConverter.toJSON(diagram); 98 | 99 | assertThat( 100 | json, 101 | is( 102 | """ 103 | { 104 | "objects" : [ 105 | { 106 | "id" : "Object_1", 107 | "type" : "type1", 108 | "variableName" : "varName1", 109 | "attributeValues" : [ ], 110 | "links" : [ ] 111 | }, 112 | { 113 | "id" : "Object_2", 114 | "type" : "type2", 115 | "variableName" : "varName2", 116 | "attributeValues" : [ ], 117 | "links" : [ ] 118 | } 119 | ], 120 | "links" : [ 121 | { 122 | "id" : "Link_Object_1_to_Object_2_type_friend", 123 | "type" : "friend", 124 | "from" : "Object_1", 125 | "to" : "Object_2" 126 | }, 127 | { 128 | "id" : "Link_Object_2_to_Object_1_type_enemy", 129 | "type" : "enemy", 130 | "from" : "Object_2", 131 | "to" : "Object_1" 132 | } 133 | ], 134 | "empty" : false 135 | } 136 | """ 137 | .replace("\n", "") 138 | .replace(" ", ""))); 139 | } 140 | } 141 | --------------------------------------------------------------------------------