├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── checkPlugin.yml ├── .gitignore ├── CHANGELOG.md ├── NOTICE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── access-converter-tool.png ├── asmified-view.png ├── constantpool-view.png ├── decompiled-view.png ├── instructions-overview.png ├── open-files-from-editor.png ├── open-files-from-tool-window.png ├── plain-view.png ├── show-method-frames-action.png ├── signature-parser-tool.png ├── structure-view.png └── validate-byte-code-action.png ├── settings.gradle.kts ├── src ├── main │ ├── kotlin │ │ └── dev │ │ │ └── turingcomplete │ │ │ └── intellijbytecodeplugin │ │ │ ├── _ui │ │ │ ├── ByteCodePluginIcons.kt │ │ │ ├── ByteCodeToolWindowFactory.kt │ │ │ ├── ClassFileTab.kt │ │ │ ├── CopyValueAction.kt │ │ │ ├── DefaultClassFileContext.kt │ │ │ ├── NotificationUtils.kt │ │ │ ├── SimpleListCellRenderer.kt │ │ │ ├── ToggleActionButton.kt │ │ │ ├── UiExtensions.kt │ │ │ ├── UiUtils.kt │ │ │ └── ViewValueAction.kt │ │ │ ├── bytecode │ │ │ ├── AccessUtils.kt │ │ │ ├── ClassVersionUtils.kt │ │ │ ├── MethodDeclarationUtils.kt │ │ │ ├── MethodFramesUtils.kt │ │ │ ├── TraceUtils.kt │ │ │ ├── TypeUtils.kt │ │ │ └── _internal │ │ │ │ └── constantpool │ │ │ │ ├── ClassInfo.kt │ │ │ │ ├── ConstantPool.kt │ │ │ │ ├── ConstantPoolInfo.kt │ │ │ │ ├── ConstantPoolUtils.kt │ │ │ │ ├── DoubleInfo.kt │ │ │ │ ├── DynamicInfo.kt │ │ │ │ ├── FloatInfo.kt │ │ │ │ ├── IntegerInfo.kt │ │ │ │ ├── LongInfo.kt │ │ │ │ ├── MethodHandleInfo.kt │ │ │ │ ├── MethodTypeInfo.kt │ │ │ │ ├── ModuleInfo.kt │ │ │ │ ├── NameAndTypeInfo.kt │ │ │ │ ├── PackageInfo.kt │ │ │ │ ├── RefInfo.kt │ │ │ │ ├── StringInfo.kt │ │ │ │ ├── UnusableInfo.kt │ │ │ │ └── Utf8Info.kt │ │ │ ├── common │ │ │ ├── ByteCodeAnalyserOpenClassFileService.kt │ │ │ ├── ClassFile.kt │ │ │ ├── ClassFileContext.kt │ │ │ ├── CommonDataKeys.kt │ │ │ ├── SourceFile.kt │ │ │ └── _internal │ │ │ │ ├── AsyncUtils.kt │ │ │ │ ├── DataProviderUtils.kt │ │ │ │ └── KotlinExtension.kt │ │ │ ├── openclassfiles │ │ │ ├── OpenClassFilesToolWindowAction.kt │ │ │ └── _internal │ │ │ │ ├── AnalyzeByteCodeAction.kt │ │ │ │ ├── ClassFileCandidates.kt │ │ │ │ ├── ClassFilesFinderService.kt │ │ │ │ ├── ClassFilesPreparatorService.kt │ │ │ │ ├── CurrentEditorFileAction.kt │ │ │ │ ├── FileChooserAction.kt │ │ │ │ └── FilesDropHandler.kt │ │ │ ├── settings │ │ │ └── ByteCodeAnalyserSettingsService.kt │ │ │ ├── tool │ │ │ ├── ByteCodeTool.kt │ │ │ └── _internal │ │ │ │ ├── AccessConverterTool.kt │ │ │ │ ├── ClassVersionsOverviewTool.kt │ │ │ │ ├── InstructionsOverviewTool.kt │ │ │ │ └── SignatureParserTool.kt │ │ │ └── view │ │ │ ├── ByteCodeAction.kt │ │ │ ├── ByteCodeParsingResultView.kt │ │ │ ├── ByteCodeView.kt │ │ │ ├── _internal │ │ │ ├── AsmView.kt │ │ │ ├── ErrorStateHandler.kt │ │ │ ├── PlainView.kt │ │ │ ├── ReParseByteCodeAction.kt │ │ │ ├── VerifyByteCodeAction.kt │ │ │ ├── _constantpool │ │ │ │ ├── ConstantPoolTable.kt │ │ │ │ └── ConstantPoolView.kt │ │ │ ├── _decompiler │ │ │ │ ├── DecompiledView.kt │ │ │ │ └── DecompilerUtils.kt │ │ │ └── _structure │ │ │ │ ├── GoToProvider.kt │ │ │ │ ├── StructureTree.kt │ │ │ │ ├── StructureTreeContext.kt │ │ │ │ ├── StructureView.kt │ │ │ │ ├── _class │ │ │ │ ├── ClassStructureNode.kt │ │ │ │ ├── FieldStructureNode.kt │ │ │ │ ├── FramesDialog.kt │ │ │ │ ├── FramesPanel.kt │ │ │ │ ├── KotlinMetadataStructureNode.kt │ │ │ │ ├── MethodStructureNode.kt │ │ │ │ └── ModuleStructureNode.kt │ │ │ │ └── _common │ │ │ │ ├── HtmlTextNode.kt │ │ │ │ ├── HyperLinkNode.kt │ │ │ │ ├── InteractiveNode.kt │ │ │ │ ├── StructureNode.kt │ │ │ │ ├── TextNode.kt │ │ │ │ └── ValueNode.kt │ │ │ └── common │ │ │ └── OpenInEditorAction.kt │ └── resources │ │ ├── META-INF │ │ ├── plugin.xml │ │ ├── pluginIcon.svg │ │ └── pluginIcon_dark.svg │ │ ├── dev │ │ └── turingcomplete │ │ │ └── intellijbytecodeplugin │ │ │ ├── byte-code-instructions.csv │ │ │ └── icon │ │ │ └── kotlin.svg │ │ └── icons │ │ ├── action.svg │ │ ├── action_dark.svg │ │ ├── toolwindow.svg │ │ ├── toolwindow_dark.svg │ │ ├── verify.svg │ │ └── verify_dark.svg └── test │ └── kotlin │ └── dev │ └── turingcomplete │ └── intellijbytecodeplugin │ ├── ClassFileConsumerTestCase.kt │ ├── _internal │ └── constantpool │ │ └── ConstantPoolTest.kt │ ├── openclassfiles │ └── _internal │ │ ├── ClassFilesFinderServiceTest.kt │ │ └── ClassFilesPreparatorServiceTest.kt │ └── view │ └── _internal │ └── _structure │ └── StructureTreeTest.kt └── testProject ├── README.md ├── src └── main │ ├── java │ ├── foo │ │ └── bar │ │ │ ├── JavaEnum.java │ │ │ ├── JavaNestedClasses.java │ │ │ └── package-info.java │ └── module-info.java │ └── kotlin │ ├── KotlinEmptyFile.kt │ ├── KotlinFileWithTwoClasses.kt │ ├── KotlinFileWithoutClassAndPackage.kt │ └── foo │ └── bar │ └── baz │ ├── KotlinEnum.kt │ ├── KotlinFileWithoutClass.kt │ └── KotlinNestedClasses.kt └── testProject.iml /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.github/workflows/checkPlugin.yml: -------------------------------------------------------------------------------- 1 | name: Check Plugin 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | verifyPlugin: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-java@v4 18 | with: 19 | distribution: 'temurin' 20 | java-version: '21' 21 | - uses: burrunan/gradle-cache-action@v2 22 | 23 | - name: Test Plugin 24 | run: ./gradlew test --stacktrace 25 | 26 | - name: Verify Plugin 27 | run: ./gradlew verifyPlugin --stacktrace 28 | 29 | - name: Publish Test Report 30 | uses: mikepenz/action-junit-report@v5 31 | if: success() || failure() 32 | with: 33 | report_paths: '**/build/test-results/test/TEST-*.xml' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # Package Files # 8 | *.jar 9 | !gradle/wrapper/gradle-wrapper.jar 10 | *.war 11 | *.ear 12 | *.zip 13 | *.tar.gz 14 | *.rar 15 | 16 | # Virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 17 | hs_err_pid* 18 | 19 | # Intellij 20 | .idea 21 | /out/ 22 | .intellijPlatform 23 | 24 | # Gradle 25 | .gradle 26 | /build/ 27 | gradle-app.setting 28 | !gradle-wrapper.jar 29 | .gradletasknamecache 30 | 31 | # Mac 32 | .DS_Store -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This software includes code from IntelliJ IDEA Community Edition 2 | Copyright (C) JetBrains s.r.o. 3 | https://www.jetbrains.com/idea/ -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 3 | import org.jetbrains.changelog.Changelog 4 | import org.jetbrains.changelog.date 5 | import org.jetbrains.intellij.platform.gradle.TestFrameworkType 6 | import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.COMPATIBILITY_PROBLEMS 7 | import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.INTERNAL_API_USAGES 8 | import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.INVALID_PLUGIN 9 | import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.MISSING_DEPENDENCIES 10 | import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.NON_EXTENDABLE_API_USAGES 11 | import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.OVERRIDE_ONLY_API_USAGES 12 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 13 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 14 | 15 | fun properties(key: String) = project.findProperty(key).toString() 16 | 17 | plugins { 18 | java 19 | alias(libs.plugins.kotlin.jvm) 20 | alias(libs.plugins.intellij.platform) 21 | alias(libs.plugins.changelog) 22 | alias(libs.plugins.shadow) 23 | } 24 | 25 | group = properties("pluginGroup") 26 | version = properties("pluginVersion") 27 | 28 | repositories { 29 | mavenCentral() 30 | 31 | intellijPlatform { 32 | defaultRepositories() 33 | } 34 | } 35 | 36 | val asm: Configuration by configurations.creating 37 | val asmSource: Configuration by configurations.creating 38 | val testData: Configuration by configurations.creating 39 | configurations.named("testRuntimeOnly").get().extendsFrom(testData) 40 | 41 | val shadowAsmJar = tasks.register("shadowAsmJar") { 42 | group = "shadow" 43 | relocate("org.objectweb.asm", "dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm") 44 | configurations = listOf(asm, asmSource) 45 | archiveClassifier.set("asm") 46 | exclude { file -> file.name == "module-info.class" } 47 | manifest { 48 | attributes("Asm-Version" to libs.versions.asm.get()) 49 | } 50 | } 51 | 52 | dependencies { 53 | intellijPlatform { 54 | val platformVersion = properties("platformVersion") 55 | create(properties("platform"), platformVersion, platformVersion == "LATEST-EAP-SNAPSHOT") 56 | 57 | bundledPlugins(properties("platformBundledPlugins").split(',')) 58 | 59 | pluginVerifier() 60 | zipSigner() 61 | 62 | testFramework(TestFrameworkType.Platform) 63 | testFramework(TestFrameworkType.JUnit5) 64 | testFramework(TestFrameworkType.Plugin.Java) 65 | } 66 | 67 | api(shadowAsmJar.get().outputs.files) 68 | asm(libs.bundles.asm) 69 | asm.dependencies.forEach { 70 | asmSource("${it.group}:${it.name}:${it.version}:sources") 71 | } 72 | 73 | implementation(libs.commons.text) 74 | 75 | testImplementation(libs.assertj.core) 76 | testImplementation(libs.bundles.junit.implementation) 77 | testRuntimeOnly(libs.bundles.junit.runtime) 78 | 79 | testData(libs.groovy) 80 | testData(libs.kotlin.stdlib) 81 | testData(libs.commons.lang3) 82 | } 83 | 84 | intellijPlatform { 85 | pluginConfiguration { 86 | version = providers.gradleProperty("pluginVersion") 87 | ideaVersion { 88 | sinceBuild = properties("pluginSinceBuild") 89 | untilBuild = provider { null } 90 | } 91 | changeNotes.set(provider { changelog.renderItem(changelog.get(project.version as String), Changelog.OutputType.HTML) }) 92 | } 93 | 94 | signing { 95 | val jetbrainsDir = File(System.getProperty("user.home"), ".jetbrains") 96 | certificateChain.set(project.provider { File(jetbrainsDir, "plugin-sign-chain.crt").readText() }) 97 | privateKey.set(project.provider { File(jetbrainsDir, "plugin-sign-private-key.pem").readText() }) 98 | password.set(project.provider { properties("jetbrains.sign-plugin.password") }) 99 | } 100 | 101 | publishing { 102 | // dependsOn("patchChangelog") 103 | token.set(project.provider { properties("jetbrains.marketplace.token") }) 104 | channels.set(listOf(properties("pluginVersion").split('-').getOrElse(1) { "default" }.split('.').first())) 105 | } 106 | 107 | pluginVerification { 108 | failureLevel.set( 109 | listOf( 110 | COMPATIBILITY_PROBLEMS, INTERNAL_API_USAGES, NON_EXTENDABLE_API_USAGES, 111 | OVERRIDE_ONLY_API_USAGES, MISSING_DEPENDENCIES, INVALID_PLUGIN 112 | ) 113 | ) 114 | 115 | ides { 116 | recommended() 117 | } 118 | } 119 | } 120 | 121 | java { 122 | toolchain { 123 | languageVersion.set(JavaLanguageVersion.of(21)) 124 | } 125 | } 126 | 127 | changelog { 128 | val projectVersion = project.version as String 129 | version.set(projectVersion) 130 | header.set("$projectVersion - ${date()}") 131 | groups.set(listOf("Added", "Changed", "Removed", "Fixed")) 132 | } 133 | 134 | tasks { 135 | runIde { 136 | // Enable to test Kotlin K2 beta mode 137 | // systemProperty("idea.kotlin.plugin.use.k2", "true") 138 | } 139 | 140 | withType { 141 | compilerOptions { 142 | freeCompilerArgs = listOf("-Xjsr305=strict") 143 | jvmTarget.set(JvmTarget.JVM_21) 144 | } 145 | } 146 | 147 | withType { 148 | useJUnitPlatform() 149 | } 150 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | pluginGroup=dev.turingcomplete 2 | pluginVersion=4.3.0 3 | pluginSinceBuild=242 4 | platform=IC 5 | # LATEST-EAP-SNAPSHOT 6 | platformVersion=2024.2 7 | platformBundledPlugins=com.intellij.java,org.jetbrains.kotlin 8 | # Opt-out flag for bundling Kotlin standard library. 9 | # See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details. 10 | kotlin.stdlib.default.dependency=false -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | asm = "9.7.1" 3 | commons-text = "1.11.0" 4 | assertj = "3.24.2" 5 | junit5 = "5.10.1" 6 | junit4 = "4.13.2" 7 | groovy = "3.0.16" 8 | commons-lang3 = "3.12.0" 9 | # See bundled version: https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library 10 | kotlin = "1.9.24" 11 | intellij-platform = "2.2.1" 12 | changelog = "2.2.0" 13 | shadow = "8.1.1" 14 | 15 | [libraries] 16 | asm = { group = "org.ow2.asm", name = "asm", version.ref = "asm" } 17 | asm-analysis = { group = "org.ow2.asm", name = "asm-analysis", version.ref = "asm" } 18 | asm-util = { group = "org.ow2.asm", name = "asm-util", version.ref = "asm" } 19 | asm-commons = { group = "org.ow2.asm", name = "asm-commons", version.ref = "asm" } 20 | 21 | commons-text = { group = "org.apache.commons", name = "commons-text", version.ref = "commons-text" } 22 | 23 | assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" } 24 | 25 | junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit5" } 26 | junit-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit5" } 27 | junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit5" } 28 | junit-vintage-engine = { group = "org.junit.vintage", name = "junit-vintage-engine", version.ref = "junit5" } 29 | junit4 = { group = "junit", name = "junit", version.ref = "junit4" } 30 | 31 | groovy = { group = "org.codehaus.groovy", name = "groovy", version.ref = "groovy" } 32 | kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } 33 | commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version.ref = "commons-lang3" } 34 | 35 | [bundles] 36 | asm = ["asm", "asm-analysis", "asm-util", "asm-commons"] 37 | junit-implementation = ["junit-jupiter-api", "junit-jupiter-params", "junit4"] 38 | junit-runtime = ["junit-jupiter-engine", "junit-vintage-engine"] 39 | 40 | [plugins] 41 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 42 | intellij-platform = { id = "org.jetbrains.intellij.platform", version.ref = "intellij-platform" } 43 | changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } 44 | shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/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.12-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /screenshots/access-converter-tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/access-converter-tool.png -------------------------------------------------------------------------------- /screenshots/asmified-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/asmified-view.png -------------------------------------------------------------------------------- /screenshots/constantpool-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/constantpool-view.png -------------------------------------------------------------------------------- /screenshots/decompiled-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/decompiled-view.png -------------------------------------------------------------------------------- /screenshots/instructions-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/instructions-overview.png -------------------------------------------------------------------------------- /screenshots/open-files-from-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/open-files-from-editor.png -------------------------------------------------------------------------------- /screenshots/open-files-from-tool-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/open-files-from-tool-window.png -------------------------------------------------------------------------------- /screenshots/plain-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/plain-view.png -------------------------------------------------------------------------------- /screenshots/show-method-frames-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/show-method-frames-action.png -------------------------------------------------------------------------------- /screenshots/signature-parser-tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/signature-parser-tool.png -------------------------------------------------------------------------------- /screenshots/structure-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/structure-view.png -------------------------------------------------------------------------------- /screenshots/validate-byte-code-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelkliemannel/intellij-byte-code-plugin/24a1ec58ad9bcb54a692cebe1aed64131d96fb74/screenshots/validate-byte-code-action.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "intellij-byte-code-plugin" 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/_ui/ByteCodePluginIcons.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin._ui 2 | 3 | import com.intellij.openapi.util.IconLoader 4 | 5 | internal object ByteCodePluginIcons { 6 | // -- Properties -------------------------------------------------------------------------------------------------- // 7 | 8 | @Suppress("unused") // Referenced in plugin.xml 9 | @JvmField 10 | val TOOL_WINDOW_ICON = IconLoader.getIcon("/icons/toolwindow.svg", ByteCodePluginIcons::class.java) 11 | 12 | val ACTION_ICON = IconLoader.getIcon("/icons/action.svg", ByteCodePluginIcons::class.java) 13 | 14 | val VERIFY_ICON = IconLoader.getIcon("/icons/verify.svg", ByteCodePluginIcons::class.java) 15 | 16 | // -- Initialization ---------------------------------------------------------------------------------------------- // 17 | // -- Exported Methods -------------------------------------------------------------------------------------------- // 18 | // -- Private Methods --------------------------------------------------------------------------------------------- // 19 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/_ui/CopyValueAction.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin._ui 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.ide.CopyPasteManager 6 | import com.intellij.openapi.project.DumbAwareAction 7 | import com.intellij.util.PlatformIcons 8 | import dev.turingcomplete.intellijbytecodeplugin.common.CommonDataKeys 9 | import dev.turingcomplete.intellijbytecodeplugin.common._internal.DataProviderUtils 10 | import java.awt.datatransfer.StringSelection 11 | 12 | class CopyValueAction : DumbAwareAction("Copy Value", null, PlatformIcons.COPY_ICON) { 13 | // -- Companion Object -------------------------------------------------------------------------------------------- // 14 | // -- Properties -------------------------------------------------------------------------------------------------- // 15 | // -- Initialization ---------------------------------------------------------------------------------------------- // 16 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 17 | 18 | override fun update(e: AnActionEvent) { 19 | e.presentation.isVisible = CommonDataKeys.VALUE.getData(e.dataContext) != null 20 | } 21 | 22 | override fun actionPerformed(e: AnActionEvent) { 23 | val value = DataProviderUtils.getData(CommonDataKeys.VALUE, e.dataContext) 24 | CopyPasteManager.getInstance().setContents(StringSelection(value)) 25 | } 26 | 27 | override fun getActionUpdateThread() = ActionUpdateThread.EDT 28 | 29 | // -- Private Methods --------------------------------------------------------------------------------------------- // 30 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/_ui/DefaultClassFileContext.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin._ui 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.vfs.VirtualFile 5 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFile 6 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFileContext 7 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.ClassReader 8 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.Opcodes 9 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.tree.ClassNode 10 | 11 | internal class DefaultClassFileContext( 12 | private val project: Project, 13 | private val classFile: ClassFile, 14 | private val workAsync: Boolean 15 | ) : ClassFileContext { 16 | 17 | // -- Companion Object -------------------------------------------------------------------------------------------- // 18 | 19 | companion object { 20 | const val ASM_API: Int = Opcodes.ASM9 21 | } 22 | 23 | // -- Properties -------------------------------------------------------------------------------------------------- // 24 | 25 | private val classReader: ClassReader = ClassReader(classFile.file.inputStream) 26 | private val classNode: ClassNode = readClassNode() 27 | private val relatedClassFiles: List = findRelatedClassFiles() 28 | 29 | // -- Initialization ---------------------------------------------------------------------------------------------- // 30 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 31 | 32 | override fun workAsync(): Boolean = workAsync 33 | 34 | override fun classFile(): ClassFile = classFile 35 | 36 | override fun project(): Project = project 37 | 38 | override fun classNode(): ClassNode = classNode 39 | 40 | override fun classReader(): ClassReader = classReader 41 | 42 | override fun relatedClassFiles(): List = relatedClassFiles 43 | 44 | // -- Private Methods --------------------------------------------------------------------------------------------- // 45 | 46 | private fun readClassNode() = ClassNode(ASM_API).apply { 47 | classReader.accept(this, ClassReader.EXPAND_FRAMES) 48 | } 49 | 50 | private fun findRelatedClassFiles(): List { 51 | val parentDirectory = classFile.file.parent 52 | if (!parentDirectory.isDirectory) { 53 | return emptyList() 54 | } 55 | 56 | val rootClassFileName = classFile.file.nameWithoutExtension.takeWhile { it != '$' } 57 | val rootClassFileNamePrefix = "$rootClassFileName$" 58 | return parentDirectory.children 59 | .filter { it.extension == "class" && (it.name == "$rootClassFileName.class" || it.name.startsWith(rootClassFileNamePrefix)) } 60 | } 61 | 62 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 63 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/_ui/NotificationUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin._ui 2 | 3 | import com.intellij.notification.NotificationGroupManager 4 | import com.intellij.notification.NotificationType 5 | import com.intellij.openapi.application.ApplicationManager 6 | import com.intellij.openapi.diagnostic.Logger 7 | import com.intellij.openapi.project.Project 8 | 9 | internal object NotificationUtils { 10 | // -- Properties -------------------------------------------------------------------------------------------------- // 11 | 12 | private val logger = Logger.getInstance(NotificationUtils::class.java) 13 | private const val notificationGroupId = "dev.turingcomplete.intellijbytecodeplugin.notificationGroup" 14 | 15 | // -- Initialization ---------------------------------------------------------------------------------------------- // 16 | // -- Exported Methods -------------------------------------------------------------------------------------------- // 17 | 18 | fun notifyInternalError(title: String, message: String, e: Throwable? = null, project: Project? = null) { 19 | logger.warn(message, e) 20 | 21 | ApplicationManager.getApplication().invokeLater { 22 | NotificationGroupManager.getInstance() 23 | .getNotificationGroup(notificationGroupId) 24 | .createNotification(title, "$message\nSee idea.log for more details.", NotificationType.ERROR) 25 | .notify(project) 26 | } 27 | } 28 | 29 | // -- Private Methods --------------------------------------------------------------------------------------------- // 30 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/_ui/SimpleListCellRenderer.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin._ui 2 | 3 | import com.intellij.util.ui.JBUI 4 | import com.intellij.util.ui.UIUtil 5 | import java.awt.Component 6 | import javax.swing.DefaultListCellRenderer 7 | import javax.swing.JList 8 | 9 | class SimpleListCellRenderer(private val textTransformer: (Any?) -> String): DefaultListCellRenderer() { 10 | // -- Properties -------------------------------------------------------------------------------------------------- // 11 | // -- Initialization ---------------------------------------------------------------------------------------------- // 12 | // -- Exported Methods -------------------------------------------------------------------------------------------- // 13 | 14 | override fun getListCellRendererComponent(list: JList<*>?, value: Any?, index: Int, selected: Boolean, focused: Boolean): Component { 15 | super.getListCellRendererComponent(list, value, index, selected, false) 16 | 17 | text = textTransformer(value) 18 | 19 | border = JBUI.Borders.empty(0, 5, 0, 10) 20 | if (!selected) { 21 | background = UIUtil.getLabelBackground() 22 | } 23 | 24 | return this 25 | } 26 | 27 | // -- Private Methods --------------------------------------------------------------------------------------------- // 28 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 29 | // -- Companion Object -------------------------------------------------------------------------------------------- // 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/_ui/ToggleActionButton.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin._ui 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.project.DumbAwareAction 6 | import com.intellij.util.PlatformIcons 7 | import com.intellij.util.ui.EmptyIcon 8 | 9 | internal class ToggleActionButton( 10 | title: String, 11 | private val setValue: () -> Unit, 12 | private val isSelected: () -> Boolean 13 | ) : DumbAwareAction(title) { 14 | 15 | // -- Properties -------------------------------------------------------------------------------------------------- // 16 | // -- Initialization ---------------------------------------------------------------------------------------------- // 17 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 18 | 19 | override fun update(e: AnActionEvent) { 20 | e.presentation.icon = if (isSelected()) PlatformIcons.CHECK_ICON else UNCHECKED_ICON 21 | } 22 | 23 | override fun actionPerformed(e: AnActionEvent) { 24 | setValue() 25 | } 26 | 27 | override fun getActionUpdateThread() = ActionUpdateThread.EDT 28 | 29 | // -- Private Methods --------------------------------------------------------------------------------------------- // 30 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 31 | // -- Companion Object -------------------------------------------------------------------------------------------- // 32 | 33 | companion object { 34 | 35 | private val UNCHECKED_ICON = EmptyIcon.create(PlatformIcons.CHECK_ICON) 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/_ui/UiExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin._ui 2 | 3 | import com.intellij.ui.components.JBLabel 4 | import com.intellij.ui.render.RenderingUtil 5 | import com.intellij.util.ui.GridBag 6 | import com.intellij.util.ui.JBFont 7 | import com.intellij.util.ui.JBUI 8 | import java.awt.Color 9 | import java.awt.Dimension 10 | import java.awt.Font 11 | import java.awt.GridBagConstraints 12 | import javax.swing.JComponent 13 | import javax.swing.JTable 14 | import javax.swing.JTree 15 | import javax.swing.table.TableCellRenderer 16 | 17 | // -- Properties ---------------------------------------------------------------------------------------------------- // 18 | // -- Exposed Methods ----------------------------------------------------------------------------------------------- // 19 | 20 | fun JBFont.toMonospace(): JBFont = JBFont.create(Font(Font.MONOSPACED, this.style, this.size)) 21 | 22 | fun GridBag.withCommonsDefaults() = this 23 | .setDefaultAnchor(GridBagConstraints.NORTHWEST) 24 | .setDefaultInsets(0, 0, 0, 0) 25 | .setDefaultFill(GridBagConstraints.NONE) 26 | 27 | fun JComponent.configureForCell(tree: JTree, selected: Boolean, hasFocus: Boolean): JComponent { 28 | val background = RenderingUtil.getBackground(tree, selected) 29 | val backgroundToUse: Color = when { 30 | selected -> background 31 | hasFocus -> RenderingUtil.getHoverBackground(tree) ?: background 32 | else -> background 33 | } 34 | return configureForCell(tree, RenderingUtil.getForeground(tree, selected), backgroundToUse) 35 | } 36 | 37 | fun JComponent.configureForCell(table: JTable, selected: Boolean, hasFocus: Boolean): JComponent { 38 | val background = RenderingUtil.getBackground(table, selected) 39 | val backgroundToUse: Color = when { 40 | selected -> background 41 | hasFocus -> RenderingUtil.getHoverBackground(table) ?: background 42 | else -> background 43 | } 44 | return configureForCell(table, RenderingUtil.getForeground(table, selected), backgroundToUse) 45 | } 46 | 47 | fun JComponent.configureForCell(cellContainer: JComponent, foreground: Color, background: Color): JComponent { 48 | this.foreground = foreground 49 | this.background = background 50 | componentOrientation = cellContainer.componentOrientation 51 | font = cellContainer.font 52 | isEnabled = cellContainer.isEnabled 53 | border = JBUI.Borders.empty(2, 3) 54 | return this 55 | } 56 | 57 | fun GridBag.overrideLeftInset(leftInset: Int): GridBag { 58 | this.insets(this.insets.top, leftInset, this.insets.bottom, this.insets.right) 59 | return this 60 | } 61 | 62 | fun GridBag.overrideRightInset(rightInset: Int): GridBag { 63 | this.insets(this.insets.top, this.insets.left, this.insets.bottom, rightInset) 64 | return this 65 | } 66 | 67 | fun GridBag.overrideBottomInset(bottomInset: Int): GridBag { 68 | this.insets(this.insets.top, this.insets.left, bottomInset, this.insets.right) 69 | return this 70 | } 71 | 72 | fun GridBag.overrideTopInset(topInset: Int): GridBag { 73 | this.insets(topInset, this.insets.left, this.insets.bottom, this.insets.right) 74 | return this 75 | } 76 | 77 | fun JBLabel.copyable(): JBLabel { 78 | setCopyable(true) 79 | return this 80 | } 81 | 82 | fun JTable.getMaxRowWith(column: Int): Int { 83 | var width = 0 84 | 85 | for (row in 0 until rowCount) { 86 | val renderer: TableCellRenderer = getCellRenderer(row, column) 87 | val comp = prepareRenderer(renderer, row, column) 88 | width = (comp.preferredSize.width + 1).coerceAtLeast(width) 89 | } 90 | 91 | return width 92 | } 93 | 94 | fun JComponent.widthStrictWidth(width: Int): JComponent { 95 | this.maximumSize = Dimension(width, this.maximumSize.height) 96 | this.preferredSize = Dimension(width, this.preferredSize.height) 97 | this.minimumSize = Dimension(width, this.minimumSize.height) 98 | return this 99 | } 100 | 101 | // -- Private Methods ----------------------------------------------------------------------------------------------- // 102 | // -- Type ---------------------------------------------------------------------------------------------------------- // 103 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/_ui/ViewValueAction.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin._ui 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.project.DumbAwareAction 6 | import dev.turingcomplete.intellijbytecodeplugin.common.CommonDataKeys 7 | import dev.turingcomplete.intellijbytecodeplugin.common._internal.DataProviderUtils 8 | 9 | class ViewValueAction : DumbAwareAction("View Value") { 10 | // -- Companion Object -------------------------------------------------------------------------------------------- // 11 | // -- Properties -------------------------------------------------------------------------------------------------- // 12 | // -- Initialization ---------------------------------------------------------------------------------------------- // 13 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 14 | 15 | override fun update(e: AnActionEvent) { 16 | e.presentation.isVisible = CommonDataKeys.VALUE.getData(e.dataContext) != null 17 | } 18 | 19 | override fun actionPerformed(e: AnActionEvent) { 20 | val value = DataProviderUtils.getData(CommonDataKeys.VALUE, e.dataContext) 21 | UiUtils.PopUp.showTextAreaPopup(value, e.dataContext) 22 | } 23 | 24 | override fun getActionUpdateThread() = ActionUpdateThread.EDT 25 | 26 | // -- Private Methods --------------------------------------------------------------------------------------------- // 27 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/AccessUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode 2 | 3 | import com.jetbrains.rd.util.EnumSet 4 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.Access.* 5 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.Opcodes.* 6 | import java.util.* 7 | 8 | enum class Access(val value: Int) { 9 | PUBLIC(ACC_PUBLIC), 10 | PRIVATE(ACC_PRIVATE), 11 | PROTECTED(ACC_PROTECTED), 12 | STATIC(ACC_STATIC), 13 | FINAL(ACC_FINAL), 14 | SUPER(ACC_SUPER), 15 | SYNCHRONIZED(ACC_SYNCHRONIZED), 16 | OPEN(ACC_OPEN), 17 | TRANSITIVE(ACC_TRANSITIVE), 18 | VOLATILE(ACC_VOLATILE), 19 | BRIDGE(ACC_BRIDGE), 20 | STATIC_PHASE(ACC_STATIC_PHASE), 21 | VARARGS(ACC_VARARGS), 22 | TRANSIENT(ACC_TRANSIENT), 23 | NATIVE(ACC_NATIVE), 24 | INTERFACE(ACC_INTERFACE), 25 | ABSTRACT(ACC_ABSTRACT), 26 | STRICT(ACC_STRICT), 27 | SYNTHETIC(ACC_SYNTHETIC), 28 | ANNOTATION(ACC_ANNOTATION), 29 | ENUM(ACC_ENUM), 30 | MANDATED(ACC_MANDATED), 31 | MODULE(ACC_MODULE), 32 | RECORD(ACC_RECORD), 33 | DEPRECATED(ACC_DEPRECATED); 34 | 35 | fun check(access: Int): Boolean = (value and access) != 0 36 | } 37 | 38 | enum class AccessGroup(val accesses: EnumSet) { 39 | CLASS(EnumSet.of(PUBLIC, PRIVATE, PROTECTED, FINAL, SUPER, INTERFACE, ABSTRACT, SYNTHETIC, ANNOTATION, RECORD, DEPRECATED, ENUM, Access.MODULE)), 40 | FIELD(EnumSet.of(PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, VOLATILE, TRANSIENT, SYNTHETIC, DEPRECATED, ENUM)), 41 | METHOD(EnumSet.of(PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, SYNCHRONIZED, BRIDGE, VARARGS, NATIVE, ABSTRACT, STRICT, SYNTHETIC, DEPRECATED)), 42 | PARAMETER(EnumSet.of(FINAL, SYNTHETIC, MANDATED)), 43 | MODULE(EnumSet.of(OPEN, SYNTHETIC, MANDATED)), 44 | MODULE_REQUIRES(EnumSet.of(TRANSITIVE, STATIC_PHASE, SYNTHETIC, MANDATED)), 45 | MODULE_EXPORTS(EnumSet.of(SYNTHETIC, MANDATED)), 46 | MODULE_OPENS(EnumSet.of(SYNTHETIC, MANDATED)); 47 | 48 | fun toReadableAccess(access: Int): List { 49 | return accesses.asSequence() 50 | .filter { it.check(access) } 51 | .map { it.name.lowercase(Locale.ENGLISH) } 52 | .sorted() 53 | .toList() 54 | } 55 | 56 | override fun toString() = name.lowercase(Locale.getDefault()).replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }.replace("_", " ") 57 | } 58 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/ClassVersionUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode 2 | 3 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.Opcodes 4 | 5 | object ClassVersionUtils { 6 | // -- Properties -------------------------------------------------------------------------------------------------- // 7 | 8 | val CLASS_VERSIONS = arrayOf( 9 | ClassVersion(Opcodes.V24.toByte(), "Java SE 24"), 10 | ClassVersion(Opcodes.V23.toByte(), "Java SE 23"), 11 | ClassVersion(Opcodes.V22.toByte(), "Java SE 22"), 12 | ClassVersion(Opcodes.V21.toByte(), "Java SE 21"), 13 | ClassVersion(Opcodes.V20.toByte(), "Java SE 20"), 14 | ClassVersion(Opcodes.V19.toByte(), "Java SE 19"), 15 | ClassVersion(Opcodes.V18.toByte(), "Java SE 18"), 16 | ClassVersion(Opcodes.V17.toByte(), "Java SE 17"), 17 | ClassVersion(Opcodes.V16.toByte(), "Java SE 16"), 18 | ClassVersion(Opcodes.V15.toByte(), "Java SE 15"), 19 | ClassVersion(Opcodes.V14.toByte(), "Java SE 14"), 20 | ClassVersion(Opcodes.V13.toByte(), "Java SE 13"), 21 | ClassVersion(Opcodes.V12.toByte(), "Java SE 12"), 22 | ClassVersion(Opcodes.V11.toByte(), "Java SE 11"), 23 | ClassVersion(Opcodes.V10.toByte(), "Java SE 10"), 24 | ClassVersion(Opcodes.V9.toByte(), "Java SE 9"), 25 | ClassVersion(Opcodes.V1_8.toByte(), "Java SE 8"), 26 | ClassVersion(Opcodes.V1_7.toByte(), "Java SE 7"), 27 | ClassVersion(Opcodes.V1_6.toByte(), "Java SE 6.0"), 28 | ClassVersion(Opcodes.V1_5.toByte(), "Java SE 5.0"), 29 | ClassVersion(Opcodes.V1_4.toByte(), "JDK 1.4"), 30 | ClassVersion(Opcodes.V1_3.toByte(), "JDK 1.3"), 31 | ClassVersion(Opcodes.V1_2.toByte(), "JDK 1.2"), 32 | ClassVersion(Opcodes.V1_1.toByte(), "JDK 1.1") 33 | ) 34 | 35 | val MAJOR_TO_CLASS_VERSION: Map = CLASS_VERSIONS.associateBy { it.major } 36 | 37 | // -- Initialization ---------------------------------------------------------------------------------------------- // 38 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 39 | 40 | fun toClassVersion(asmClassVersion: Int): ClassVersion? { 41 | val (major, minor) = parseMajorMinor(asmClassVersion) 42 | return if (minor == 0.toByte()) MAJOR_TO_CLASS_VERSION[major] else null 43 | } 44 | 45 | fun toMajorMinorString(asmClassVersion: Int): String { 46 | val (major, minor) = parseMajorMinor(asmClassVersion) 47 | return major.toString() + (if (minor != 0.toByte()) ".$minor" else "") 48 | } 49 | 50 | // -- Private Methods --------------------------------------------------------------------------------------------- // 51 | 52 | private fun parseMajorMinor(asmClassVersion: Int): Pair { 53 | val major = (asmClassVersion and 0xFF).toByte() 54 | val minor = (asmClassVersion shr 8 and 0xFF).toByte() 55 | return Pair(major, minor) 56 | } 57 | 58 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 59 | 60 | data class ClassVersion(val major: Byte, val specification: String) { 61 | 62 | override fun toString() = "$major ($specification)" 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/MethodDeclarationUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode 2 | 3 | import com.intellij.openapi.util.text.StringUtil 4 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.Type 5 | 6 | object MethodDeclarationUtils { 7 | // -- Properties -------------------------------------------------------------------------------------------------- // 8 | // -- Initialization ---------------------------------------------------------------------------------------------- // 9 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 10 | 11 | fun toReadableDeclaration(name: String, 12 | descriptor: String, 13 | ownerInternalName: String, 14 | typeNameRenderMode: TypeUtils.TypeNameRenderMode, 15 | methodDescriptorRenderMode: MethodDescriptorRenderMode, 16 | formatWithHtml: Boolean): String { 17 | 18 | return when (methodDescriptorRenderMode) { 19 | MethodDescriptorRenderMode.DESCRIPTOR -> "${StringUtil.escapeXmlEntities(name)}${descriptor}" 20 | MethodDescriptorRenderMode.DECLARATION -> when (name) { 21 | "" -> if (formatWithHtml) "Static Initializer" else "Static Initializer" 22 | "" -> { 23 | if (formatWithHtml) { 24 | "${TypeUtils.toReadableName(ownerInternalName, TypeUtils.TypeNameRenderMode.SIMPLE)}" + 25 | "(${toReadableParameters(Type.getMethodType(descriptor), typeNameRenderMode)})" 26 | } 27 | else { 28 | TypeUtils.toReadableName(ownerInternalName, TypeUtils.TypeNameRenderMode.SIMPLE) + 29 | "(${toReadableParameters(Type.getMethodType(descriptor), typeNameRenderMode)})" 30 | } 31 | } 32 | else -> { 33 | val methodType = Type.getMethodType(descriptor) 34 | if (formatWithHtml) { 35 | "${TypeUtils.toReadableType(methodType.returnType, typeNameRenderMode)} " + 36 | "$name(${toReadableParameters(methodType, typeNameRenderMode)})" 37 | } 38 | else { 39 | "${TypeUtils.toReadableType(methodType.returnType, typeNameRenderMode)} " + 40 | "$name(${toReadableParameters(methodType, typeNameRenderMode)})" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | // -- Private Methods --------------------------------------------------------------------------------------------- // 48 | 49 | private fun toReadableParameters(methodType: Type, typeNameRenderMode: TypeUtils.TypeNameRenderMode): String { 50 | return methodType.argumentTypes.joinToString(", ") { TypeUtils.toReadableType(it, typeNameRenderMode) } 51 | } 52 | 53 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 54 | 55 | enum class MethodDescriptorRenderMode(val title: String) { 56 | DESCRIPTOR("Method Descriptor"), 57 | DECLARATION("Method Declaration") 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/TraceUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode 2 | 3 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.ClassReader 4 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.util.Printer 5 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.util.TraceClassVisitor 6 | import java.io.PrintWriter 7 | import java.io.StringWriter 8 | 9 | object TraceUtils { 10 | // -- Properties -------------------------------------------------------------------------------------------------- // 11 | // -- Initialization ---------------------------------------------------------------------------------------------- // 12 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 13 | 14 | fun traceVisit(classReader: ClassReader, parsingOptions: Int, printer: Printer): String { 15 | val traceWriter = StringWriter() 16 | val printWriter = PrintWriter(traceWriter) 17 | val traceClassVisitor = TraceClassVisitor(null, printer, printWriter) 18 | classReader.accept(traceClassVisitor, parsingOptions) 19 | return traceWriter.toString() 20 | } 21 | 22 | // -- Private Methods --------------------------------------------------------------------------------------------- // 23 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/TypeUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode 2 | 3 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.Type 4 | 5 | object TypeUtils { 6 | // -- Properties -------------------------------------------------------------------------------------------------- // 7 | // -- Initialization ---------------------------------------------------------------------------------------------- // 8 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 9 | 10 | fun toReadableName(internalName: String, renderMode: TypeNameRenderMode): String { 11 | return when(renderMode) { 12 | TypeNameRenderMode.INTERNAL -> internalName 13 | TypeNameRenderMode.QUALIFIED -> internalName.replace("/", ".") 14 | TypeNameRenderMode.SIMPLE -> { 15 | val lastSlashIndex = internalName.lastIndexOf("/") 16 | return if (lastSlashIndex > 0) internalName.substring(lastSlashIndex + 1) else internalName 17 | } 18 | } 19 | } 20 | 21 | fun toReadableType(typeDescriptor: String, renderMode: TypeNameRenderMode): String { 22 | return toReadableType(Type.getType(typeDescriptor), renderMode) 23 | } 24 | 25 | fun toReadableType(type: Type, renderMode: TypeNameRenderMode): String { 26 | return when (type.sort) { 27 | Type.VOID -> "void" 28 | Type.BOOLEAN -> "boolean" 29 | Type.CHAR -> "char" 30 | Type.BYTE -> "byte" 31 | Type.SHORT -> "short" 32 | Type.INT -> "int" 33 | Type.FLOAT -> "float" 34 | Type.LONG -> "long" 35 | Type.DOUBLE -> "double" 36 | Type.ARRAY -> "${toReadableType(type.elementType, renderMode)}${"[]".repeat(type.dimensions)}" 37 | else -> toReadableName(type.internalName, renderMode) 38 | } 39 | } 40 | 41 | // -- Private Methods --------------------------------------------------------------------------------------------- // 42 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 43 | 44 | enum class TypeNameRenderMode(val title: String) { 45 | INTERNAL("Internal Name"), 46 | QUALIFIED("Qualified Class Name"), 47 | SIMPLE("Simple Class Name") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/ClassInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class ClassInfo(dataInputStream: DataInputStream) : ConstantPoolInfo("Class", readValues(dataInputStream)) { 6 | // -- Companion Object -------------------------------------------------------------------------------------------- // 7 | 8 | companion object { 9 | fun readValues(dataInputStream: DataInputStream): List { 10 | return listOf(ResolvableIndexValue("name", dataInputStream.readUnsignedShort())) 11 | } 12 | } 13 | 14 | // -- Properties -------------------------------------------------------------------------------------------------- // 15 | // -- Initialization ---------------------------------------------------------------------------------------------- // 16 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 17 | // -- Private Methods --------------------------------------------------------------------------------------------- // 18 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/ConstantPool.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFile 4 | import java.io.DataInputStream 5 | 6 | internal class ConstantPool(val entries: List) { 7 | // -- Companion Object -------------------------------------------------------------------------------------------- // 8 | 9 | companion object { 10 | private val MAGIC_BYTES: UInt = 3405691582.toUInt() 11 | 12 | private val TAG_INFO_CREATORS = mapOf ConstantPoolInfo>( 13 | Pair(1) { dataInputStream -> Utf8Info(dataInputStream) }, 14 | Pair(3) { dataInputStream -> IntegerInfo(dataInputStream) }, 15 | Pair(4) { dataInputStream -> FloatInfo(dataInputStream) }, 16 | Pair(5) { dataInputStream -> LongInfo(dataInputStream) }, 17 | Pair(6) { dataInputStream -> DoubleInfo(dataInputStream) }, 18 | Pair(7) { dataInputStream -> ClassInfo(dataInputStream) }, 19 | Pair(8) { dataInputStream -> StringInfo(dataInputStream) }, 20 | Pair(9) { dataInputStream -> RefInfo.Field(dataInputStream) }, 21 | Pair(10) { dataInputStream -> RefInfo.Method(dataInputStream) }, 22 | Pair(11) { dataInputStream -> RefInfo.InterfaceMethod(dataInputStream) }, 23 | Pair(12) { dataInputStream -> NameAndTypeInfo(dataInputStream) }, 24 | Pair(15) { dataInputStream -> MethodHandleInfo(dataInputStream) }, 25 | Pair(16) { dataInputStream -> MethodTypeInfo(dataInputStream) }, 26 | Pair(17) { dataInputStream -> DynamicInfo.Simple(dataInputStream) }, 27 | Pair(18) { dataInputStream -> DynamicInfo.Invoke(dataInputStream) }, 28 | Pair(19) { dataInputStream -> ModuleInfo(dataInputStream) }, 29 | Pair(20) { dataInputStream -> PackageInfo(dataInputStream) }) 30 | 31 | fun create(classFile: ClassFile): ConstantPool { 32 | DataInputStream(classFile.file.inputStream).use { dataInputStream -> 33 | // Validate magic bytes 34 | val magicBytes = dataInputStream.readInt().toUInt() 35 | if (magicBytes != MAGIC_BYTES) { 36 | throw IllegalArgumentException("Missing expected magic bytes.") 37 | } 38 | 39 | // Read major and minor version 40 | dataInputStream.readInt() 41 | 42 | // Read entries 43 | val entries = mutableListOf() 44 | val constantPoolCount = dataInputStream.readUnsignedShort() 45 | var index = 1 46 | while (index <= constantPoolCount - 1) { 47 | val tag = dataInputStream.readUnsignedByte() 48 | val constantPoolInfo = TAG_INFO_CREATORS[tag]?.invoke(dataInputStream) 49 | ?: throw IllegalStateException("Unknown constant pool entry tag: $tag (already read $index out of ${constantPoolCount - 1})") 50 | entries.add(constantPoolInfo) 51 | val usedConstantPoolIndices = constantPoolInfo.usedConstantPoolIndices 52 | if (usedConstantPoolIndices > 1) { 53 | for (ignore in 1 until usedConstantPoolIndices) { 54 | entries.add(UnusableInfo()) 55 | } 56 | } 57 | index += usedConstantPoolIndices 58 | } 59 | return ConstantPool(entries) 60 | } 61 | } 62 | } 63 | 64 | // -- Properties -------------------------------------------------------------------------------------------------- // 65 | // -- Initialization ---------------------------------------------------------------------------------------------- // 66 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 67 | 68 | /** 69 | * The constant pool starts at index one, but the underling structure used in 70 | * this class at 0. To correctly resolve referenced indices from entries this 71 | * method must be used. 72 | * 73 | * If its required to work on the 0-indexed structure, the property [entries] 74 | * is to be used. 75 | */ 76 | fun getReference(index: Int): ConstantPoolInfo { 77 | return entries.getOrNull(index - 1) 78 | ?: throw IllegalArgumentException("Unknown constant pool entry index: $index") 79 | } 80 | 81 | // -- Private Methods --------------------------------------------------------------------------------------------- // 82 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 83 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/ConstantPoolInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | internal abstract class ConstantPoolInfo(val type: String, 4 | private val values: List, 5 | val usedConstantPoolIndices: Int = 1) { 6 | // -- Companion Object -------------------------------------------------------------------------------------------- // 7 | // -- Properties -------------------------------------------------------------------------------------------------- // 8 | 9 | val unresolvedDisplayText : String by lazy { 10 | values.joinToString(", ") { it.createUnresolvedDisplayText() } 11 | } 12 | private var resolvedDisplayText : String? = null 13 | 14 | val goToIndices : List by lazy { values.mapNotNull { it.goToIndex() }.toList() } 15 | 16 | // -- Initialization ---------------------------------------------------------------------------------------------- // 17 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 18 | 19 | fun resolvedDisplayText(constantPool: ConstantPool): String { 20 | if (resolvedDisplayText == null) { 21 | resolvedDisplayText = values.joinToString(", ") { it.createResolvedDisplayText(constantPool) } 22 | } 23 | return resolvedDisplayText!! 24 | } 25 | 26 | // -- Private Methods --------------------------------------------------------------------------------------------- // 27 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 28 | 29 | internal abstract class Value { 30 | 31 | abstract fun createUnresolvedDisplayText(): String 32 | 33 | open fun createResolvedDisplayText(constantPool: ConstantPool) = createUnresolvedDisplayText() 34 | 35 | open fun goToIndex() : Int? = null 36 | } 37 | 38 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 39 | 40 | internal open class PlainValue(private val name: String? = null, private val value: String) : Value() { 41 | 42 | override fun createUnresolvedDisplayText(): String { 43 | return "${if (name != null) "$name=" else ""}$value" 44 | } 45 | } 46 | 47 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 48 | 49 | internal open class ResolvableIndexValue(private val name: String, private val index: Int) : Value() { 50 | 51 | override fun createUnresolvedDisplayText(): String = "${name}_index=$index" 52 | 53 | override fun createResolvedDisplayText(constantPool: ConstantPool): String { 54 | val resolvedIndexValue = constantPool.getReference(index).resolvedDisplayText(constantPool) 55 | return "$name=$resolvedIndexValue" 56 | } 57 | 58 | override fun goToIndex(): Int? = index 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/ConstantPoolUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | import java.nio.ByteBuffer 5 | 6 | internal object ConstantPoolUtils { 7 | // -- Properties -------------------------------------------------------------------------------------------------- // 8 | // -- Initialization ---------------------------------------------------------------------------------------------- // 9 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 10 | 11 | fun readLong(dataInputStream: DataInputStream): Long { 12 | return readTwoInts(dataInputStream, java.lang.Long.BYTES).getLong(0) 13 | } 14 | 15 | fun readDouble(dataInputStream: DataInputStream): Double { 16 | return readTwoInts(dataInputStream, java.lang.Double.BYTES).getDouble(0) 17 | } 18 | 19 | // -- Private Methods --------------------------------------------------------------------------------------------- // 20 | 21 | private fun readTwoInts(dataInputStream: DataInputStream, capacity: Int) : ByteBuffer { 22 | return ByteBuffer.allocate(capacity).run { 23 | putInt(dataInputStream.readInt()) 24 | putInt(dataInputStream.readInt()) 25 | flip() 26 | } 27 | } 28 | 29 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/DoubleInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class DoubleInfo(dataInputStream: DataInputStream) : ConstantPoolInfo("double", readValues(dataInputStream), 2) { 6 | // -- Companion Object -------------------------------------------------------------------------------------------- // 7 | 8 | companion object { 9 | fun readValues(dataInputStream: DataInputStream): List { 10 | return listOf(PlainValue(value = ConstantPoolUtils.readDouble(dataInputStream).toString())) 11 | } 12 | } 13 | 14 | // -- Properties -------------------------------------------------------------------------------------------------- // 15 | // -- Initialization ---------------------------------------------------------------------------------------------- // 16 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 17 | // -- Private Methods --------------------------------------------------------------------------------------------- // 18 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/DynamicInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal abstract class DynamicInfo(dataInputStream: DataInputStream, type: String) 6 | : ConstantPoolInfo(type, readValues(dataInputStream)) { 7 | 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | 10 | companion object { 11 | fun readValues(dataInputStream: DataInputStream): List { 12 | // The bootstrapMethodAttrIndex is NOT a reference into the constant pool. 13 | return listOf(PlainValue("bootstrap_method_attr_index", dataInputStream.readUnsignedShort().toString()), 14 | ResolvableIndexValue("name_and_type", dataInputStream.readUnsignedShort())) 15 | } 16 | } 17 | 18 | // -- Properties -------------------------------------------------------------------------------------------------- // 19 | // -- Initialization ---------------------------------------------------------------------------------------------- // 20 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 21 | // -- Private Methods --------------------------------------------------------------------------------------------- // 22 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 23 | 24 | class Simple(dataInputStream: DataInputStream) : DynamicInfo(dataInputStream, "Dynamic") 25 | 26 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 27 | 28 | class Invoke(dataInputStream: DataInputStream) : DynamicInfo(dataInputStream, "InvokeDynamic") 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/FloatInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class FloatInfo(dataInputStream: DataInputStream) : ConstantPoolInfo("Float", readValues(dataInputStream)) { 6 | // -- Companion Object -------------------------------------------------------------------------------------------- // 7 | 8 | companion object { 9 | fun readValues(dataInputStream: DataInputStream): List { 10 | return listOf(PlainValue(value = dataInputStream.readInt().toFloat().toString())) 11 | } 12 | } 13 | 14 | // -- Properties -------------------------------------------------------------------------------------------------- // 15 | // -- Initialization ---------------------------------------------------------------------------------------------- // 16 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 17 | // -- Private Methods --------------------------------------------------------------------------------------------- // 18 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/IntegerInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class IntegerInfo(dataInputStream: DataInputStream) 6 | : ConstantPoolInfo("Integer", readValues(dataInputStream)) { 7 | 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | 10 | companion object { 11 | fun readValues(dataInputStream: DataInputStream): List { 12 | return listOf(PlainValue(value = dataInputStream.readInt().toString())) 13 | } 14 | } 15 | 16 | // -- Properties -------------------------------------------------------------------------------------------------- // 17 | // -- Initialization ---------------------------------------------------------------------------------------------- // 18 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 19 | // -- Private Methods --------------------------------------------------------------------------------------------- // 20 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/LongInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class LongInfo(dataInputStream: DataInputStream) : ConstantPoolInfo("Long", readValues(dataInputStream), 2) { 6 | // -- Companion Object -------------------------------------------------------------------------------------------- // 7 | 8 | companion object { 9 | fun readValues(dataInputStream: DataInputStream): List { 10 | return listOf(PlainValue(value = ConstantPoolUtils.readLong(dataInputStream).toString())) 11 | } 12 | } 13 | 14 | // -- Properties -------------------------------------------------------------------------------------------------- // 15 | // -- Initialization ---------------------------------------------------------------------------------------------- // 16 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 17 | // -- Private Methods --------------------------------------------------------------------------------------------- // 18 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/MethodHandleInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class MethodHandleInfo(dataInputStream: DataInputStream) 6 | : ConstantPoolInfo("MethodHandle", readValues(dataInputStream)) { 7 | 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | 10 | companion object { 11 | private val REFERENCE_KINDS = mapOf(Pair(1, "REF_getField"), 12 | Pair(2, "REF_getStatic"), 13 | Pair(3, "REF_putField"), 14 | Pair(4, "REF_putStatic"), 15 | Pair(5, "REF_invokeVirtual"), 16 | Pair(6, "REF_invokeStatic"), 17 | Pair(7, "REF_invokeSpecial"), 18 | Pair(8, "REF_newInvokeSpecial"), 19 | Pair(9, "REF_invokeInterface")) 20 | 21 | fun readValues(dataInputStream: DataInputStream): List { 22 | val referenceKind: Int = dataInputStream.readUnsignedByte() 23 | val referenceKindText = REFERENCE_KINDS[referenceKind] 24 | return listOf(PlainValue("reference_kind=", "$referenceKind ($referenceKindText)"), 25 | ResolvableIndexValue("reference", dataInputStream.readUnsignedShort())) 26 | } 27 | } 28 | 29 | // -- Properties -------------------------------------------------------------------------------------------------- // 30 | // -- Initialization ---------------------------------------------------------------------------------------------- // 31 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 32 | // -- Private Methods --------------------------------------------------------------------------------------------- // 33 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 34 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/MethodTypeInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class MethodTypeInfo(dataInputStream: DataInputStream) 6 | : ConstantPoolInfo("MethodType", readValues(dataInputStream)) { 7 | 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | 10 | companion object { 11 | fun readValues(dataInputStream: DataInputStream): List { 12 | return listOf(ResolvableIndexValue("descriptor", dataInputStream.readUnsignedShort())) 13 | } 14 | } 15 | 16 | // -- Properties -------------------------------------------------------------------------------------------------- // 17 | 18 | // -- Initialization ---------------------------------------------------------------------------------------------- // 19 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 20 | // -- Private Methods --------------------------------------------------------------------------------------------- // 21 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/ModuleInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class ModuleInfo(dataInputStream: DataInputStream) 6 | : ConstantPoolInfo("Module_info", readValues(dataInputStream)) { 7 | 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | 10 | companion object { 11 | fun readValues(dataInputStream: DataInputStream): List { 12 | return listOf(ResolvableIndexValue("name", dataInputStream.readUnsignedShort())) 13 | } 14 | } 15 | 16 | // -- Properties -------------------------------------------------------------------------------------------------- // 17 | // -- Initialization ---------------------------------------------------------------------------------------------- // 18 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 19 | // -- Private Methods --------------------------------------------------------------------------------------------- // 20 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/NameAndTypeInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class NameAndTypeInfo(dataInputStream: DataInputStream) 6 | : ConstantPoolInfo("NameAndType", readValues(dataInputStream)) { 7 | 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | 10 | companion object { 11 | fun readValues(dataInputStream: DataInputStream): List { 12 | return listOf(ResolvableIndexValue("name", dataInputStream.readUnsignedShort()), 13 | ResolvableIndexValue("descriptor", dataInputStream.readUnsignedShort())) 14 | } 15 | } 16 | 17 | // -- Properties -------------------------------------------------------------------------------------------------- // 18 | // -- Initialization ---------------------------------------------------------------------------------------------- // 19 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 20 | // -- Private Methods --------------------------------------------------------------------------------------------- // 21 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/PackageInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class PackageInfo(dataInputStream: DataInputStream) 6 | : ConstantPoolInfo("Package_info", readValues(dataInputStream)) { 7 | 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | 10 | companion object { 11 | fun readValues(dataInputStream: DataInputStream): List { 12 | return listOf(ResolvableIndexValue("name", dataInputStream.readUnsignedShort())) 13 | } 14 | } 15 | 16 | // -- Properties -------------------------------------------------------------------------------------------------- // 17 | // -- Initialization ---------------------------------------------------------------------------------------------- // 18 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 19 | // -- Private Methods --------------------------------------------------------------------------------------------- // 20 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/RefInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal abstract class RefInfo(dataInputStream: DataInputStream, type: String) 6 | : ConstantPoolInfo(type, readValues(dataInputStream)) { 7 | 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | 10 | companion object { 11 | fun readValues(dataInputStream: DataInputStream): List { 12 | return listOf(ResolvableIndexValue("class", dataInputStream.readUnsignedShort()), 13 | ResolvableIndexValue("name_and_type", dataInputStream.readUnsignedShort())) 14 | } 15 | } 16 | 17 | // -- Properties -------------------------------------------------------------------------------------------------- // 18 | // -- Initialization ---------------------------------------------------------------------------------------------- // 19 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 20 | // -- Private Methods --------------------------------------------------------------------------------------------- // 21 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 22 | 23 | internal class Field(dataInputStream: DataInputStream) : RefInfo(dataInputStream, "Fieldref") 24 | 25 | internal class Method(dataInputStream: DataInputStream) : RefInfo(dataInputStream, "Methodref") 26 | 27 | internal class InterfaceMethod(dataInputStream: DataInputStream) : RefInfo(dataInputStream, "InterfaceMethodref") 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/StringInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class StringInfo(dataInputStream: DataInputStream) 6 | : ConstantPoolInfo("String", readValues(dataInputStream)) { 7 | 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | 10 | companion object { 11 | fun readValues(dataInputStream: DataInputStream): List { 12 | return listOf(ResolvableIndexValue("string", dataInputStream.readUnsignedShort())) 13 | } 14 | } 15 | 16 | // -- Properties -------------------------------------------------------------------------------------------------- // 17 | // -- Initialization ---------------------------------------------------------------------------------------------- // 18 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 19 | // -- Private Methods --------------------------------------------------------------------------------------------- // 20 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/UnusableInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | internal class UnusableInfo : ConstantPoolInfo("Unusable", listOf()) { 4 | // -- Companion Object -------------------------------------------------------------------------------------------- // 5 | // -- Properties -------------------------------------------------------------------------------------------------- // 6 | // -- Initialization ---------------------------------------------------------------------------------------------- // 7 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 8 | // -- Private Methods --------------------------------------------------------------------------------------------- // 9 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/bytecode/_internal/constantpool/Utf8Info.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool 2 | 3 | import java.io.DataInputStream 4 | 5 | internal class Utf8Info(dataInputStream: DataInputStream) : ConstantPoolInfo("Utf8", readValues(dataInputStream)) { 6 | // -- Companion Object -------------------------------------------------------------------------------------------- // 7 | 8 | companion object { 9 | fun readValues(dataInputStream: DataInputStream): List { 10 | return listOf(PlainValue(value = dataInputStream.readUTF())) 11 | } 12 | } 13 | 14 | // -- Properties -------------------------------------------------------------------------------------------------- // 15 | // -- Initialization ---------------------------------------------------------------------------------------------- // 16 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 17 | // -- Private Methods --------------------------------------------------------------------------------------------- // 18 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/common/ByteCodeAnalyserOpenClassFileService.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.common 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.components.Service 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.ui.Messages 7 | import com.intellij.openapi.vfs.VirtualFile 8 | import com.intellij.openapi.wm.ToolWindowManager 9 | import com.intellij.psi.PsiElement 10 | import com.intellij.psi.PsiFile 11 | import dev.turingcomplete.intellijbytecodeplugin._ui.ByteCodeToolWindowFactory 12 | import dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal.ClassFilesFinderService 13 | import dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal.ClassFilesFinderService.Result 14 | import dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal.ClassFilesPreparatorService 15 | 16 | @Service(Service.Level.PROJECT) 17 | class ByteCodeAnalyserOpenClassFileService(val project: Project) { 18 | // -- Companion Object -------------------------------------------------------------------------------------------- // 19 | // -- Properties -------------------------------------------------------------------------------------------------- // 20 | // -- Initialization ---------------------------------------------------------------------------------------------- // 21 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 22 | 23 | fun openPsiFiles(psiFiles: List) { 24 | run { service -> service.findByPsiFiles(psiFiles) } 25 | } 26 | 27 | fun openPsiElements(psiElementToPsiElementOriginPsiFile: Map) { 28 | run { service -> service.findByPsiElements(psiElementToPsiElementOriginPsiFile) } 29 | } 30 | 31 | fun openVirtualFiles(files: List) { 32 | run { service -> service.findByVirtualFiles(files) } 33 | } 34 | 35 | internal fun openClassFiles(classFiles: List) { 36 | run { service -> service.findByClassFiles(classFiles) } 37 | } 38 | 39 | // -- Private Methods --------------------------------------------------------------------------------------------- // 40 | 41 | private fun run(findBy: (ClassFilesFinderService) -> Result) { 42 | val result = findBy(project.getService(ClassFilesFinderService::class.java)) 43 | handleResult(result) 44 | } 45 | 46 | private fun handleResult(result: Result) { 47 | val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(ByteCodeToolWindowFactory.TOOL_WINDOW_ID) 48 | ?: throw IllegalStateException("Could not find tool window '${ByteCodeToolWindowFactory.TOOL_WINDOW_ID}'") 49 | 50 | result.classFilesToOpen.forEach { 51 | ByteCodeToolWindowFactory.openClassFile(it, toolWindow, project) 52 | } 53 | 54 | if (result.classFilesToPrepare.isNotEmpty()) { 55 | val classFilesPreparatorService = project.getService(ClassFilesPreparatorService::class.java) 56 | classFilesPreparatorService.prepareClassFiles(result.classFilesToPrepare, toolWindow.component) { 57 | ByteCodeToolWindowFactory.openClassFile(it, toolWindow, project) 58 | } 59 | } 60 | 61 | if (result.errors.isNotEmpty()) { 62 | val errorMessage = if (result.errors.size == 1) { 63 | result.errors[0] 64 | } 65 | else { 66 | "The following errors occurred: ${result.errors.joinToString(prefix = "
    ", postfix = "
") { "
  • $it
  • " }}" 67 | } 68 | ApplicationManager.getApplication().invokeLater { 69 | Messages.showErrorDialog(project, errorMessage, "Analyse Class Files") 70 | } 71 | } 72 | } 73 | 74 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 75 | 76 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/common/ClassFile.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.common 2 | 3 | import com.intellij.openapi.vfs.VirtualFile 4 | 5 | data class ClassFile internal constructor(val file: VirtualFile, val sourceFile: SourceFile? = null) { 6 | // -- Properties -------------------------------------------------------------------------------------------------- // 7 | // -- Initialization ---------------------------------------------------------------------------------------------- // 8 | 9 | init { 10 | assert(file.extension == "class") 11 | } 12 | 13 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 14 | 15 | fun refreshValidity(): Boolean { 16 | file.refresh(false, false) 17 | return file.isValid 18 | } 19 | 20 | // -- Private Methods --------------------------------------------------------------------------------------------- // 21 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 22 | // -- Companion Object -------------------------------------------------------------------------------------------- // 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/common/ClassFileContext.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.common 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.vfs.VirtualFile 5 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.ClassReader 6 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.tree.ClassNode 7 | 8 | interface ClassFileContext { 9 | // -- Companion Object -------------------------------------------------------------------------------------------- // 10 | // -- Properties -------------------------------------------------------------------------------------------------- // 11 | // -- Initialization ---------------------------------------------------------------------------------------------- // 12 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 13 | 14 | /** 15 | * Working in a synchronous way is for testing. 16 | */ 17 | fun workAsync(): Boolean 18 | 19 | fun project(): Project 20 | 21 | fun classFile(): ClassFile 22 | 23 | fun classNode(): ClassNode 24 | 25 | fun classReader(): ClassReader 26 | 27 | fun relatedClassFiles(): List 28 | 29 | // -- Private Methods --------------------------------------------------------------------------------------------- // 30 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 31 | 32 | class VerificationResult(val success: Boolean, val output: String) 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/common/CommonDataKeys.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.common 2 | 3 | import com.intellij.ide.impl.dataRules.GetDataRule 4 | import com.intellij.openapi.actionSystem.AnAction 5 | import com.intellij.openapi.actionSystem.DataKey 6 | import com.intellij.openapi.actionSystem.DataProvider 7 | import com.intellij.openapi.vfs.VirtualFile 8 | import dev.turingcomplete.intellijbytecodeplugin._ui.ByteCodeToolWindowFactory 9 | import dev.turingcomplete.intellijbytecodeplugin._ui.ClassFileTab 10 | 11 | /** 12 | * If the tool window is open but the focus is outside it (e.g. in the editor) 13 | * and an [AnAction] inside the tool window is executed, the [DataProvider] inside 14 | * the tool window is not called because the DataProvider is searched starting 15 | * from the focused component. Therefore, the two [DataKey]s are also registered 16 | * globally as [GetDataRule]s. 17 | */ 18 | object CommonDataKeys { 19 | // -- Properties -------------------------------------------------------------------------------------------------- // 20 | 21 | /** 22 | * The data key is also hard coded for the [ClassFileContextDataRule] in the `plugin.xml`. 23 | */ 24 | val CLASS_FILE_CONTEXT_DATA_KEY = DataKey.create("dev.turingcomplete.intellijbytecodeplugin.classFileContext") 25 | 26 | /** 27 | * The data key is also hard coded for the [ClassFileTabDataRule] in the `plugin.xml`. 28 | */ 29 | internal val CLASS_FILE_TAB_DATA_KEY = DataKey.create("dev.turingcomplete.intellijbytecodeplugin.classFileTab") 30 | 31 | /** 32 | * The data key is also hard coded for the [OnErrorDataRule] in the `plugin.xml`. 33 | */ 34 | val ON_ERROR_DATA_KEY = DataKey.create<(String, Throwable) -> Unit>("dev.turingcomplete.intellijbytecodeplugin.onError") 35 | 36 | /** 37 | * The data key is also hard coded for the [OpenInEditorDataRule] in the `plugin.xml`. 38 | */ 39 | val OPEN_IN_EDITOR_DATA_KEY = DataKey.create("dev.turingcomplete.intellijbytecodeplugin.openInEditor") 40 | 41 | val VALUE = DataKey.create("dev.turingcomplete.intellijbytecodeplugin.value") 42 | 43 | // -- Initialization ---------------------------------------------------------------------------------------------- // 44 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 45 | // -- Private Methods --------------------------------------------------------------------------------------------- // 46 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 47 | 48 | class ClassFileContextDataRule : GetDataRule { 49 | 50 | override fun getData(dataProvider: DataProvider): Any? { 51 | return CLASS_FILE_CONTEXT_DATA_KEY.getData(dataProvider) 52 | ?: ByteCodeToolWindowFactory.getData(dataProvider, CLASS_FILE_CONTEXT_DATA_KEY) 53 | } 54 | } 55 | 56 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 57 | 58 | class ClassFileTabDataRule : GetDataRule { 59 | 60 | override fun getData(dataProvider: DataProvider): Any? { 61 | return CLASS_FILE_TAB_DATA_KEY.getData(dataProvider) 62 | ?: ByteCodeToolWindowFactory.getData(dataProvider, CLASS_FILE_TAB_DATA_KEY) 63 | } 64 | } 65 | 66 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 67 | 68 | class OnErrorDataRule : GetDataRule { 69 | 70 | override fun getData(dataProvider: DataProvider): Any? { 71 | return ON_ERROR_DATA_KEY.getData(dataProvider) 72 | ?: ByteCodeToolWindowFactory.getData(dataProvider, ON_ERROR_DATA_KEY) 73 | } 74 | } 75 | 76 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 77 | 78 | class OpenInEditorDataRule : GetDataRule { 79 | 80 | override fun getData(dataProvider: DataProvider): Any? { 81 | return OPEN_IN_EDITOR_DATA_KEY.getData(dataProvider) 82 | ?: ByteCodeToolWindowFactory.getData(dataProvider, OPEN_IN_EDITOR_DATA_KEY) 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/common/SourceFile.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.common 2 | 3 | import com.google.common.base.Objects 4 | import com.intellij.openapi.module.Module 5 | import com.intellij.openapi.vfs.VirtualFile 6 | 7 | sealed class SourceFile private constructor(val file: VirtualFile) { 8 | // -- Properties -------------------------------------------------------------------------------------------------- // 9 | // -- Initialization ---------------------------------------------------------------------------------------------- // 10 | // -- Exported Methods -------------------------------------------------------------------------------------------- // 11 | 12 | override fun equals(other: Any?): Boolean { 13 | if (this === other) return true 14 | if (other !is SourceFile) return false 15 | 16 | if (file != other.file) return false 17 | 18 | return true 19 | } 20 | 21 | override fun hashCode(): Int { 22 | return file.hashCode() 23 | } 24 | 25 | // -- Private Methods --------------------------------------------------------------------------------------------- // 26 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 27 | 28 | class CompilableSourceFile(file: VirtualFile, val module: Module) : SourceFile(file) { 29 | 30 | override fun equals(other: Any?): Boolean { 31 | if (this === other) return true 32 | if (javaClass != other?.javaClass) return false 33 | 34 | other as CompilableSourceFile 35 | 36 | return file == other.file && module == other.module 37 | } 38 | 39 | override fun hashCode(): Int { 40 | return Objects.hashCode(module, file) 41 | } 42 | 43 | override fun toString(): String = "CompilableSourceFile(file=$file, module=$module)" 44 | } 45 | 46 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 47 | 48 | class NonCompilableSourceFile(file: VirtualFile) : SourceFile(file) { 49 | 50 | override fun toString(): String = "NonCompilableSourceFile(file=$file)" 51 | } 52 | 53 | // -- Companion Object -------------------------------------------------------------------------------------------- // 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/common/_internal/AsyncUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.common._internal 2 | 3 | import com.intellij.ide.BrowserUtil 4 | import com.intellij.openapi.application.ApplicationManager 5 | import com.intellij.openapi.diagnostic.Logger 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.ui.Messages 8 | import dev.turingcomplete.intellijbytecodeplugin._ui.ByteCodeToolWindowFactory.Companion.PLUGIN_NAME 9 | import java.nio.file.Path 10 | import java.util.concurrent.Callable 11 | 12 | internal object AsyncUtils { 13 | // -- Properties -------------------------------------------------------------------------------------------------- // 14 | 15 | private val log = Logger.getInstance(AsyncUtils::class.java) 16 | 17 | // -- Initialization ---------------------------------------------------------------------------------------------- // 18 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 19 | 20 | fun runAsync(project: Project?, execute: Runnable, onError: (Throwable) -> Unit) = 21 | runAsync(project, { execute.run(); null }, {}, onError) 22 | 23 | fun runAsync(project: Project?, execute: Callable, onSuccess: (V) -> Unit, onError: (Throwable) -> Unit) { 24 | ApplicationManager.getApplication().executeOnPooledThread { 25 | try { 26 | if (project?.isDisposed == true) { 27 | return@executeOnPooledThread 28 | } 29 | 30 | onSuccess(execute.call()) 31 | } 32 | catch (e: Throwable) { 33 | onError(e) 34 | } 35 | } 36 | } 37 | 38 | fun browseAsync(project: Project?, url: String) { 39 | browseAsync(project, { BrowserUtil.browse(url) }, url) 40 | } 41 | 42 | fun browseAsync(project: Project?, path: Path) { 43 | browseAsync(project, { BrowserUtil.browse(path) }, path.toString()) 44 | } 45 | 46 | // -- Private Methods --------------------------------------------------------------------------------------------- // 47 | 48 | private fun browseAsync(project: Project?, runnable: Runnable, target: String) { 49 | runAsync(project, runnable) { e -> 50 | log.warn("Could not open: $target", e) 51 | ApplicationManager.getApplication().invokeLater { 52 | Messages.showErrorDialog(project, "Could not open '$target': ${e.message ?: "Unknown error"}", PLUGIN_NAME) 53 | } 54 | } 55 | } 56 | 57 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 58 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/common/_internal/DataProviderUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.common._internal 2 | 3 | import com.intellij.openapi.actionSystem.DataContext 4 | import com.intellij.openapi.actionSystem.DataKey 5 | 6 | internal object DataProviderUtils { 7 | // -- Properties -------------------------------------------------------------------------------------------------- // 8 | // -- Initialization ---------------------------------------------------------------------------------------------- // 9 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 10 | 11 | fun getData(dataKey: DataKey, dataContext: DataContext) : T { 12 | return dataKey.getData(dataContext) ?: throw IllegalStateException("snh: Missing data from key: ${dataKey.name}") 13 | } 14 | 15 | // -- Private Methods --------------------------------------------------------------------------------------------- // 16 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/common/_internal/KotlinExtension.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.common._internal 2 | 3 | // -- Properties ---------------------------------------------------------------------------------------------------- // 4 | // -- Exported Methods ---------------------------------------------------------------------------------------------- // 5 | 6 | fun List.joinAsNaturalLanguage(transform: (T) -> String): String { 7 | val size = this.size 8 | if (size == 0) { 9 | return "" 10 | } 11 | 12 | val separator = if (size < 2) " and " else ", " 13 | return this.subList(0, size - 1).joinToString( 14 | separator, 15 | postfix = (if (size < 2) "" else " and ") + transform(this[size - 1]), 16 | transform = transform 17 | ) 18 | } 19 | 20 | // -- Private Methods ----------------------------------------------------------------------------------------------- // 21 | // -- Type ---------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/openclassfiles/OpenClassFilesToolWindowAction.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.openclassfiles 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent 4 | import com.intellij.openapi.extensions.ExtensionPointName 5 | import com.intellij.openapi.project.DumbAwareAction 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.util.NlsActions 8 | import org.jetbrains.annotations.Nls 9 | import javax.swing.Icon 10 | 11 | abstract class OpenClassFilesToolWindowAction(@NlsActions.ActionText val actionTitle: String, 12 | @Nls(capitalization = Nls.Capitalization.Sentence) val linkTitle: String, 13 | val icon: Icon? = null) { 14 | // -- Companion Object -------------------------------------------------------------------------------------------- // 15 | 16 | companion object { 17 | val EP: ExtensionPointName = ExtensionPointName.create("dev.turingcomplete.intellijbytecodeplugin.openClassFilesAction") 18 | } 19 | 20 | // -- Properties -------------------------------------------------------------------------------------------------- // 21 | // -- Initialization ---------------------------------------------------------------------------------------------- // 22 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 23 | 24 | fun createAsEmbeddedAction(project: Project) = object: DumbAwareAction(actionTitle, null, icon) { 25 | override fun actionPerformed(e: AnActionEvent) { 26 | execute(project) 27 | } 28 | } 29 | 30 | abstract fun execute(project: Project) 31 | 32 | // -- Private Methods --------------------------------------------------------------------------------------------- // 33 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 34 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/openclassfiles/_internal/AnalyzeByteCodeAction.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal 2 | 3 | import com.intellij.injected.editor.EditorWindow 4 | import com.intellij.lang.injection.InjectedLanguageManager 5 | import com.intellij.openapi.actionSystem.ActionUpdateThread 6 | import com.intellij.openapi.actionSystem.AnActionEvent 7 | import com.intellij.openapi.actionSystem.CommonDataKeys 8 | import com.intellij.openapi.actionSystem.DataContext 9 | import com.intellij.openapi.editor.Editor 10 | import com.intellij.openapi.project.DumbAwareAction 11 | import com.intellij.openapi.project.Project 12 | import com.intellij.psi.PsiElement 13 | import com.intellij.psi.PsiFile 14 | import com.intellij.psi.util.PsiUtilBase 15 | import dev.turingcomplete.intellijbytecodeplugin._ui.ByteCodePluginIcons 16 | import dev.turingcomplete.intellijbytecodeplugin.common.ByteCodeAnalyserOpenClassFileService 17 | 18 | internal class AnalyzeByteCodeAction : DumbAwareAction(TITLE, null, ByteCodePluginIcons.ACTION_ICON) { 19 | // -- Companion Object -------------------------------------------------------------------------------------------- // 20 | 21 | companion object { 22 | const val TITLE = "Analyze Byte Code" 23 | } 24 | 25 | // -- Properties -------------------------------------------------------------------------------------------------- // 26 | // -- Initialization ---------------------------------------------------------------------------------------------- // 27 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 28 | 29 | override fun update(e: AnActionEvent) { 30 | e.presentation.isEnabledAndVisible = CommonDataKeys.PROJECT.getData(e.dataContext)?.let { project -> 31 | val files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(e.dataContext) 32 | if (files?.any { ClassFilesFinderService.fileCanBeAnalysed(it, project) } == true) { 33 | true 34 | } 35 | else { 36 | findPsiElement(project, e.dataContext).let { result -> 37 | val containingFile = result.first?.containingFile 38 | if (containingFile != null) ClassFilesFinderService.fileCanBeAnalysed(containingFile) else false 39 | } 40 | } 41 | } ?: false 42 | } 43 | 44 | override fun actionPerformed(e: AnActionEvent) { 45 | val project = CommonDataKeys.PROJECT.getData(e.dataContext) ?: return 46 | 47 | val result = findPsiElement(project, e.dataContext) 48 | val psiElement = result.first 49 | val editorPsiFile = result.second 50 | if (psiElement != null) { 51 | project.getService(ByteCodeAnalyserOpenClassFileService::class.java).openPsiElements(mapOf(psiElement to editorPsiFile)) 52 | return 53 | } 54 | 55 | val files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(e.dataContext) 56 | if (files != null) { 57 | project.getService(ByteCodeAnalyserOpenClassFileService::class.java).openVirtualFiles(files.toList()) 58 | } 59 | } 60 | 61 | override fun getActionUpdateThread() = ActionUpdateThread.BGT 62 | 63 | // -- Private Methods --------------------------------------------------------------------------------------------- // 64 | 65 | private fun findPsiElement(project: Project, dataContext: DataContext): Pair { 66 | val editor = dataContext.getData(CommonDataKeys.EDITOR) 67 | ?: return Pair(dataContext.getData(CommonDataKeys.PSI_ELEMENT), dataContext.getData(CommonDataKeys.PSI_FILE)) 68 | 69 | val editorPsiFile = PsiUtilBase.getPsiFileInEditor(editor, project) 70 | val psiElement = findPsiElementInInjectedEditor(editor, editorPsiFile, project) 71 | ?: editorPsiFile?.findElementAt(editor.caretModel.offset) 72 | return Pair(psiElement, editorPsiFile) 73 | } 74 | 75 | private fun findPsiElementInInjectedEditor(editor: Editor, editorPsiFile: PsiFile?, project: Project): PsiElement? { 76 | if (editorPsiFile == null || editor is EditorWindow) { 77 | return null 78 | } 79 | 80 | val offset = editor.caretModel.offset 81 | return InjectedLanguageManager.getInstance(project).findInjectedElementAt(editorPsiFile, offset) 82 | ?.containingFile 83 | ?.findElementAt(editor.caretModel.offset) 84 | } 85 | 86 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 87 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/openclassfiles/_internal/ClassFileCandidates.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.project.guessProjectDir 5 | import dev.turingcomplete.intellijbytecodeplugin.common._internal.joinAsNaturalLanguage 6 | import org.jsoup.internal.StringUtil.StringJoiner 7 | import java.nio.file.Path 8 | import kotlin.io.path.relativeToOrNull 9 | 10 | sealed class ClassFileCandidates private constructor(val primaryPath: Path, private val fallbackPaths: List = emptyList()) { 11 | // -- Properties -------------------------------------------------------------------------------------------------- // 12 | // -- Initialization ---------------------------------------------------------------------------------------------- // 13 | // -- Exported Methods -------------------------------------------------------------------------------------------- // 14 | 15 | fun allPaths() = listOf(primaryPath) + fallbackPaths 16 | 17 | fun formatNotFoundError(postfix: String = "cannot be found.", project: Project? = null): String = 18 | StringJoiner(" ").apply { 19 | add("Class file") 20 | add("'${formatPath(primaryPath, project)}'") 21 | if (fallbackPaths.isNotEmpty()) { 22 | add("or possible fallback class file${if (fallbackPaths.size >= 2) "s" else ""}") 23 | add(fallbackPaths.joinAsNaturalLanguage { "'${formatPath(it, project)}'" }) 24 | } 25 | add(postfix) 26 | }.complete() 27 | 28 | override fun equals(other: Any?): Boolean { 29 | if (this === other) return true 30 | if (other !is ClassFileCandidates) return false 31 | 32 | if (primaryPath != other.primaryPath) return false 33 | if (fallbackPaths != other.fallbackPaths) return false 34 | 35 | return true 36 | } 37 | 38 | override fun hashCode(): Int { 39 | var result = primaryPath.hashCode() 40 | result = 31 * result + fallbackPaths.hashCode() 41 | return result 42 | } 43 | 44 | override fun toString(): String { 45 | return "ClassFileCandidates(primaryPath=$primaryPath, fallbackPaths=$fallbackPaths)" 46 | } 47 | 48 | // -- Private Methods --------------------------------------------------------------------------------------------- // 49 | 50 | private fun formatPath(path: Path, project: Project?): String { 51 | val projectDir = project?.guessProjectDir()?.toNioPath() 52 | return if (projectDir != null) { 53 | path.relativeToOrNull(projectDir)?.toString() ?: path.toString() 54 | } 55 | else { 56 | path.toString() 57 | } 58 | } 59 | 60 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 61 | 62 | class RelativeClassFileCandidates(primaryPath: Path, fallbackPaths: List) 63 | : ClassFileCandidates(primaryPath, fallbackPaths) 64 | 65 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 66 | 67 | class AbsoluteClassFileCandidates(primaryPath: Path, fallbackPaths: List) 68 | : ClassFileCandidates(primaryPath, fallbackPaths) 69 | 70 | // -- Companion Object -------------------------------------------------------------------------------------------- // 71 | 72 | companion object { 73 | 74 | fun fromRelativePaths(vararg paths: Path): RelativeClassFileCandidates { 75 | assert(paths.all { !it.isAbsolute }) 76 | assert(paths.isNotEmpty()) 77 | 78 | val fallbackPaths = if (paths.size > 1) paths.sliceArray(1 until paths.size) else emptyArray() 79 | return RelativeClassFileCandidates(primaryPath = paths.first(), fallbackPaths = fallbackPaths.toList()) 80 | } 81 | 82 | fun fromAbsolutePaths(vararg paths: Path): AbsoluteClassFileCandidates { 83 | assert(paths.all { it.isAbsolute }) 84 | assert(paths.isNotEmpty()) 85 | 86 | val fallbackPaths = if (paths.size > 1) paths.sliceArray(1 until paths.size) else emptyArray() 87 | return AbsoluteClassFileCandidates(primaryPath = paths.first(), fallbackPaths = fallbackPaths.toList()) 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/openclassfiles/_internal/CurrentEditorFileAction.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal 2 | 3 | import com.intellij.openapi.fileEditor.FileEditorManager 4 | import com.intellij.openapi.fileEditor.TextEditor 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.psi.PsiDocumentManager 7 | import dev.turingcomplete.intellijbytecodeplugin.common.ByteCodeAnalyserOpenClassFileService 8 | import dev.turingcomplete.intellijbytecodeplugin.openclassfiles.OpenClassFilesToolWindowAction 9 | 10 | internal class CurrentEditorFileAction : OpenClassFilesToolWindowAction( 11 | "Analyze Current Editor File", 12 | "Analyze current editor file" 13 | ) { 14 | // -- Companion Object -------------------------------------------------------------------------------------------- // 15 | // -- Properties -------------------------------------------------------------------------------------------------- // 16 | // -- Initialization ---------------------------------------------------------------------------------------------- // 17 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 18 | 19 | override fun execute(project: Project) { 20 | val editorPsiFiles = FileEditorManager.getInstance(project).selectedEditors.mapNotNull { editor -> 21 | if (editor is TextEditor) { 22 | PsiDocumentManager.getInstance(project).getPsiFile(editor.editor.document) 23 | } 24 | else { 25 | null 26 | } 27 | }.toList() 28 | project.getService(ByteCodeAnalyserOpenClassFileService::class.java).openPsiFiles(editorPsiFiles) 29 | } 30 | 31 | // -- Private Methods --------------------------------------------------------------------------------------------- // 32 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/openclassfiles/_internal/FileChooserAction.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal 2 | 3 | import com.intellij.icons.AllIcons 4 | import com.intellij.openapi.fileChooser.FileTypeDescriptor 5 | import com.intellij.openapi.fileChooser.ex.FileChooserDialogImpl 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.project.guessProjectDir 8 | import com.intellij.openapi.vfs.VfsUtil 9 | import dev.turingcomplete.intellijbytecodeplugin.common.ByteCodeAnalyserOpenClassFileService 10 | import dev.turingcomplete.intellijbytecodeplugin.openclassfiles.OpenClassFilesToolWindowAction 11 | 12 | internal class FileChooserAction : OpenClassFilesToolWindowAction("Open Class Files...", 13 | "Open class files...", 14 | AllIcons.General.OpenDisk) { 15 | // -- Companion Object -------------------------------------------------------------------------------------------- // 16 | // -- Properties -------------------------------------------------------------------------------------------------- // 17 | // -- Initialization ---------------------------------------------------------------------------------------------- // 18 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 19 | 20 | override fun execute(project: Project) { 21 | val descriptor = FileTypeDescriptor("Open Class Files", "class") 22 | val dialog = FileChooserDialogImpl(descriptor, project) 23 | val startPath = project.guessProjectDir() ?: VfsUtil.getUserHomeDir() 24 | val classFilesToOpen = dialog.choose(project, startPath).filter { it.isValid }.toList() 25 | project.getService(ByteCodeAnalyserOpenClassFileService::class.java).openVirtualFiles(classFilesToOpen) 26 | } 27 | 28 | // -- Private Methods --------------------------------------------------------------------------------------------- // 29 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/openclassfiles/_internal/FilesDropHandler.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal 2 | 3 | import com.intellij.ide.dnd.FileCopyPasteUtil 4 | import com.intellij.ide.dnd.TransferableWrapper 5 | import com.intellij.openapi.editor.EditorDropHandler 6 | import com.intellij.openapi.fileEditor.impl.EditorWindow 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.openapi.vfs.VirtualFileManager 9 | import dev.turingcomplete.intellijbytecodeplugin._ui.NotificationUtils 10 | import dev.turingcomplete.intellijbytecodeplugin.common.ByteCodeAnalyserOpenClassFileService 11 | import java.awt.datatransfer.DataFlavor 12 | import java.awt.datatransfer.Transferable 13 | import java.awt.dnd.DropTargetDragEvent 14 | import java.awt.dnd.DropTargetDropEvent 15 | import java.awt.dnd.DropTargetEvent 16 | import java.awt.dnd.DropTargetListener 17 | import javax.swing.JComponent 18 | import javax.swing.TransferHandler 19 | 20 | internal class FilesDropHandler(private val project: Project) : TransferHandler(), EditorDropHandler, DropTargetListener { 21 | // -- Companion Object -------------------------------------------------------------------------------------------- // 22 | // -- Properties -------------------------------------------------------------------------------------------------- // 23 | // -- Initialization ---------------------------------------------------------------------------------------------- // 24 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 25 | 26 | override fun canImport(comp: JComponent?, transferFlavors: Array?): Boolean { 27 | return canHandleDrop0(transferFlavors) 28 | } 29 | 30 | override fun canHandleDrop(transferFlavors: Array): Boolean { 31 | return canHandleDrop0(transferFlavors) 32 | } 33 | 34 | override fun drop(event: DropTargetDropEvent) { 35 | event.acceptDrop(event.dropAction) 36 | handleDrop0(event.transferable) 37 | } 38 | 39 | override fun handleDrop(transferable: Transferable, project: Project?, editorWindow: EditorWindow?) { 40 | handleDrop0(transferable) 41 | } 42 | 43 | override fun importData(comp: JComponent?, transferable: Transferable): Boolean { 44 | return handleDrop0(transferable) 45 | } 46 | 47 | override fun dragEnter(dtde: DropTargetDragEvent?) { 48 | // Nothing to do 49 | } 50 | 51 | override fun dragOver(dtde: DropTargetDragEvent?) { 52 | // Nothing to do 53 | } 54 | 55 | override fun dropActionChanged(dtde: DropTargetDragEvent?) { 56 | // Nothing to do 57 | } 58 | 59 | override fun dragExit(dte: DropTargetEvent?) { 60 | // Nothing to do 61 | } 62 | 63 | // -- Private Methods --------------------------------------------------------------------------------------------- // 64 | 65 | private fun canHandleDrop0(transferFlavors: Array?): Boolean { 66 | return transferFlavors != null && FileCopyPasteUtil.isFileListFlavorAvailable(transferFlavors) 67 | } 68 | 69 | private fun handleDrop0(transferable: Transferable): Boolean { 70 | try { 71 | // Handle drop of PSI elements 72 | if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { 73 | val transferData = transferable.getTransferData(DataFlavor.javaFileListFlavor) 74 | if (transferData is TransferableWrapper) { 75 | transferData.psiElements 76 | ?.map { it to null } 77 | ?.toMap() 78 | ?.takeIf { it.isNotEmpty() } 79 | ?.let { 80 | project.getService(ByteCodeAnalyserOpenClassFileService::class.java) 81 | .openPsiElements(it) 82 | } 83 | 84 | return true 85 | } 86 | } 87 | 88 | // Handle drop of other elements 89 | val virtualFileManager = VirtualFileManager.getInstance() 90 | FileCopyPasteUtil.getFileList(transferable) 91 | ?.mapNotNull { virtualFileManager.findFileByNioPath(it.toPath()) } 92 | ?.let { 93 | project.getService(ByteCodeAnalyserOpenClassFileService::class.java) 94 | .openVirtualFiles(it) 95 | 96 | return true 97 | } 98 | } 99 | catch (e: Exception) { 100 | NotificationUtils.notifyInternalError("Open .class files", "Failed to handle dropped files: ${e.message}.", e, project) 101 | } 102 | 103 | return false 104 | } 105 | 106 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 107 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/settings/ByteCodeAnalyserSettingsService.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.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.openapi.observable.properties.AtomicBooleanProperty 8 | import com.intellij.openapi.observable.properties.AtomicProperty 9 | import com.intellij.util.xmlb.annotations.Attribute 10 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.MethodDeclarationUtils.MethodDescriptorRenderMode 11 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.TypeUtils.TypeNameRenderMode 12 | 13 | @State(name = "ByteCodeAnalyserSettingsService", storages = [Storage("byte-code-analyser.xml")]) 14 | internal class ByteCodeAnalyserSettingsService : PersistentStateComponent { 15 | // -- Properties -------------------------------------------------------------------------------------------------- // 16 | 17 | var typeNameRenderMode by AtomicProperty(DEFAULT_TYPE_NAME_RENDER_MODE) 18 | var methodDescriptorRenderMode by AtomicProperty(DEFAULT_METHOD_DESCRIPTOR_RENDER_MODE) 19 | var showAccessAsHex by AtomicBooleanProperty(DEFAULT_SHOW_ACCESS_AS_HEX) 20 | var skipDebug by AtomicBooleanProperty(DEFAULT_SKIP_DEBUG) 21 | var skipCode by AtomicBooleanProperty(DEFAULT_SKIP_CODE) 22 | var skipFrame by AtomicBooleanProperty(DEFAULT_SKIP_FRAME) 23 | 24 | // -- Initialization ---------------------------------------------------------------------------------------------- // 25 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 26 | 27 | override fun getState() = State( 28 | typeNameRenderMode = typeNameRenderMode.name, 29 | methodDescriptorRenderMode = methodDescriptorRenderMode.name, 30 | showAccessAsHex = showAccessAsHex, 31 | skipDebug = skipDebug, 32 | skipCode = skipCode, 33 | skipFrame = skipFrame 34 | ) 35 | 36 | override fun loadState(state: State) { 37 | typeNameRenderMode = state.typeNameRenderMode?.let { TypeNameRenderMode.valueOf(it) } ?: DEFAULT_TYPE_NAME_RENDER_MODE 38 | methodDescriptorRenderMode = state.methodDescriptorRenderMode?.let { MethodDescriptorRenderMode.valueOf(it) } ?: DEFAULT_METHOD_DESCRIPTOR_RENDER_MODE 39 | showAccessAsHex = state.showAccessAsHex ?: DEFAULT_SHOW_ACCESS_AS_HEX 40 | skipDebug = state.skipDebug ?: DEFAULT_SKIP_DEBUG 41 | skipCode = state.skipCode ?: DEFAULT_SKIP_CODE 42 | skipFrame = state.skipFrame ?: DEFAULT_SKIP_FRAME 43 | } 44 | 45 | // -- Private Methods --------------------------------------------------------------------------------------------- // 46 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 47 | 48 | data class State( 49 | @get:Attribute("typeNameRenderMode") 50 | var typeNameRenderMode: String? = null, 51 | @get:Attribute("methodDescriptorRenderMode") 52 | var methodDescriptorRenderMode: String? = null, 53 | @get:Attribute("showAccessAsHex") 54 | var showAccessAsHex: Boolean? = null, 55 | @get:Attribute("skipDebug") 56 | var skipDebug: Boolean? = null, 57 | @get:Attribute("skipCode") 58 | var skipCode: Boolean? = null, 59 | @get:Attribute("skipFrame") 60 | var skipFrame: Boolean? = null, 61 | ) 62 | 63 | // -- Companion Object -------------------------------------------------------------------------------------------- // 64 | 65 | companion object { 66 | 67 | private val DEFAULT_TYPE_NAME_RENDER_MODE = TypeNameRenderMode.QUALIFIED 68 | private val DEFAULT_METHOD_DESCRIPTOR_RENDER_MODE = MethodDescriptorRenderMode.DESCRIPTOR 69 | private const val DEFAULT_SHOW_ACCESS_AS_HEX = true 70 | private const val DEFAULT_SKIP_DEBUG = false 71 | private const val DEFAULT_SKIP_CODE = false 72 | private const val DEFAULT_SKIP_FRAME = false 73 | 74 | val instance: ByteCodeAnalyserSettingsService 75 | get() = ApplicationManager.getApplication().getService(ByteCodeAnalyserSettingsService::class.java) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/tool/ByteCodeTool.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.tool 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent 4 | import com.intellij.openapi.actionSystem.CommonDataKeys 5 | import com.intellij.openapi.extensions.ExtensionPointName 6 | import com.intellij.openapi.project.DumbAwareAction 7 | import com.intellij.openapi.project.Project 8 | import javax.swing.Icon 9 | 10 | abstract class ByteCodeTool(val title: String, val icon: Icon? = null) { 11 | // -- Companion Object -------------------------------------------------------------------------------------------- // 12 | 13 | companion object { 14 | val EP: ExtensionPointName = ExtensionPointName.create("dev.turingcomplete.intellijbytecodeplugin.byteCodeTool") 15 | } 16 | 17 | // -- Properties -------------------------------------------------------------------------------------------------- // 18 | // -- Initialization ---------------------------------------------------------------------------------------------- // 19 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 20 | 21 | abstract fun execute(project: Project?) 22 | 23 | fun toAction(): DumbAwareAction { 24 | return object: DumbAwareAction(title, null, icon) { 25 | override fun actionPerformed(e: AnActionEvent) { 26 | execute(CommonDataKeys.PROJECT.getData(e.dataContext)) 27 | } 28 | } 29 | } 30 | 31 | // -- Private Methods --------------------------------------------------------------------------------------------- // 32 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/tool/_internal/ClassVersionsOverviewTool.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.tool._internal 2 | 3 | import com.intellij.openapi.actionSystem.DataProvider 4 | import com.intellij.openapi.actionSystem.DefaultActionGroup 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.ui.DialogWrapper.IdeModalityType 7 | import com.intellij.ui.ScrollPaneFactory 8 | import com.intellij.ui.table.JBTable 9 | import com.intellij.util.ui.components.BorderLayoutPanel 10 | import dev.turingcomplete.intellijbytecodeplugin._ui.CopyValueAction 11 | import dev.turingcomplete.intellijbytecodeplugin._ui.UiUtils 12 | import dev.turingcomplete.intellijbytecodeplugin._ui.ViewValueAction 13 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.ClassVersionUtils 14 | import dev.turingcomplete.intellijbytecodeplugin.common.CommonDataKeys 15 | import dev.turingcomplete.intellijbytecodeplugin.tool.ByteCodeTool 16 | import java.awt.Dimension 17 | import javax.swing.table.DefaultTableModel 18 | import javax.swing.table.TableModel 19 | 20 | class ClassVersionsOverviewTool : ByteCodeTool("Class Versions Overview") { 21 | // -- Companion Object -------------------------------------------------------------------------------------------- // 22 | // -- Properties -------------------------------------------------------------------------------------------------- // 23 | // -- Initialization ---------------------------------------------------------------------------------------------- // 24 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 25 | 26 | override fun execute(project: Project?) { 27 | if (project?.isDisposed == true) { 28 | return 29 | } 30 | 31 | UiUtils.Dialog.show("Class Versions Overview", BorderLayoutPanel().apply { 32 | val model = DefaultTableModel( 33 | ClassVersionUtils.CLASS_VERSIONS.map { arrayOf(it.specification, it.major) }.toTypedArray(), 34 | arrayOf("Specification", "Class Version") 35 | ) 36 | val table = ClassVersionsTable(model) 37 | addToCenter(ScrollPaneFactory.createScrollPane(table)) 38 | }, Dimension(400, 500), project, IdeModalityType.MODELESS) 39 | } 40 | 41 | // -- Private Methods --------------------------------------------------------------------------------------------- // 42 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 43 | 44 | private class ClassVersionsTable(tableModel: TableModel): JBTable(tableModel), DataProvider { 45 | 46 | init { 47 | addMouseListener(UiUtils.Table.createContextMenuMouseListener(ClassVersionsOverviewTool::class.java.simpleName) { 48 | DefaultActionGroup().apply { 49 | add(CopyValueAction()) 50 | add(ViewValueAction()) 51 | } 52 | }) 53 | } 54 | 55 | override fun getData(dataId: String): Any? = when { 56 | CommonDataKeys.VALUE.`is`(dataId) -> UiUtils.Table.getSingleSelectedValue(this) 57 | else -> null 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/ByteCodeAction.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MemberVisibilityCanBePrivate") 2 | 3 | package dev.turingcomplete.intellijbytecodeplugin.view 4 | 5 | import com.intellij.openapi.actionSystem.DefaultActionGroup 6 | import com.intellij.openapi.extensions.ExtensionPointName 7 | import com.intellij.openapi.project.DumbAwareAction 8 | import com.intellij.openapi.util.NlsActions 9 | import javax.swing.Icon 10 | 11 | abstract class ByteCodeAction(@NlsActions.ActionText text : String?, 12 | @NlsActions.ActionDescription description : String?, 13 | icon : Icon?) : DumbAwareAction(text, description, icon) { 14 | // -- Companion Object -------------------------------------------------------------------------------------------- // 15 | 16 | companion object { 17 | val EP: ExtensionPointName = ExtensionPointName.create("dev.turingcomplete.intellijbytecodeplugin.byteCodeAction") 18 | 19 | fun DefaultActionGroup.addAllByteCodeActions() { 20 | EP.extensions.takeIf {it.isNotEmpty() }?.let { 21 | addAll(it.toList()) 22 | } 23 | } 24 | } 25 | 26 | // -- Properties -------------------------------------------------------------------------------------------------- // 27 | // -- Initialization ---------------------------------------------------------------------------------------------- // 28 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 29 | // -- Private Methods --------------------------------------------------------------------------------------------- // 30 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/ByteCodeView.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.actionSystem.DataProvider 5 | import com.intellij.openapi.extensions.ExtensionPointName 6 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFileContext 7 | import dev.turingcomplete.intellijbytecodeplugin.common.CommonDataKeys 8 | import dev.turingcomplete.intellijbytecodeplugin.view._internal.ErrorStateHandler 9 | 10 | abstract class ByteCodeView(val classFileContext: ClassFileContext, val title: String) 11 | : ErrorStateHandler(), Disposable, DataProvider { 12 | 13 | // -- Companion Object -------------------------------------------------------------------------------------------- // 14 | 15 | companion object { 16 | val EP: ExtensionPointName = ExtensionPointName.create("dev.turingcomplete.intellijbytecodeplugin.byteCodeView") 17 | } 18 | 19 | // -- Properties -------------------------------------------------------------------------------------------------- // 20 | // -- Initialization ---------------------------------------------------------------------------------------------- // 21 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 22 | 23 | override fun dispose() { 24 | // Override if needed 25 | } 26 | 27 | fun selected() { 28 | initComponent() 29 | doSelected() 30 | } 31 | 32 | open fun doSelected() { 33 | // Override if needed 34 | } 35 | 36 | override fun getData(dataId: String): Any? = when { 37 | CommonDataKeys.CLASS_FILE_CONTEXT_DATA_KEY.`is`(dataId) -> classFileContext 38 | CommonDataKeys.ON_ERROR_DATA_KEY.`is`(dataId) -> { message: String, cause: Throwable -> onError(message, cause) } 39 | else -> null 40 | } 41 | 42 | // -- Private Methods --------------------------------------------------------------------------------------------- // 43 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 44 | 45 | fun interface Creator { 46 | fun create(classFileContext: ClassFileContext): ByteCodeView 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/AsmView.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal 2 | 3 | import com.intellij.codeInsight.actions.RearrangeCodeProcessor 4 | import com.intellij.codeInsight.actions.ReformatCodeProcessor 5 | import com.intellij.ide.highlighter.JavaFileType 6 | import com.intellij.openapi.application.ApplicationManager 7 | import com.intellij.openapi.util.Computable 8 | import com.intellij.psi.PsiFile 9 | import com.intellij.psi.PsiManager 10 | import com.intellij.testFramework.LightVirtualFile 11 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.TraceUtils.traceVisit 12 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFileContext 13 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.util.ASMifier 14 | import dev.turingcomplete.intellijbytecodeplugin.view.ByteCodeParsingResultView 15 | 16 | class AsmView(classFileContext: ClassFileContext) : 17 | ByteCodeParsingResultView(classFileContext, "ASM", METHOD_LINE_REGEX) { 18 | 19 | // -- Companion Object -------------------------------------------------------------------------------------------- // 20 | 21 | companion object { 22 | // Example: "methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "", "()V", null, null);" 23 | private val METHOD_LINE_REGEX = Regex("^.*classWriter\\.visitMethod\\(.*?,\\s\"(?.+?)\".*\$") 24 | } 25 | 26 | // -- Properties -------------------------------------------------------------------------------------------------- // 27 | 28 | // -- Initialization ---------------------------------------------------------------------------------------------- // 29 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 30 | 31 | override fun asyncParseByteCode(parsingOptions: Int, onSuccess: (String) -> Unit) { 32 | val asmifiedText = traceVisit(classFileContext.classReader(), parsingOptions, ASMifier()) 33 | 34 | val asmifiedTextPsiFile = ApplicationManager.getApplication().runReadAction(Computable { 35 | val lightVirtualFile = LightVirtualFile(openInEditorFileName(), JavaFileType.INSTANCE, asmifiedText) 36 | PsiManager.getInstance(classFileContext.project()).findFile(lightVirtualFile) 37 | }) 38 | 39 | if (asmifiedTextPsiFile != null) { 40 | ApplicationManager.getApplication().invokeAndWait { 41 | val reformatProcessor = ReformatCodeProcessor(classFileContext.project(), asmifiedTextPsiFile, null, false) 42 | val rearrangeProcessor = RearrangeCodeProcessor(reformatProcessor) 43 | rearrangeProcessor.setPostRunnable { onSuccess(asmifiedTextPsiFile.text) } 44 | rearrangeProcessor.run() 45 | } 46 | } 47 | else { 48 | onSuccess(asmifiedText) 49 | } 50 | } 51 | 52 | override fun openInEditorFileName() = "${classFileContext.classFile().file.nameWithoutExtension}Dump.java" 53 | 54 | // -- Private Methods --------------------------------------------------------------------------------------------- // 55 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 56 | 57 | class MyCreator : Creator { 58 | override fun create(classFileContext: ClassFileContext) = AsmView(classFileContext) 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/PlainView.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal 2 | 3 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.TraceUtils.traceVisit 4 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFileContext 5 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.util.Textifier 6 | import dev.turingcomplete.intellijbytecodeplugin.view.ByteCodeParsingResultView 7 | 8 | 9 | internal class PlainView(classFileContext: ClassFileContext) 10 | : ByteCodeParsingResultView(classFileContext, "Plain", METHOD_LINE_REGEX) { 11 | 12 | // -- Companion Object -------------------------------------------------------------------------------------------- // 13 | 14 | companion object { 15 | // Example: " private (Ljava/lang/ClassLoader;Ljava/lang/Class;)V" 16 | private val METHOD_LINE_REGEX = Regex("^\\s\\s(?:[^/\\s]+\\s)*(?(\\w|\\\$|_|<|>)[^\\s(]+)\\(.*?\\).*\$") 17 | } 18 | 19 | // -- Properties -------------------------------------------------------------------------------------------------- // 20 | 21 | // -- Initialization ---------------------------------------------------------------------------------------------- // 22 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 23 | 24 | override fun asyncParseByteCode(parsingOptions: Int, onSuccess: (String) -> Unit) { 25 | onSuccess(traceVisit(classFileContext.classReader(), parsingOptions, Textifier())) 26 | } 27 | 28 | // -- Private Methods --------------------------------------------------------------------------------------------- // 29 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 30 | 31 | class MyCreator : Creator { 32 | override fun create(classFileContext: ClassFileContext) = PlainView(classFileContext) 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/ReParseByteCodeAction.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal 2 | 3 | import com.intellij.icons.AllIcons 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import dev.turingcomplete.intellijbytecodeplugin.common.CommonDataKeys 6 | import dev.turingcomplete.intellijbytecodeplugin.common._internal.DataProviderUtils 7 | import dev.turingcomplete.intellijbytecodeplugin.view.ByteCodeAction 8 | 9 | @Suppress("ComponentNotRegistered") 10 | internal class ReParseByteCodeAction : ByteCodeAction("Re-Parse Class File", null, AllIcons.Actions.Refresh) { 11 | // -- Companion Object -------------------------------------------------------------------------------------------- // 12 | // -- Properties -------------------------------------------------------------------------------------------------- // 13 | // -- Initialization ---------------------------------------------------------------------------------------------- // 14 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 15 | 16 | override fun actionPerformed(e: AnActionEvent) { 17 | val classFileTab = DataProviderUtils.getData(CommonDataKeys.CLASS_FILE_TAB_DATA_KEY, e.dataContext) 18 | classFileTab.reParseClassNodeContext() 19 | } 20 | 21 | // -- Private Methods --------------------------------------------------------------------------------------------- // 22 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/VerifyByteCodeAction.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal 2 | 3 | import com.intellij.icons.AllIcons 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.application.ApplicationManager 6 | import com.intellij.openapi.editor.colors.EditorColorsManager 7 | import com.intellij.ui.JBColor 8 | import com.intellij.ui.ScrollPaneFactory 9 | import com.intellij.ui.components.JBLabel 10 | import com.intellij.ui.components.JBTextArea 11 | import com.intellij.util.ui.GridBag 12 | import com.intellij.util.ui.JBUI 13 | import com.intellij.util.ui.UIUtil 14 | import dev.turingcomplete.intellijbytecodeplugin._ui.ByteCodePluginIcons 15 | import dev.turingcomplete.intellijbytecodeplugin._ui.UiUtils 16 | import dev.turingcomplete.intellijbytecodeplugin._ui.overrideTopInset 17 | import dev.turingcomplete.intellijbytecodeplugin._ui.withCommonsDefaults 18 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFileContext 19 | import dev.turingcomplete.intellijbytecodeplugin.common.CommonDataKeys 20 | import dev.turingcomplete.intellijbytecodeplugin.common._internal.AsyncUtils 21 | import dev.turingcomplete.intellijbytecodeplugin.common._internal.DataProviderUtils 22 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.tree.analysis.AnalyzerException 23 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.util.CheckClassAdapter 24 | import dev.turingcomplete.intellijbytecodeplugin.view.ByteCodeAction 25 | import java.awt.Dimension 26 | import java.awt.Font 27 | import java.awt.GridBagConstraints 28 | import java.awt.GridBagLayout 29 | import java.io.PrintWriter 30 | import java.io.StringWriter 31 | import javax.swing.BorderFactory 32 | import javax.swing.JPanel 33 | import javax.swing.SwingConstants 34 | 35 | @Suppress("ComponentNotRegistered") 36 | internal class VerifyByteCodeAction : ByteCodeAction("Verify Byte Code", null, ByteCodePluginIcons.VERIFY_ICON) { 37 | 38 | // -- Companion Object -------------------------------------------------------------------------------------------- // 39 | // -- Properties -------------------------------------------------------------------------------------------------- // 40 | // -- Initialization ---------------------------------------------------------------------------------------------- // 41 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 42 | 43 | override fun actionPerformed(e: AnActionEvent) { 44 | val classFileContext = DataProviderUtils.getData(CommonDataKeys.CLASS_FILE_CONTEXT_DATA_KEY, e.dataContext) 45 | 46 | val onError = DataProviderUtils.getData(CommonDataKeys.ON_ERROR_DATA_KEY, e.dataContext) 47 | 48 | val verifyByteCode = { 49 | val resultWriter = StringWriter() 50 | val printWriter = PrintWriter(resultWriter) 51 | CheckClassAdapter.verify(classFileContext.classReader(), true, printWriter) 52 | 53 | val output = resultWriter.toString() 54 | val success = !output.contains(AnalyzerException::class.java.name) 55 | ClassFileContext.VerificationResult(success, output) 56 | } 57 | AsyncUtils.runAsync(classFileContext.project(), verifyByteCode, { result -> 58 | ApplicationManager.getApplication().invokeLater { 59 | val resultPanel = VerifyByteCodeResultPanel(result) 60 | UiUtils.Dialog.show("Verify Byte Code Result", resultPanel, Dimension(800, 450), classFileContext.project()) 61 | } 62 | }, { cause -> onError("Failed execute byte code verification", cause) }) 63 | } 64 | 65 | // -- Private Methods --------------------------------------------------------------------------------------------- // 66 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 67 | 68 | private class VerifyByteCodeResultPanel(result: ClassFileContext.VerificationResult) : JPanel(GridBagLayout()) { 69 | init { 70 | val bag = GridBag().withCommonsDefaults().setDefaultFill(GridBagConstraints.HORIZONTAL) 71 | 72 | val stateLabel = if (result.success) { 73 | JBLabel("Byte code verified.", AllIcons.General.InspectionsOK, SwingConstants.LEFT) 74 | } 75 | else { 76 | JBLabel("Byte code verification failed. See output for failure details.", AllIcons.General.BalloonError, SwingConstants.LEFT) 77 | } 78 | add(stateLabel.apply { font = font.deriveFont(Font.BOLD) }, bag.nextLine().next()) 79 | 80 | add(JBLabel("Output:"), bag.nextLine().next().overrideTopInset(UIUtil.DEFAULT_HGAP)) 81 | val resultTextArea = JBTextArea(result.output, 20, 100).apply { 82 | val globalScheme = EditorColorsManager.getInstance().globalScheme 83 | font = JBUI.Fonts.create(globalScheme.editorFontName, globalScheme.editorFontSize) 84 | } 85 | add(ScrollPaneFactory.createScrollPane(resultTextArea).apply { 86 | border = BorderFactory.createLineBorder(JBColor.border()) 87 | }, bag.nextLine().next().fillCell().weightx(1.0).weighty(1.0).overrideTopInset(2)) 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_constantpool/ConstantPoolView.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._constantpool 2 | 3 | import com.intellij.icons.AllIcons 4 | import com.intellij.openapi.actionSystem.ActionManager 5 | import com.intellij.openapi.actionSystem.ActionUpdateThread 6 | import com.intellij.openapi.actionSystem.AnActionEvent 7 | import com.intellij.openapi.actionSystem.DataProvider 8 | import com.intellij.openapi.actionSystem.DefaultActionGroup 9 | import com.intellij.openapi.actionSystem.ToggleAction 10 | import com.intellij.openapi.application.ApplicationManager 11 | import com.intellij.openapi.project.DumbAwareToggleAction 12 | import com.intellij.openapi.ui.SimpleToolWindowPanel 13 | import com.intellij.ui.AnimatedIcon 14 | import com.intellij.ui.ScrollPaneFactory 15 | import com.intellij.ui.components.JBLabel 16 | import dev.turingcomplete.intellijbytecodeplugin._ui.ByteCodeToolWindowFactory 17 | import dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool.ConstantPool 18 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFileContext 19 | import dev.turingcomplete.intellijbytecodeplugin.common._internal.AsyncUtils 20 | import dev.turingcomplete.intellijbytecodeplugin.view.ByteCodeAction.Companion.addAllByteCodeActions 21 | import dev.turingcomplete.intellijbytecodeplugin.view.ByteCodeView 22 | import javax.swing.JComponent 23 | import javax.swing.SwingConstants 24 | 25 | class ConstantPoolView(classFileContext: ClassFileContext) 26 | : ByteCodeView(classFileContext, "Constant Pool"), DataProvider { 27 | // -- Companion Object -------------------------------------------------------------------------------------------- // 28 | // -- Properties -------------------------------------------------------------------------------------------------- // 29 | 30 | private val centerComponent : SimpleToolWindowPanel by lazy { SimpleToolWindowPanel(true, false) } 31 | 32 | private var constantPoolTable: ConstantPoolTable? = null 33 | 34 | // -- Initialization ---------------------------------------------------------------------------------------------- // 35 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 36 | 37 | override fun createCenterComponent(): JComponent { 38 | return centerComponent.apply { 39 | toolbar = createToolbar(this) 40 | 41 | asyncReadConstantPool() 42 | } 43 | } 44 | 45 | override fun reParseClassNodeContext() { 46 | asyncReadConstantPool() 47 | } 48 | 49 | private fun asyncReadConstantPool() { 50 | constantPoolTable = null 51 | centerComponent. setContent(JBLabel("Parsing constant pool...", AnimatedIcon.Default(), SwingConstants.CENTER)) 52 | 53 | val onSuccess: (ConstantPool) -> Unit = { constantPool -> 54 | ApplicationManager.getApplication().invokeLater { 55 | constantPoolTable = ConstantPoolTable(constantPool) 56 | centerComponent.setContent(ScrollPaneFactory.createScrollPane(constantPoolTable, true)) 57 | } 58 | } 59 | AsyncUtils.runAsync(classFileContext.project(), { ConstantPool.create(classFileContext.classFile()) }, 60 | onSuccess, { cause -> onError("Failed to parse constant pool", cause) }) 61 | } 62 | 63 | // -- Private Methods --------------------------------------------------------------------------------------------- // 64 | 65 | private fun createToolbar(targetComponent: JComponent): JComponent { 66 | val toolbarGroup = DefaultActionGroup().apply { 67 | addAllByteCodeActions() 68 | 69 | addSeparator() 70 | 71 | add(createResolveIndicesAction()) 72 | } 73 | return ActionManager.getInstance().createActionToolbar("${ByteCodeToolWindowFactory.TOOLBAR_PLACE_PREFIX}.constantPoolView", toolbarGroup, true).run { 74 | setTargetComponent(targetComponent) 75 | component 76 | } 77 | } 78 | 79 | private fun createResolveIndicesAction(): ToggleAction { 80 | return object : DumbAwareToggleAction("Resolve Referenced Indices in Values", null, AllIcons.Diff.MagicResolveToolbar) { 81 | 82 | override fun update(e: AnActionEvent) { 83 | e.presentation.isEnabled = constantPoolTable != null 84 | } 85 | 86 | override fun isSelected(e: AnActionEvent): Boolean = constantPoolTable?.resolveIndices ?: false 87 | 88 | override fun setSelected(e: AnActionEvent, state: Boolean) { 89 | constantPoolTable?.let { it.resolveIndices = state } 90 | } 91 | 92 | override fun getActionUpdateThread() = ActionUpdateThread.EDT 93 | } 94 | } 95 | 96 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 97 | 98 | class MyCreator : Creator { 99 | override fun create(classFileContext: ClassFileContext) = ConstantPoolView(classFileContext) 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_decompiler/DecompiledView.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._decompiler 2 | 3 | import com.intellij.ide.plugins.PluginManagerCore 4 | import com.intellij.openapi.application.ApplicationManager 5 | import com.intellij.openapi.extensions.PluginId 6 | import com.intellij.openapi.fileEditor.FileEditorManager 7 | import com.intellij.openapi.fileEditor.FileEditorManagerListener 8 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFileContext 9 | import dev.turingcomplete.intellijbytecodeplugin.common.CommonDataKeys 10 | import dev.turingcomplete.intellijbytecodeplugin.view.ByteCodeParsingResultView 11 | 12 | internal class DecompiledView(classFileContext: ClassFileContext) 13 | : ByteCodeParsingResultView(classFileContext, "Decompiled", parsingOptionsAvailable = false) { 14 | 15 | // -- Properties -------------------------------------------------------------------------------------------------- // 16 | // -- Initialization ---------------------------------------------------------------------------------------------- // 17 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 18 | 19 | override fun asyncParseByteCode(parsingOptions: Int, onSuccess: (String) -> Unit) { 20 | val classFile = classFileContext.classFile() 21 | 22 | // The `IdeaDecompiler` from the bundled Jetbrains Java Decompiler 23 | // plugin, will require to accept a legal text before its usage. For 24 | // that, a dialog needs to be opened through the 25 | // `Before#beforeFileOpened` listener. If the legal text does not 26 | // get accepted (or the dialog does not get opened), IntelliJ will 27 | // do a fallback to its default decompiler, which may not be able to 28 | // decompile method bodies of Java class files. 29 | ApplicationManager.getApplication().invokeAndWait { 30 | classFileContext.project().messageBus.syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER) 31 | .beforeFileOpened(FileEditorManager.getInstance(classFileContext.project()), classFile.file) 32 | } 33 | 34 | val decompiledSourceCode = DecompilerUtils.decompile(classFile.file, classFileContext.project()) 35 | if (decompiledSourceCode != null) { 36 | onSuccess(decompiledSourceCode) 37 | } 38 | else { 39 | var errorMessage = "The class file could not be decompiled by any of the available decompilers." 40 | val decompilerPlugin = PluginManagerCore.getPlugin(PluginId.getId("org.jetbrains.java.decompiler")) 41 | if (decompilerPlugin == null || !decompilerPlugin.isEnabled) { 42 | errorMessage += " Try to install or enable JetBrain's 'Java Bytecode Decompiler' plugin." 43 | } 44 | onError(errorMessage, IllegalArgumentException()) 45 | } 46 | } 47 | 48 | override fun getData(dataId: String): Any? { 49 | return when { 50 | CommonDataKeys.OPEN_IN_EDITOR_DATA_KEY.`is`(dataId) -> classFileContext.classFile().file 51 | 52 | else -> super.getData(dataId) 53 | } 54 | } 55 | 56 | // -- Private Methods --------------------------------------------------------------------------------------------- // 57 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 58 | 59 | class MyCreator : Creator { 60 | override fun create(classFileContext: ClassFileContext) = DecompiledView(classFileContext) 61 | } 62 | 63 | // -- Companion Object -------------------------------------------------------------------------------------------- // 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_decompiler/DecompilerUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._decompiler 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.vfs.VirtualFile 5 | import com.intellij.openapi.vfs.findFile 6 | import com.intellij.psi.PsiManager 7 | import com.intellij.psi.compiled.ClassFileDecompilers 8 | 9 | object DecompilerUtils { 10 | // -- Properties -------------------------------------------------------------------------------------------------- // 11 | 12 | private const val IDEA_DECOMPILER_FQ_CLASS_NAME = "org.jetbrains.java.decompiler.IdeaDecompiler" 13 | 14 | // -- Initialization ---------------------------------------------------------------------------------------------- // 15 | // -- Exported Methods -------------------------------------------------------------------------------------------- // 16 | 17 | fun decompile(classFile: VirtualFile, project: Project): String? { 18 | val decompilers = findDecompilersForFile(classFile) 19 | 20 | val outermostClassFileName = "${classFile.name.substringBefore('$')}.class" 21 | val isNestedClassFile = classFile.name != outermostClassFileName 22 | 23 | val psiManager = PsiManager.getInstance(project) 24 | var decompiledSourceCode: String? = decompileClassFile(classFile, decompilers, psiManager) 25 | if (decompiledSourceCode == null && isNestedClassFile) { 26 | // Some decompilers have trouble decompiling standalone nested class 27 | // files. What might help is to decompile the outermost class. Some 28 | // decompilers will then include the nested class in that file. 29 | val outermostClassFile = classFile.parent.findFile(outermostClassFileName) 30 | if (outermostClassFile != null) { 31 | decompiledSourceCode = decompileClassFile(outermostClassFile, decompilers, psiManager) 32 | } 33 | } 34 | 35 | return decompiledSourceCode 36 | } 37 | 38 | fun findDecompilersForFile(classFile: VirtualFile): List { 39 | val decompilers = ClassFileDecompilers.getInstance().EP_NAME.extensions 40 | .filter { it.accepts(classFile) } 41 | // Prefer the `IdeaDecompiler` decompiler, as it produces better results. 42 | .sortedBy { it.javaClass.name != IDEA_DECOMPILER_FQ_CLASS_NAME } 43 | .toList() 44 | return decompilers 45 | } 46 | 47 | // -- Private Methods --------------------------------------------------------------------------------------------- // 48 | 49 | private fun decompileClassFile( 50 | classFile: VirtualFile, 51 | decompilers: List, 52 | psiManager: PsiManager 53 | ): String? { 54 | decompilers.forEach { decompiler -> 55 | if (decompiler is ClassFileDecompilers.Full) { 56 | val createFileViewProvider = decompiler.createFileViewProvider(classFile, psiManager, true) 57 | val decompiledText = createFileViewProvider.getPsi(createFileViewProvider.baseLanguage)?.text 58 | if (!decompiledText.isNullOrBlank()) { 59 | return decompiledText 60 | } 61 | } 62 | else if (decompiler is ClassFileDecompilers.Light) { 63 | val decompiledText = decompiler.getText(classFile).toString() 64 | if (decompiledText.isNotBlank()) { 65 | return decompiledText 66 | } 67 | } 68 | } 69 | return null 70 | } 71 | 72 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 73 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/GoToProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure 2 | 3 | import com.intellij.ide.actions.GotoClassAction 4 | import com.intellij.ide.actions.SearchEverywhereBaseAction 5 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.TypeUtils 6 | 7 | internal abstract class GoToProvider(val value: String, val goToAction: () -> SearchEverywhereBaseAction) { 8 | // -- Companion Object -------------------------------------------------------------------------------------------- // 9 | // -- Properties -------------------------------------------------------------------------------------------------- // 10 | // -- Initialization ---------------------------------------------------------------------------------------------- // 11 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 12 | // -- Private Methods --------------------------------------------------------------------------------------------- // 13 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 14 | 15 | class Class(internalName: String) 16 | : GoToProvider(TypeUtils.toReadableName(internalName, TypeUtils.TypeNameRenderMode.QUALIFIED), { GotoClassAction() }) 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/StructureTreeContext.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure 2 | 3 | import com.intellij.openapi.project.Project 4 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.MethodDeclarationUtils.MethodDescriptorRenderMode 5 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.TypeUtils.TypeNameRenderMode 6 | import dev.turingcomplete.intellijbytecodeplugin.settings.ByteCodeAnalyserSettingsService 7 | import kotlin.properties.Delegates 8 | 9 | internal class StructureTreeContext(val project: Project, private val syncStructure: () -> Unit) { 10 | // -- Companion Object -------------------------------------------------------------------------------------------- // 11 | // -- Properties -------------------------------------------------------------------------------------------------- // 12 | 13 | var typeNameRenderMode: TypeNameRenderMode by Delegates.observable(ByteCodeAnalyserSettingsService.instance.typeNameRenderMode) { _, old, new -> 14 | ByteCodeAnalyserSettingsService.instance.typeNameRenderMode = new 15 | syncStructure(new, old) 16 | } 17 | var methodDescriptorRenderMode: MethodDescriptorRenderMode by Delegates.observable(ByteCodeAnalyserSettingsService.instance.methodDescriptorRenderMode) { _, old, new -> 18 | ByteCodeAnalyserSettingsService.instance.methodDescriptorRenderMode = new 19 | syncStructure(new, old) 20 | } 21 | var showAccessAsHex: Boolean by Delegates.observable(ByteCodeAnalyserSettingsService.instance.showAccessAsHex) { _, old, new -> 22 | ByteCodeAnalyserSettingsService.instance.showAccessAsHex = new 23 | syncStructure(new, old) 24 | } 25 | 26 | // -- Initialization ---------------------------------------------------------------------------------------------- // 27 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 28 | // -- Private Methods --------------------------------------------------------------------------------------------- // 29 | 30 | private fun syncStructure(new: T, old: T) { 31 | if (new != old) { 32 | syncStructure.invoke() 33 | } 34 | } 35 | 36 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/_class/FieldStructureNode.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._class 2 | 3 | import com.intellij.icons.AllIcons 4 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.AccessGroup 5 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.TypeUtils 6 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.Type 7 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.tree.FieldNode 8 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure.GoToProvider 9 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common.HtmlTextNode 10 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common.ValueNode 11 | 12 | internal class FieldStructureNode private constructor(private val field: FieldNode, fieldTypeInternalName: String) 13 | : ValueNode(displayValue = { ctx -> "${field.name}: ${TypeUtils.toReadableName(fieldTypeInternalName, ctx.typeNameRenderMode)}" }, 14 | icon = AllIcons.Nodes.Field, 15 | goToProvider = GoToProvider.Class(fieldTypeInternalName)) { 16 | 17 | // -- Companion Object -------------------------------------------------------------------------------------------- // 18 | // -- Properties -------------------------------------------------------------------------------------------------- // 19 | // -- Initialization ---------------------------------------------------------------------------------------------- // 20 | 21 | constructor(field: FieldNode) : this(field, Type.getType(field.desc).className) 22 | 23 | init { 24 | asyncAdd { 25 | addAccessNode(field.access, AccessGroup.FIELD) 26 | addAnnotationsNode("Annotations", field.visibleAnnotations, field.invisibleAnnotations) 27 | addAnnotationsNode("Type Annotations", field.visibleTypeAnnotations, field.invisibleTypeAnnotations) 28 | addAttributesNode(field.attrs) 29 | addInitialValueNode() 30 | addSignatureNode(field.signature) 31 | } 32 | } 33 | 34 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 35 | // -- Private Methods --------------------------------------------------------------------------------------------- // 36 | 37 | private fun addInitialValueNode() { 38 | field.value?.let { 39 | add(HtmlTextNode("Initial value:", 40 | it.toString(), 41 | icon = AllIcons.Debugger.Value, 42 | postFix = "${it::class.java.simpleName}")) 43 | } 44 | } 45 | 46 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/_class/FramesDialog.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._class 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.ui.DialogWrapper 5 | import com.intellij.ui.JBColor 6 | import com.intellij.util.ui.components.BorderLayoutPanel 7 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.MethodFramesUtils 8 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.TypeUtils 9 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.tree.MethodNode 10 | import javax.swing.BorderFactory 11 | import javax.swing.JComponent 12 | 13 | class FramesDialog(methodNode: MethodNode, 14 | private val initialTypeNameRenderMode: TypeUtils.TypeNameRenderMode, 15 | private val methodFrames: List, 16 | project: Project?) : DialogWrapper(project) { 17 | 18 | // -- Companion Object -------------------------------------------------------------------------------------------- // 19 | // -- Properties -------------------------------------------------------------------------------------------------- // 20 | // -- Initialization ---------------------------------------------------------------------------------------------- // 21 | 22 | init { 23 | this.title = "Frames of Method '${methodNode.name}'" 24 | setSize(700, 300) 25 | isModal = false 26 | init() 27 | } 28 | 29 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 30 | 31 | override fun createCenterPanel(): JComponent { 32 | return BorderLayoutPanel().apply { 33 | addToCenter(FramesPanel(initialTypeNameRenderMode, methodFrames).apply { 34 | border = BorderFactory.createLineBorder(JBColor.border()) 35 | }) 36 | } 37 | } 38 | 39 | override fun createActions() = arrayOf(myOKAction) 40 | 41 | // -- Private Methods --------------------------------------------------------------------------------------------- // 42 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/_class/ModuleStructureNode.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._class 2 | 3 | import com.intellij.icons.AllIcons 4 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.AccessGroup 5 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.TypeUtils 6 | import dev.turingcomplete.intellijbytecodeplugin.org.objectweb.asm.tree.ModuleNode 7 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure.GoToProvider 8 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common.StructureNode 9 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common.TextNode 10 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common.ValueNode 11 | 12 | internal class ModuleStructureNode(private val moduleNode: ModuleNode) 13 | : TextNode("Module Descriptor", AllIcons.Nodes.JavaModule) { 14 | 15 | // -- Companion Object -------------------------------------------------------------------------------------------- // 16 | // -- Properties -------------------------------------------------------------------------------------------------- // 17 | // -- Initialization ---------------------------------------------------------------------------------------------- // 18 | 19 | init { 20 | asyncAdd { 21 | add(ValueNode("Name:", moduleNode.name)) 22 | addAccessNode(moduleNode.access, AccessGroup.MODULE) 23 | addVersionNode(moduleNode.version) 24 | addMainClass() 25 | addPackages() 26 | addModuleRequires() 27 | addExportsNode() 28 | addOpensNode() 29 | addUsesNode() 30 | addProvidesNode() 31 | } 32 | } 33 | 34 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 35 | // -- Private Methods --------------------------------------------------------------------------------------------- // 36 | 37 | private fun addMainClass() { 38 | moduleNode.mainClass?.let { 39 | add(ValueNode("Main class:", it)) 40 | } 41 | } 42 | 43 | private fun addPackages() { 44 | addTitleNodeWithElements(moduleNode.packages, { TextNode("Packages", AllIcons.Nodes.Package) }) { _, `package` -> 45 | ValueNode(displayValue = `package`, icon = AllIcons.Nodes.Package) 46 | } 47 | } 48 | 49 | private fun addModuleRequires() { 50 | addTitleNodeWithElements(moduleNode.requires, { TextNode("Requires") }) { _, requires -> 51 | ValueNode(displayValue = requires.module, icon = AllIcons.Nodes.JavaModule).apply { 52 | addAccessNode(requires.access, AccessGroup.MODULE_REQUIRES) 53 | addVersionNode(requires.version) 54 | } 55 | } 56 | } 57 | 58 | private fun StructureNode.addVersionNode(version: String?) { 59 | version ?: return 60 | add(ValueNode("Version:", version)) 61 | } 62 | 63 | private fun addExportsNode() { 64 | addTitleNodeWithElements(moduleNode.exports, { TextNode("Exports") }) { _, exports -> 65 | createPackageToModulesNode(exports.packaze, exports.access, AccessGroup.MODULE_EXPORTS, exports.modules) 66 | } 67 | } 68 | 69 | private fun addOpensNode() { 70 | addTitleNodeWithElements(moduleNode.opens, { TextNode("Opens") }) { _, opens -> 71 | createPackageToModulesNode(opens.packaze, opens.access, AccessGroup.MODULE_OPENS, opens.modules) 72 | } 73 | } 74 | 75 | private fun createPackageToModulesNode(`package`: String, 76 | access: Int, 77 | accessGroup: AccessGroup, 78 | modules: List?): StructureNode { 79 | 80 | return ValueNode(displayValue = `package`, icon = AllIcons.Nodes.Package).apply { 81 | addAccessNode(access, accessGroup) 82 | modules?.takeIf { it.isNotEmpty() }?.let { modules -> 83 | add(TextNode("to Modules").apply { 84 | modules.forEach { module -> add(ValueNode(displayValue = module, icon = AllIcons.Nodes.JavaModule)) } 85 | }) 86 | } 87 | } 88 | } 89 | 90 | private fun addUsesNode() { 91 | addTitleNodeWithElements(moduleNode.uses, { TextNode("Uses") }) { _, uses -> 92 | ValueNode("Service:", { ctx -> TypeUtils.toReadableName(uses, ctx.typeNameRenderMode) }) 93 | } 94 | } 95 | 96 | private fun addProvidesNode() { 97 | addTitleNodeWithElements(moduleNode.provides, { TextNode("Provides") }) { _, provides -> 98 | val serviceInternalName = provides.service 99 | ValueNode("Service:", 100 | { ctx -> TypeUtils.toReadableName(serviceInternalName, ctx.typeNameRenderMode) }, 101 | goToProvider = GoToProvider.Class(serviceInternalName)).apply { 102 | 103 | provides.providers.forEach { provider -> 104 | add(ValueNode("with Provider:", 105 | { ctx -> TypeUtils.toReadableName(provider, ctx.typeNameRenderMode) }, 106 | goToProvider = GoToProvider.Class(provider))) 107 | } 108 | } 109 | } 110 | } 111 | 112 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 113 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/_common/HtmlTextNode.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common 2 | 3 | import com.intellij.openapi.util.text.HtmlBuilder 4 | import com.intellij.openapi.util.text.HtmlChunk 5 | import com.intellij.ui.ColorUtil 6 | import com.intellij.ui.components.JBLabel 7 | import com.intellij.util.ui.JBUI 8 | import com.intellij.util.ui.UIUtil 9 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure.GoToProvider 10 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure.StructureTreeContext 11 | import javax.swing.Icon 12 | import javax.swing.JComponent 13 | import javax.swing.SwingConstants 14 | 15 | internal class HtmlTextNode(preFix: String? = null, 16 | displayValue: (StructureTreeContext) -> String, 17 | rawValue: (StructureTreeContext) -> String = displayValue, 18 | postFix: String? = null, 19 | icon: Icon? = null, 20 | goToProvider: GoToProvider? = null) 21 | : ValueNode(preFix, displayValue, rawValue, postFix, icon, goToProvider) { 22 | 23 | // -- Companion Object -------------------------------------------------------------------------------------------- // 24 | 25 | companion object { 26 | private val contextHelpFont = JBUI.Fonts.smallFont() 27 | private val contextHelpFontCss = "font-family: '${contextHelpFont.family}'; font-size: ${contextHelpFont.size}pt;" 28 | private val notSelectedCss = ".contextHelp { color: #${ColorUtil.toHex(UIUtil.getContextHelpForeground())}; margin-left: 50pt; $contextHelpFontCss }" 29 | private val selectedCss = ".contextHelp { color: #${ColorUtil.toHex(UIUtil.getListForeground(true, true))}; margin-left: 50pt; $contextHelpFontCss }" 30 | } 31 | 32 | // -- Properties -------------------------------------------------------------------------------------------------- // 33 | 34 | private var notSelectedComponent: JComponent? = null 35 | private var selectedComponent: JComponent? = null 36 | 37 | // -- Initialization ---------------------------------------------------------------------------------------------- // 38 | 39 | constructor(preFix: String? = null, 40 | displayValue: String, 41 | rawValue: String = displayValue, 42 | postFix: String? = null, 43 | icon: Icon? = null) : this(preFix, { displayValue }, { rawValue }, postFix, icon) 44 | 45 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 46 | 47 | override fun component(selected: Boolean, context: StructureTreeContext, componentValid: Boolean): JComponent { 48 | if (!componentValid) { 49 | notSelectedComponent = createComponent(notSelectedCss, context) 50 | selectedComponent = createComponent(selectedCss, context) 51 | } 52 | 53 | return if (selected) selectedComponent!! else notSelectedComponent!! 54 | } 55 | 56 | override fun searchText(context: StructureTreeContext) = rawValue(context) 57 | 58 | // -- Private Methods --------------------------------------------------------------------------------------------- // 59 | 60 | private fun createComponent(css: String, context: StructureTreeContext): JComponent { 61 | val text = HtmlBuilder() 62 | .append(HtmlChunk.raw(css).wrapWith("style").attr("type", "text/css").wrapWith("head")) 63 | .append(HtmlChunk.raw(StringBuilder().apply { 64 | preFix?.let { append(it).append(" ") } 65 | append(displayValue(context)) 66 | postFix?.let { append(" ").append(it) } 67 | }.toString().replace(" ", " ")).wrapWith("body")) 68 | .wrapWith("html") 69 | .toString() 70 | 71 | return JBLabel(text, icon, SwingConstants.LEFT) 72 | } 73 | 74 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 75 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/_common/HyperLinkNode.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common 2 | 3 | import com.intellij.ui.HyperlinkLabel 4 | import com.intellij.util.ui.JBUI 5 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure.StructureTreeContext 6 | import javax.swing.JComponent 7 | import javax.swing.event.HyperlinkEvent 8 | 9 | internal open class HyperLinkNode(private val text: String, initialHyperLinkListener: HyperLinkListener? = null) 10 | : StructureNode(), InteractiveNode { 11 | 12 | // -- Companion Object -------------------------------------------------------------------------------------------- // 13 | // -- Properties -------------------------------------------------------------------------------------------------- // 14 | 15 | private val hyperLinkListeners = mutableSetOf() 16 | private var component: JComponent? = null 17 | 18 | // -- Initialization ---------------------------------------------------------------------------------------------- // 19 | 20 | init { 21 | initialHyperLinkListener?.let { hyperLinkListeners.add(it) } 22 | } 23 | 24 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 25 | 26 | override fun component(selected: Boolean, context: StructureTreeContext, componentValid: Boolean): JComponent { 27 | if (component == null) { 28 | // Without the wrapping later component adjustments, like borders, will 29 | // have no effects. 30 | component = JBUI.Panels.simplePanel(HyperlinkLabel(text).apply { 31 | hyperLinkListeners.forEach { hyperLinkListener -> 32 | addHyperlinkListener { event -> hyperLinkListener.handle(event, context) } 33 | } 34 | }) 35 | } 36 | 37 | return component!! 38 | } 39 | 40 | override fun searchText(context: StructureTreeContext): Nothing? = null 41 | 42 | fun addHyperLinkListener(hyperLinkListener: HyperLinkListener) { 43 | hyperLinkListeners.add(hyperLinkListener) 44 | } 45 | 46 | // -- Private Methods --------------------------------------------------------------------------------------------- // 47 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 48 | 49 | fun interface HyperLinkListener { 50 | fun handle(event: HyperlinkEvent, context: StructureTreeContext) 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/_common/InteractiveNode.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common 2 | 3 | internal interface InteractiveNode { 4 | // -- Companion Object -------------------------------------------------------------------------------------------- // 5 | // -- Properties -------------------------------------------------------------------------------------------------- // 6 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 7 | // -- Private Methods --------------------------------------------------------------------------------------------- // 8 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/_common/TextNode.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common 2 | 3 | import com.intellij.ui.components.JBLabel 4 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure.StructureTreeContext 5 | import javax.swing.Icon 6 | import javax.swing.JComponent 7 | import javax.swing.SwingConstants 8 | 9 | internal open class TextNode(private val text: String, icon: Icon? = null) : StructureNode() { 10 | // -- Companion Object -------------------------------------------------------------------------------------------- // 11 | // -- Properties -------------------------------------------------------------------------------------------------- // 12 | 13 | private val component: JComponent by lazy { JBLabel(text, icon, SwingConstants.LEFT) } 14 | 15 | // -- Initialization ---------------------------------------------------------------------------------------------- // 16 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 17 | 18 | override fun component(selected: Boolean, context: StructureTreeContext, componentValid: Boolean) = component 19 | 20 | override fun searchText(context: StructureTreeContext) = text 21 | 22 | // -- Private Methods --------------------------------------------------------------------------------------------- // 23 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/_common/ValueNode.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common 2 | 3 | import com.intellij.ui.components.JBLabel 4 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure.GoToProvider 5 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure.StructureTreeContext 6 | import javax.swing.Icon 7 | import javax.swing.JComponent 8 | import javax.swing.SwingConstants 9 | 10 | internal open class ValueNode(protected val preFix: String? = null, 11 | val displayValue: (StructureTreeContext) -> String, 12 | val rawValue: (StructureTreeContext) -> String = displayValue, 13 | protected val postFix: String? = null, 14 | protected val icon: Icon? = null, 15 | goToProvider: GoToProvider? = null) : StructureNode(goToProvider) { 16 | 17 | // -- Companion Object -------------------------------------------------------------------------------------------- // 18 | // -- Properties -------------------------------------------------------------------------------------------------- // 19 | 20 | private var component: JComponent? = null 21 | 22 | // -- Initialization ---------------------------------------------------------------------------------------------- // 23 | 24 | constructor(preFix: String? = null, displayValue: String, rawValue: String = displayValue, postFix: String? = null, icon: Icon? = null) 25 | : this(preFix, { displayValue }, { rawValue }, postFix, icon) 26 | 27 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 28 | 29 | override fun component(selected: Boolean, context: StructureTreeContext, componentValid: Boolean): JComponent { 30 | if (!componentValid) { 31 | val text = StringBuilder().apply { 32 | preFix?.let { append(it).append(" ") } 33 | append(displayValue(context)) 34 | postFix?.let { append(" ").append(it) } 35 | } 36 | component = JBLabel("$text", icon, SwingConstants.LEFT) 37 | } 38 | 39 | return component!! 40 | } 41 | 42 | override fun searchText(context: StructureTreeContext): String = rawValue(context) 43 | 44 | // -- Private Methods --------------------------------------------------------------------------------------------- // 45 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/common/OpenInEditorAction.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view.common 2 | 3 | import com.intellij.icons.AllIcons 4 | import com.intellij.openapi.actionSystem.ActionUpdateThread 5 | import com.intellij.openapi.actionSystem.AnActionEvent 6 | import com.intellij.openapi.fileEditor.FileEditorManager 7 | import com.intellij.openapi.fileEditor.OpenFileDescriptor 8 | import com.intellij.openapi.project.DumbAwareAction 9 | import com.intellij.openapi.util.NlsActions 10 | import dev.turingcomplete.intellijbytecodeplugin.common.CommonDataKeys 11 | import dev.turingcomplete.intellijbytecodeplugin.common._internal.DataProviderUtils 12 | import org.jetbrains.annotations.Nullable 13 | import javax.swing.Icon 14 | 15 | class OpenInEditorAction(@Nullable @NlsActions.ActionText text: String = "Open in Editor", 16 | icon: Icon = AllIcons.Actions.MoveTo2) : DumbAwareAction(text, null, icon) { 17 | // -- Companion Object -------------------------------------------------------------------------------------------- // 18 | // -- Properties -------------------------------------------------------------------------------------------------- // 19 | // -- Initialization ---------------------------------------------------------------------------------------------- // 20 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 21 | 22 | override fun update(e: AnActionEvent) { 23 | e.presentation.isEnabled = CommonDataKeys.OPEN_IN_EDITOR_DATA_KEY.getData(e.dataContext) != null 24 | } 25 | 26 | override fun getActionUpdateThread() = ActionUpdateThread.BGT 27 | 28 | override fun actionPerformed(e: AnActionEvent) { 29 | val openInEditorFile = CommonDataKeys.OPEN_IN_EDITOR_DATA_KEY.getData(e.dataContext) ?: return 30 | val project = DataProviderUtils.getData(com.intellij.openapi.actionSystem.CommonDataKeys.PROJECT, e.dataContext) 31 | FileEditorManager.getInstance(project).openEditor(OpenFileDescriptor(project, openInEditorFile), true) 32 | } 33 | 34 | // -- Private Methods --------------------------------------------------------------------------------------------- // 35 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 36 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/dev/turingcomplete/intellijbytecodeplugin/icon/kotlin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/icons/action.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/icons/action_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/icons/toolwindow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/icons/toolwindow_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/icons/verify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/icons/verify_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/kotlin/dev/turingcomplete/intellijbytecodeplugin/ClassFileConsumerTestCase.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin 2 | 3 | import com.intellij.ide.highlighter.ArchiveFileType 4 | import com.intellij.openapi.application.WriteAction 5 | import com.intellij.openapi.fileTypes.FileTypeManager 6 | import com.intellij.openapi.vfs.VirtualFile 7 | import com.intellij.openapi.vfs.VirtualFileManager 8 | import com.intellij.testFramework.LightPlatform4TestCase 9 | import org.assertj.core.api.Assertions.assertThat 10 | import org.junit.Assert 11 | import java.io.File 12 | import java.nio.file.Path 13 | import java.util.zip.ZipFile 14 | 15 | 16 | abstract class ClassFileConsumerTestCase(private val classFilePath: String) : LightPlatform4TestCase() { 17 | // -- Companion Object -------------------------------------------------------------------------------------------- // 18 | 19 | companion object { 20 | private const val LIMIT_CLASSES = 800 21 | 22 | fun testData(): List> = mutableListOf>().apply { 23 | addLibraryClasses("kotlin-stdlib") 24 | addLibraryClasses("groovy-") 25 | addLibraryClasses("commons-lang3") 26 | } 27 | 28 | private fun MutableList>.addLibraryClasses(libraryFileNamePrefix: String) { 29 | val oldSize = this.size 30 | findInClassPath(libraryFileNamePrefix) 31 | .forEach { kotlinStdLib -> this.addAll(readArchiveEntriesPaths(kotlinStdLib.toFile()).shuffled().take(LIMIT_CLASSES)) } 32 | assertThat(this.size - oldSize) 33 | .describedAs("Library with filename prefix '$libraryFileNamePrefix' should add at least 100 files") 34 | .isGreaterThanOrEqualTo(100) 35 | } 36 | 37 | private fun findInClassPath(prefix: String) = 38 | System.getProperty("java.class.path").split(System.getProperty("path.separator")) 39 | .asSequence() 40 | .map { Path.of(it) } 41 | .filter { it.fileName.toString().startsWith(prefix) } 42 | .toList() 43 | 44 | private fun readArchiveEntriesPaths(archiveFile: File): List> { 45 | val entriesPaths = mutableListOf>() 46 | 47 | ZipFile(archiveFile).use { zipFile -> 48 | val entries = zipFile.entries() 49 | while (entries.hasMoreElements()) { 50 | val zipEntry = entries.nextElement() 51 | if (zipEntry.name.endsWith(".class")) { 52 | entriesPaths.add(arrayOf(zipEntry.name, "jar://$archiveFile!/${zipEntry.name}")) 53 | } 54 | } 55 | } 56 | 57 | return entriesPaths 58 | } 59 | } 60 | 61 | // -- Properties -------------------------------------------------------------------------------------------------- // 62 | 63 | protected lateinit var classFileAsVirtualFile: VirtualFile 64 | 65 | // -- Initialization ---------------------------------------------------------------------------------------------- // 66 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 67 | 68 | override fun setUp() { 69 | super.setUp() 70 | 71 | WriteAction.runAndWait { 72 | FileTypeManager.getInstance().associateExtension(ArchiveFileType.INSTANCE, "jmod") 73 | } 74 | 75 | val virtualFile0 = VirtualFileManager.getInstance().findFileByUrl(classFilePath) 76 | Assert.assertNotNull("File $classFilePath not found", virtualFile0) 77 | classFileAsVirtualFile = virtualFile0 as VirtualFile 78 | } 79 | 80 | // -- Private Methods --------------------------------------------------------------------------------------------- // 81 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 82 | } -------------------------------------------------------------------------------- /src/test/kotlin/dev/turingcomplete/intellijbytecodeplugin/_internal/constantpool/ConstantPoolTest.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin._internal.constantpool 2 | 3 | import dev.turingcomplete.intellijbytecodeplugin.ClassFileConsumerTestCase 4 | import dev.turingcomplete.intellijbytecodeplugin.bytecode._internal.constantpool.ConstantPool 5 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFile 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | @RunWith(org.junit.runners.Parameterized::class) 10 | class ConstantPoolTest(@Suppress("UNUSED_PARAMETER") testName: String, classFilePath: String) : ClassFileConsumerTestCase(classFilePath) { 11 | // -- Companion Object -------------------------------------------------------------------------------------------- // 12 | 13 | companion object { 14 | @org.junit.runners.Parameterized.Parameters(name = "{0}") 15 | @JvmStatic 16 | fun data(): List> = testData() 17 | } 18 | 19 | // -- Properties -------------------------------------------------------------------------------------------------- // 20 | // -- Initialization ---------------------------------------------------------------------------------------------- // 21 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 22 | // -- Private Methods --------------------------------------------------------------------------------------------- // 23 | 24 | @Test 25 | fun testCreationOfConstantPool() { 26 | // We don't have an expected result here to compare with. This test should only 27 | // ensure, that there are no exceptions. 28 | ConstantPool.create(ClassFile(classFileAsVirtualFile)) 29 | } 30 | 31 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 32 | } -------------------------------------------------------------------------------- /src/test/kotlin/dev/turingcomplete/intellijbytecodeplugin/openclassfiles/_internal/ClassFilesPreparatorServiceTest.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal 2 | 3 | import dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal.ClassFilesPreparatorService.PrepareReason 4 | import dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal.ClassFilesPreparatorService.PrepareReason.MISSING 5 | import dev.turingcomplete.intellijbytecodeplugin.openclassfiles._internal.ClassFilesPreparatorService.PrepareReason.OUT_DATED 6 | import org.assertj.core.api.Assertions.assertThat 7 | import org.junit.jupiter.params.ParameterizedTest 8 | import org.junit.jupiter.params.provider.Arguments 9 | import org.junit.jupiter.params.provider.Arguments.arguments 10 | import org.junit.jupiter.params.provider.MethodSource 11 | 12 | internal class ClassFilesPreparatorServiceTest { 13 | // -- Properties -------------------------------------------------------------------------------------------------- // 14 | // -- Initialization ---------------------------------------------------------------------------------------------- // 15 | // -- Exported Methods -------------------------------------------------------------------------------------------- // 16 | 17 | @ParameterizedTest 18 | @MethodSource("prepareReasonQuestionTestVectors") 19 | fun `Given variations of source and class file names, When generation the PrepareReason question, Then get the expected text`( 20 | preparationReason: PrepareReason, 21 | sourceFileNamesToClassFilesNames: Map>, 22 | expectedQuestion: String 23 | ) { 24 | val actualQuestion = preparationReason.question(sourceFileNamesToClassFilesNames) 25 | assertThat(actualQuestion).isEqualTo(expectedQuestion) 26 | } 27 | 28 | // -- Private Methods --------------------------------------------------------------------------------------------- // 29 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 30 | // -- Companion Object -------------------------------------------------------------------------------------------- // 31 | 32 | companion object { 33 | 34 | @JvmStatic 35 | fun prepareReasonQuestionTestVectors(): List = listOf( 36 | arguments( 37 | MISSING, 38 | mapOf("Foo.kt" to listOf("A.class")), 39 | " The class file 'A.class' for the source file 'Foo.kt' is missing. Should it be compiled? " 40 | ), 41 | arguments( 42 | MISSING, 43 | mapOf("Foo.kt" to listOf("A.class", "B.class")), 44 | " The class files
    • A.class
    • B.class
    for the source file 'Foo.kt' are missing. Should it be compiled? " 45 | ), 46 | arguments( 47 | MISSING, 48 | mapOf("Foo.kt" to listOf("A.class", "B.class", "C.class")), 49 | " The class files
    • A.class
    • B.class
    • C.class
    for the source file 'Foo.kt' are missing. Should it be compiled? " 50 | ), 51 | arguments( 52 | MISSING, 53 | mapOf("Foo.kt" to listOf("A.class"), "Bar.kt" to listOf("A.class"), "Baz.kt" to listOf("A.class")), 54 | " The class files
    • A.class
    • A.class
    • A.class
    for the source files
    • Foo.kt
    • Bar.kt
    • Baz.kt
    are missing. Should they be compiled? " 55 | ), 56 | arguments( 57 | OUT_DATED, 58 | mapOf("Foo.kt" to listOf("A.class")), 59 | " The source file 'Foo.kt' is outdated. Should it be compiled? " 60 | ), 61 | arguments( 62 | OUT_DATED, 63 | mapOf("Foo.kt" to listOf("A.class", "B.class")), 64 | " The source file 'Foo.kt' are outdated. Should it be compiled? " 65 | ), 66 | arguments( 67 | OUT_DATED, 68 | mapOf("Foo.kt" to listOf("A.class"), "Bar.kt" to listOf("A.class"), "Baz.kt" to listOf("A.class")), 69 | " The source files
    • Foo.kt
    • Bar.kt
    • Baz.kt
    are outdated. Should they be compiled? " 70 | ), 71 | ) 72 | } 73 | } -------------------------------------------------------------------------------- /src/test/kotlin/dev/turingcomplete/intellijbytecodeplugin/view/_internal/_structure/StructureTreeTest.kt: -------------------------------------------------------------------------------- 1 | package dev.turingcomplete.intellijbytecodeplugin.view._internal._structure 2 | 3 | import com.intellij.ide.highlighter.ArchiveFileType 4 | import com.intellij.openapi.application.WriteAction 5 | import com.intellij.openapi.fileTypes.FileTypeManager 6 | import dev.turingcomplete.intellijbytecodeplugin.ClassFileConsumerTestCase 7 | import dev.turingcomplete.intellijbytecodeplugin._ui.DefaultClassFileContext 8 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.MethodDeclarationUtils 9 | import dev.turingcomplete.intellijbytecodeplugin.bytecode.TypeUtils 10 | import dev.turingcomplete.intellijbytecodeplugin.common.ClassFile 11 | import dev.turingcomplete.intellijbytecodeplugin.view._internal._structure._common.ValueNode 12 | import junit.framework.AssertionFailedError 13 | import org.junit.Test 14 | import org.junit.runner.RunWith 15 | import javax.swing.tree.TreeNode 16 | 17 | /** 18 | * This test tries to parse all classes from the `java.base` module and from the 19 | * `kotlin-stdlib` into a [StructureTree]. 20 | */ 21 | @RunWith(org.junit.runners.Parameterized::class) 22 | class StructureTreeTest(@Suppress("unused") testName: String, classFilePath: String) : ClassFileConsumerTestCase(classFilePath) { 23 | // -- Companion Object -------------------------------------------------------------------------------------------- // 24 | 25 | companion object { 26 | @org.junit.runners.Parameterized.Parameters(name = "{0}") 27 | @JvmStatic 28 | fun data(): List> = testData() 29 | } 30 | 31 | // -- Properties -------------------------------------------------------------------------------------------------- // 32 | 33 | private val structureTreeContextPermutations = mutableListOf() 34 | 35 | // -- Initialization ---------------------------------------------------------------------------------------------- // 36 | // -- Exposed Methods --------------------------------------------------------------------------------------------- // 37 | 38 | override fun setUp() { 39 | super.setUp() 40 | WriteAction.runAndWait { 41 | FileTypeManager.getInstance().associateExtension(ArchiveFileType.INSTANCE, "jmod") 42 | } 43 | 44 | val defaultStructureTreeContext = StructureTreeContext(project) {} 45 | structureTreeContextPermutations.add(defaultStructureTreeContext) 46 | TypeUtils.TypeNameRenderMode.values() 47 | .filter { defaultStructureTreeContext.typeNameRenderMode != it } 48 | .forEach { 49 | structureTreeContextPermutations.add(StructureTreeContext(project) {}.apply { typeNameRenderMode = it }) 50 | } 51 | MethodDeclarationUtils.MethodDescriptorRenderMode.values() 52 | .filter { defaultStructureTreeContext.methodDescriptorRenderMode != it } 53 | .forEach { 54 | structureTreeContextPermutations.add(StructureTreeContext(project) {}.apply { methodDescriptorRenderMode = it }) 55 | } 56 | } 57 | 58 | @Test 59 | fun testFullStructureTreeCreation() { 60 | val classFileContext = DefaultClassFileContext(project, ClassFile(classFileAsVirtualFile, null), false) 61 | val tree = StructureTree(classFileContext, testRootDisposable) 62 | loadAllChildren(tree, tree.getChildren()) 63 | } 64 | 65 | private fun loadAllChildren(tree: StructureTree, children: List?) { 66 | children?.forEach { child -> 67 | if (child is ValueNode) { 68 | testValueNode(child) 69 | } 70 | 71 | // Calling `child.getChildren()` may not trigger the loading of all 72 | // children by the model if [StructureNode#willAlwaysHaveAsyncChildren] 73 | // is true. 74 | loadAllChildren(tree, tree.getChildren(child)) 75 | } ?: throw AssertionFailedError("Children not loaded.") 76 | } 77 | 78 | /** 79 | * Test generation of the ValueNode values. 80 | */ 81 | private fun testValueNode(valueNode: ValueNode) { 82 | structureTreeContextPermutations.forEach { structureTreeContext -> 83 | valueNode.displayValue(structureTreeContext) 84 | valueNode.rawValue(structureTreeContext) 85 | } 86 | } 87 | 88 | // -- Private Methods --------------------------------------------------------------------------------------------- // 89 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 90 | } -------------------------------------------------------------------------------- /testProject/README.md: -------------------------------------------------------------------------------- 1 | # Test Project 2 | 3 | -------------------------------------------------------------------------------- /testProject/src/main/java/foo/bar/JavaEnum.java: -------------------------------------------------------------------------------- 1 | // FileTestVector{baseFqClassNames: foo.bar.JavaEnum, containingFqClassNames: foo.bar.JavaEnum } 2 | package foo.bar; 3 | 4 | public enum JavaEnum { 5 | 6 | VALUE // PsiElementTestVector{reference: ENUM_VALUE|VALUE, baseFqClassName: foo.bar.JavaEnum, expectedFqClassNames: foo.bar.JavaEnum} 7 | } 8 | -------------------------------------------------------------------------------- /testProject/src/main/java/foo/bar/JavaNestedClasses.java: -------------------------------------------------------------------------------- 1 | // FileTestVector{baseFqClassNames: foo.bar.JavaNestedClasses, containingFqClassNames: foo.bar.JavaNestedClasses|foo.bar.JavaNestedClasses$Nested|foo.bar.JavaNestedClasses$Inner|foo.bar.JavaNestedClasses$Inner$Record } 2 | package foo.bar; 3 | 4 | import java.util.stream.IntStream; 5 | 6 | public class JavaNestedClasses { 7 | 8 | void method() { 9 | // PsiElementTestVector{reference: METHOD|JavaNestedClasses#method, baseFqClassName: foo.bar.JavaNestedClasses, expectedFqClassNames: foo.bar.JavaNestedClasses} 10 | } 11 | 12 | void methodWithLocalClass() { 13 | new Thread() { 14 | @Override 15 | public void run() { 16 | // PsiElementTestVector{reference: METHOD|JavaNestedClasses#methodWithLocalClass, baseFqClassName: foo.bar.JavaNestedClasses, expectedFqClassNames: foo.bar.JavaNestedClasses} 17 | } 18 | }; 19 | } 20 | 21 | void methodWithLambda(String input) { 22 | IntStream.of(1, 2, 3).forEach(it -> { 23 | // PsiElementTestVector{reference: LAMBDA|JavaNestedClasses#methodWithLambda, baseFqClassName: foo.bar.JavaNestedClasses, expectedFqClassNames: foo.bar.JavaNestedClasses, sourceFileOnly: true} 24 | System.out.println(input.length() + it); 25 | }); 26 | } 27 | 28 | public interface Nested { 29 | 30 | private void method() { 31 | // PsiElementTestVector{reference: METHOD|JavaNestedClasses$Nested#method, baseFqClassName: foo.bar.JavaNestedClasses, expectedFqClassNames: foo.bar.JavaNestedClasses$Nested} 32 | } 33 | } 34 | 35 | public class Inner { 36 | 37 | void method() { 38 | // PsiElementTestVector{reference: METHOD|JavaNestedClasses$Inner#method, baseFqClassName: foo.bar.JavaNestedClasses, expectedFqClassNames: foo.bar.JavaNestedClasses$Inner} 39 | } 40 | 41 | public record Record(String parameter) { 42 | 43 | void method() { 44 | // PsiElementTestVector{reference: METHOD|JavaNestedClasses$Inner$Record#method, baseFqClassName: foo.bar.JavaNestedClasses, expectedFqClassNames: foo.bar.JavaNestedClasses$Inner$Record} 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /testProject/src/main/java/foo/bar/package-info.java: -------------------------------------------------------------------------------- 1 | // FileTestVector{baseFqClassNames: package-info, containingFqClassNames:, sourceFileOnly: true } 2 | package foo.bar; -------------------------------------------------------------------------------- /testProject/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | // FileTestVector{baseFqClassNames: module-info, containingFqClassNames: module-info } 2 | module myModule { 3 | // If the test project gets opened with a real IntelliJ instance, this line 4 | // is required to make the project buildable. However, the unit tests will 5 | // fail if it is present since it can't find the module on the module path. 6 | // This is something, that should be investigated further... 7 | //requires kotlin.stdlib; 8 | 9 | exports foo.bar; // PsiElementTestVector{reference:MODULE_EXPORTS|foo.bar, baseFqClassName:module-info, expectedFqClassNames:module-info} 10 | } -------------------------------------------------------------------------------- /testProject/src/main/kotlin/KotlinEmptyFile.kt: -------------------------------------------------------------------------------- 1 | // There is no class file for this source file. But since we do not check if the 2 | // file is actually empty (which would be complex), there will be a 3 | // `ClassFilePreparationTask` for this source file. Therefore we have the 4 | // `expectedFqClassNames`. 5 | // FileTestVector{baseFqClassNames: KotlinEmptyFileKt, containingFqClassNames:, sourceFileOnly: true, emptyKotlinFile: true } 6 | // PsiElementTestVector{reference: BEGIN_OF_FILE, baseFqClassName: KotlinEmptyfile, expectedFqClassNames: KotlinEmptyFileKt, sourceFileOnly: true, emptyKotlinFile: true } -------------------------------------------------------------------------------- /testProject/src/main/kotlin/KotlinFileWithTwoClasses.kt: -------------------------------------------------------------------------------- 1 | // FileTestVector{baseFqClassNames: KotlinFirst|KotlinSecond, containingFqClassNames: KotlinFirst|KotlinSecond } 2 | // PsiElementTestVector{reference: BEGIN_OF_FILE, baseFqClassName: KotlinFirst, expectedFqClassNames: KotlinFirst|KotlinSecond, sourceFileOnly: true} 3 | 4 | class KotlinFirst { 5 | 6 | fun method() { 7 | // PsiElementTestVector{reference: METHOD|KotlinFirst#method, baseFqClassName: KotlinFirst, expectedFqClassNames: KotlinFirst} 8 | } 9 | } 10 | 11 | class KotlinSecond { 12 | 13 | fun method() { 14 | // PsiElementTestVector{reference: METHOD|KotlinSecond#method, baseFqClassName: KotlinSecond, expectedFqClassNames: KotlinSecond} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /testProject/src/main/kotlin/KotlinFileWithoutClassAndPackage.kt: -------------------------------------------------------------------------------- 1 | // FileTestVector{baseFqClassNames: KotlinFileWithoutClassAndPackageKt, containingFqClassNames: KotlinFileWithoutClassAndPackageKt } 2 | fun method() { 3 | // PsiElementTestVector{reference: METHOD|#method, baseFqClassName: KotlinFileWithoutClassAndPackageKt, expectedFqClassNames: KotlinFileWithoutClassAndPackageKt} 4 | } 5 | -------------------------------------------------------------------------------- /testProject/src/main/kotlin/foo/bar/baz/KotlinEnum.kt: -------------------------------------------------------------------------------- 1 | // FileTestVector{baseFqClassNames: foo.bar.baz.KotlinEnum, containingFqClassNames: foo.bar.baz.KotlinEnum } 2 | package foo.bar.baz 3 | 4 | enum class KotlinEnum { 5 | // -- Properties -------------------------------------------------------------------------------------------------- // 6 | 7 | VALUE // PsiElementTestVector{reference: ENUM_VALUE|VALUE, baseFqClassName: foo.bar.baz.KotlinEnum, expectedFqClassNames: foo.bar.baz.KotlinEnum} 8 | 9 | // -- Variables --------------------------------------------------------------------------------------------------- // 10 | // -- Initialization ---------------------------------------------------------------------------------------------- // 11 | // -- Exported Methods -------------------------------------------------------------------------------------------- // 12 | // -- Private Methods --------------------------------------------------------------------------------------------- // 13 | // -- Inner Type -------------------------------------------------------------------------------------------------- // 14 | // -- Companion Object -------------------------------------------------------------------------------------------- // 15 | } -------------------------------------------------------------------------------- /testProject/src/main/kotlin/foo/bar/baz/KotlinFileWithoutClass.kt: -------------------------------------------------------------------------------- 1 | // FileTestVector{baseFqClassNames: foo.bar.baz.KotlinFileWithoutClassKt, containingFqClassNames: foo.bar.baz.KotlinFileWithoutClassKt } 2 | package foo.bar.baz; 3 | 4 | fun method() { 5 | // PsiElementTestVector{reference: METHOD|#method, baseFqClassName: foo.bar.baz.KotlinFileWithoutClassKt, expectedFqClassNames: foo.bar.baz.KotlinFileWithoutClassKt} 6 | } 7 | -------------------------------------------------------------------------------- /testProject/src/main/kotlin/foo/bar/baz/KotlinNestedClasses.kt: -------------------------------------------------------------------------------- 1 | // FileTestVector{baseFqClassNames: foo.bar.baz.KotlinNestedClasses, containingFqClassNames: foo.bar.baz.KotlinNestedClasses|foo.bar.baz.KotlinNestedClasses$method with local and anonymous class$1|foo.bar.baz.KotlinNestedClasses$method with local and anonymous class$LocalClass|foo.bar.baz.KotlinNestedClasses$method with local and anonymous class$myObject$1|foo.bar.baz.KotlinNestedClasses$method with local and anonymous class$myObject$1|foo.bar.baz.KotlinNestedClasses$Inner|foo.bar.baz.KotlinNestedClasses$Nested|foo.bar.baz.KotlinNestedClasses$Nested$Companion|foo.bar.baz.KotlinNestedClasses$Companion } 2 | package foo.bar.baz; 3 | 4 | import java.util.stream.IntStream 5 | 6 | class KotlinNestedClasses { 7 | 8 | fun method() { 9 | // PsiElementTestVector{reference: METHOD|KotlinNestedClasses#method, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses} 10 | } 11 | 12 | fun `kotlin lambda`(input: String) { 13 | // The SDK will return #lambdas$result$1 as a class name but the Kotlin 14 | // compiler will optimize this code without an additional class. 15 | val result = listOf(1, 2, 3).minOfOrNull { 16 | // `sourceFileOnly` because decompiler can't restore method body 17 | // PsiElementTestVector{reference: LAMBDA|KotlinNestedClasses#kotlin lambda, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses$kotlin lambda$result$1[foo.bar.baz.KotlinNestedClasses], sourceFileOnly: true} 18 | input.length + it 19 | } 20 | } 21 | 22 | fun `java lambda in kotlin code`(input: String) { 23 | IntStream.of(1, 2, 3).forEach { 24 | // `sourceFileOnly` because decompiler can't restore method body 25 | // PsiElementTestVector{reference: LAMBDA|KotlinNestedClasses#java lambda in kotlin code, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses$java lambda in kotlin code$1[foo.bar.baz.KotlinNestedClasses], sourceFileOnly: true} 26 | println(input.length + it) 27 | } 28 | } 29 | 30 | fun `method with local and anonymous class`() { 31 | val myObject = object { 32 | fun methodInObjectWithVariable() { 33 | // PsiElementTestVector{reference: METHOD|KotlinNestedClasses#methodInObjectWithVariable, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses$method with local and anonymous class$myObject$1[foo.bar.baz.KotlinNestedClasses], sourceFileOnly: true} 34 | } 35 | } 36 | 37 | class LocalClass { 38 | fun methodInLocalClass() { 39 | // PsiElementTestVector{reference: METHOD|KotlinNestedClasses#methodInLocalClass, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses$method with local and anonymous class$LocalClass, sourceFileOnly: true} 40 | } 41 | } 42 | 43 | object: Runnable { 44 | override fun run() { 45 | // PsiElementTestVector{reference: METHOD|KotlinNestedClasses#run, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses$method with local and anonymous class$1[foo.bar.baz.KotlinNestedClasses], sourceFileOnly: true} 46 | } 47 | } 48 | } 49 | 50 | inner class Inner { 51 | 52 | fun method() { 53 | // PsiElementTestVector{reference: METHOD|KotlinNestedClasses$Inner#method, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses$Inner} 54 | } 55 | } 56 | 57 | interface Nested { 58 | 59 | private fun method() { 60 | // PsiElementTestVector{reference: METHOD|KotlinNestedClasses$Nested#method, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses$Nested[foo.bar.baz.KotlinNestedClasses$Nested$DefaultImpls]} 61 | } 62 | 63 | companion object { 64 | 65 | fun method() { 66 | // PsiElementTestVector{reference: METHOD|KotlinNestedClasses$Nested$Companion#method, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses$Nested$Companion} 67 | } 68 | } 69 | } 70 | 71 | companion object { 72 | 73 | fun method() { 74 | // PsiElementTestVector{reference: METHOD|KotlinNestedClasses$Companion#method, baseFqClassName: foo.bar.baz.KotlinNestedClasses, expectedFqClassNames: foo.bar.baz.KotlinNestedClasses$Companion} 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /testProject/testProject.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------