├── .gitignore ├── JarEditor.iml ├── LICENSE ├── README.md ├── README_CN.md ├── build.gradle.kts ├── count_lines.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── JarEditor_demo.gif ├── JarEditor_install.png ├── JarEditor_javassist.png ├── JarEditor_main.png ├── JarEditor_new_delete.png ├── JarEditor_search.png ├── logo.png └── vcb_main.png ├── settings.gradle.kts └── src ├── main ├── java │ ├── com │ │ └── liubs │ │ │ └── jareditor │ │ │ ├── action │ │ │ ├── CompressionMethod.java │ │ │ ├── ExpandNestedJarAction.java │ │ │ ├── JarEditorAddDirectory.java │ │ │ ├── JarEditorAddJar.java │ │ │ ├── JarEditorAddJarInJar.java │ │ │ ├── JarEditorAddJavaFile.java │ │ │ ├── JarEditorAddKotlinFile.java │ │ │ ├── JarEditorAddManifest.java │ │ │ ├── JarEditorAddResourceFile.java │ │ │ ├── JarEditorBackup.java │ │ │ ├── JarEditorClear.java │ │ │ ├── JarEditorCopyFile.java │ │ │ ├── JarEditorDeleteFiles.java │ │ │ ├── JarEditorExportSourceJar.java │ │ │ ├── JarEditorPasteFile.java │ │ │ ├── JarEditorRefactorClass.java │ │ │ ├── JarEditorRenameFile.java │ │ │ ├── JarEditorReset.java │ │ │ ├── JarEditorSearch.java │ │ │ ├── JavaEditorAddFile.java │ │ │ ├── JavassistAction.java │ │ │ ├── MyToolbarAction.java │ │ │ ├── RefreshFileTree.java │ │ │ └── VcbEditorAction.java │ │ │ ├── backup │ │ │ ├── Backup.java │ │ │ ├── BackupData.java │ │ │ ├── BackupDialog.java │ │ │ ├── ChangeData.java │ │ │ ├── ChangeItem.java │ │ │ └── ChangeType.java │ │ │ ├── bytestool │ │ │ ├── ToClassFile.java │ │ │ ├── javassist │ │ │ │ ├── ClassInitializerSignature.java │ │ │ │ ├── ConstructorSignature.java │ │ │ │ ├── FieldSignature.java │ │ │ │ ├── ISignature.java │ │ │ │ ├── JavassistClassHolder.java │ │ │ │ ├── JavassistDialog.java │ │ │ │ ├── MethodSignature.java │ │ │ │ └── TargetUnit.java │ │ │ └── vcb │ │ │ │ └── GotoVisualClassBytesEditor.java │ │ │ ├── clipboard │ │ │ ├── ClipboardToFile.java │ │ │ ├── CopyResult.java │ │ │ └── FileToClipBoard.java │ │ │ ├── compile │ │ │ ├── CommandParam.java │ │ │ ├── CompilationResult.java │ │ │ ├── IMyCompiler.java │ │ │ ├── JavaSourceObject.java │ │ │ ├── MyJBRJavacCompiler.java │ │ │ ├── MyJavaFileManager.java │ │ │ ├── MyJavacCompiler.java │ │ │ ├── MyKotlincCompiler.java │ │ │ ├── MyRuntimeCompiler.java │ │ │ ├── MySDKDefaultKotlincCompiler.java │ │ │ └── ProcessCommandCompiler.java │ │ │ ├── constant │ │ │ ├── ClassVersion.java │ │ │ ├── JarConstant.java │ │ │ └── PathConstant.java │ │ │ ├── decompile │ │ │ ├── CFRDecompiler.java │ │ │ ├── DecompiledEnum.java │ │ │ ├── IDecompiler.java │ │ │ ├── IdeaDecompiler.java │ │ │ ├── MyDecompiler.java │ │ │ └── ProcyonDecompiler.java │ │ │ ├── dependency │ │ │ ├── ExtraDependencyManager.java │ │ │ ├── IDependencyHandler.java │ │ │ ├── NestedJarDependency.java │ │ │ └── SpringBootDependency.java │ │ │ ├── editor │ │ │ ├── BuildJarSelection.java │ │ │ ├── JarEditorCore.java │ │ │ ├── LanguageType.java │ │ │ ├── MyFileEditorProvider.java │ │ │ ├── MyJarEditor.java │ │ │ ├── ProcessCompilerFactory.java │ │ │ └── SourceJarResolver.java │ │ │ ├── jarbuild │ │ │ ├── ChangedItemCallBack.java │ │ │ ├── JarBuildResult.java │ │ │ ├── JarBuilder.java │ │ │ ├── JarRefactor.java │ │ │ └── PackageRemapper.java │ │ │ ├── persistent │ │ │ ├── BackupStorage.java │ │ │ └── SDKSettingStorage.java │ │ │ ├── sdk │ │ │ ├── JavacToolProvider.java │ │ │ ├── MessageDialog.java │ │ │ ├── NoticeInfo.java │ │ │ ├── ProjectDependency.java │ │ │ ├── SDKManager.java │ │ │ └── SDKSettingDialog.java │ │ │ ├── search │ │ │ ├── JarFileSearchDialog.java │ │ │ ├── SearchAllJarPanel.java │ │ │ ├── SearchInJarPanel.java │ │ │ └── SearchResultItem.java │ │ │ ├── structure │ │ │ ├── CompressionMethodDialog.java │ │ │ ├── JarTreeStructureProvider.java │ │ │ ├── NestedJar.java │ │ │ ├── NestedJarChangedListener.java │ │ │ ├── NestedJarDirNode.java │ │ │ └── NestedJarHolder.java │ │ │ ├── template │ │ │ ├── DefaultParser.java │ │ │ ├── ITextParser.java │ │ │ ├── JavaTextParser.java │ │ │ ├── KotlinTextParser.java │ │ │ ├── TemplateManager.java │ │ │ └── TemplateType.java │ │ │ └── util │ │ │ ├── CommandTools.java │ │ │ ├── DateUtil.java │ │ │ ├── ExceptionUtil.java │ │ │ ├── JarUtil.java │ │ │ ├── JavaFileUtil.java │ │ │ ├── JsonUtil.java │ │ │ ├── Md5Util.java │ │ │ ├── MyFileUtil.java │ │ │ ├── MyPathUtil.java │ │ │ ├── OSUtil.java │ │ │ ├── PsiFileUtil.java │ │ │ ├── ScheduleUtil.java │ │ │ └── StringUtils.java │ └── icons │ │ └── MyIcons.java └── resources │ ├── META-INF │ ├── plugin.xml │ ├── pluginIcon.svg │ └── pluginIcon_dark.svg │ ├── icons │ ├── classKotlin.svg │ ├── clean.svg │ ├── reset.svg │ └── tool.svg │ └── template │ ├── MANIFEST.template │ ├── java.template │ ├── kotlin.template │ └── xml.template └── test └── java └── javassist └── target └── TestClass.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | out 5 | 6 | -------------------------------------------------------------------------------- /JarEditor.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 |

JarEditor liubsyy

7 | 8 |

English | 简体中文

9 | 10 | [![License](https://img.shields.io/github/license/Liubsyy/JarEditor?color=blue)](./LICENSE) 11 | [![downloads](https://img.shields.io/jetbrains/plugin/d/24397)](https://plugins.jetbrains.com/plugin/24397-jareditor) 12 | [![release](https://img.shields.io/jetbrains/plugin/v/24397?label=version)](https://plugins.jetbrains.com/plugin/24397-jareditor) 13 | ![sdk](https://img.shields.io/badge/plugin%20sdk-IDEA%202020.3-red.svg) 14 | Featured|HelloGitHub 15 | 16 | IDEA plugin for modifying files in jar directly without decompression, including class and resource files. 17 | 18 | **Plugin marketplace** : [https://plugins.jetbrains.com/plugin/24397-jareditor](https://plugins.jetbrains.com/plugin/24397-jareditor) 19 | 20 | ## Features 21 | - Edit class/resource file in jar directly without decompression 22 | - Add/Delete/Rename file/directory in jar 23 | - Search the contents of the jar package 24 | - Copy/Paste file/directory to/from clipboard 25 | - Support SpringBoot jar/nested jar 26 | - Support kotlin 27 | - Export source jar 28 | - Support class bytes tool : Javassist/VisualClassBytes 29 | - Decompilers : Fernflower/CFR/Procyon 30 | 31 | 32 | 33 | ## Quick start 34 | 35 | ### 1. Install plugin from marketplace 36 | First install the plugin JarEditor from marketplace, IDEA at least version **2020.3** 37 | 38 | 39 | 40 | 41 | ### 2. Edit and Build Jar 42 | After installation , you can see a tab page to switch to Jar Editor in the .class decompiled file. 43 | 44 | > **External jar** : File->Project Structure->Libraries->Add Library , then you can see the decompiled jar.
45 | > **Nested jar** : Right click on nested jar->JarEditor->Structure->Expand Nested Jar 46 | 47 | 48 | 49 | After modification, click **Save(Compile)** to compile and save the currently modified java content. 50 | 51 | Finally click **Build Jar** to write the compiled and saved class file into the Jar package. 52 | 53 | Modifying the resource files in the jar package is also supported. 54 | 55 | Here is an example: 56 | 57 | 58 | 59 | ### 3. Class bytes tool 60 | For obfuscated jars, the decompilation result is not satisfactory. In this case, you can use tools to directly modify the bytecode. 61 | Click the **Class bytes tool** icon to select the tool 62 | 63 | - **Javassist** : Field/method/constructor/static code can be modified/added/deleted (include inner class) 64 | 65 | 66 | 67 | 68 | - [**Visual ClassBytes**](https://github.com/Liubsyy/VisualClassBytes) : Class bytecode editor,based on ASM and BCEL 69 | 70 | 71 | 72 | 73 | ### 4. Other operations of JarEditor 74 | In the project view of the jar package, right-click to see **JarEditor->New/Delete** and other operations, where you can add/delete/rename/copy/paste/export/backup files. 75 | 76 | 77 | 78 | 79 | Click the **Search** icon to search the contents of the jar package. If it is a class jar, it will be searched based on the decompiled content. 80 | 81 | 82 | 83 | ## Some mechanisms 84 | - The JDK that the compilation depends on is the JDK of your SDK list. You can choose SDK and target version of the compiled class. 85 | - The classpath you depend on when compiling java is the project's Libraries dependency. If the dependency package cannot be found, you can add Libraries(File->Project Structure->Libraries). 86 | - Save(Compile) will save the modified files to the subdirectory **jar_edit_out** of the directory where the jar package is located. Build Jar will incrementally write the modified files to jar, and finally delete this directory. 87 | 88 | 89 | ### SDK Default Versions 90 | 91 | When compiling and selecting **SDK Default**, the runtime JDK (JBR) integrated by Jetbrains is used. If SDK Default is not selected, the JDK installed by the specific user will be used. 92 | 93 | IDEA|JDK 94 | ---|--- 95 | IDEA 2020.3 - IDEA 2022.1 |JBR JDK11 96 | IDEA 2022.2 - IDEA 2024.1 |JBR JDK17 97 | IDEA 2024.2 and later |JBR JDK21 98 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 |

JarEditor liubsyy

7 | 8 |

English | 简体中文

9 | 10 | [![License](https://img.shields.io/github/license/Liubsyy/JarEditor?color=blue)](./LICENSE) 11 | [![downloads](https://img.shields.io/jetbrains/plugin/d/24397)](https://plugins.jetbrains.com/plugin/24397-jareditor) 12 | [![release](https://img.shields.io/jetbrains/plugin/v/24397?label=version)](https://plugins.jetbrains.com/plugin/24397-jareditor) 13 | ![sdk](https://img.shields.io/badge/plugin%20sdk-IDEA%202020.3-red.svg) 14 | Featured|HelloGitHub 15 | 16 | 可直接修改jar包内文件的IDEA插件,无需解压 17 | 18 | **Plugin marketplace** : [https://plugins.jetbrains.com/plugin/24397-jareditor](https://plugins.jetbrains.com/plugin/24397-jareditor) 19 | 20 | ## 功能 21 | - 直接编辑jar包内class/resource文件,无需解压 22 | - 添加/删除/重命名jar包内文件/文件夹 23 | - 搜索jar包的内容 24 | - jar内复制/粘贴文件到外部剪切板 25 | - 支持SpringBoot jar/嵌套jar 26 | - 支持kotlin 27 | - 可导出source jar 28 | - 支持class字节码修改工具 : Javassist/VisualClassBytes 29 | - 反编译器 : Fernflower/CFR/Procyon 30 | 31 | ## 快速开始 32 | 33 | ### 1. 从插件市场安装插件 34 | 首先从市场安装插件 JarEditor,IDEA版本 >= **2020.3** 35 | 36 | 37 | 38 | 39 | ### 2. 编辑并构建 Jar 40 | 安装完成后,在.class反编译文件中可以看到切换到Jar Editor的tab页。 41 | 42 | > **外部jar** :File->Project Structure->Libraries->Add Library,然后就可以看到反编译的jar了。
43 | > **嵌套jar** : 嵌套jar上右键->JarEditor->Structure->Expand Nested Jar 44 | 45 | 46 | 47 | 修改完成后,点击**Save(Compile)**,编译并保存当前修改的java内容。 48 | 49 | 最后点击**Build Jar**,将编译保存的类文件写入Jar包中。 50 | 51 | 修改jar包中的资源文件也是支持的。 52 | 53 | 下面是一个演示例子: 54 | 55 | 56 | 57 | ### 3. 修改字节码工具 58 | 针对混淆jar,反编译的效果不是很好,此时可以使用直接修改字节码工具 59 | 点击 **Class bytes tool** 图标选择工具 60 | 61 | - **Javassist** : 可以对字段/方法/构造函数/静态代码块进行增删改 (包括内部类) 62 | 63 | 64 | 65 | - [**Visual ClassBytes**](https://github.com/Liubsyy/VisualClassBytes) : Class字节码编辑器,基于 ASM 和 BCEL 66 | 67 | 68 | 69 | ### 4. 其他操作 70 | 在jar包的项目视图中,右键可以看到**JarEditor->New/Delete**等操作,可以在jar内添加/删除/重命名/复制/粘贴/导出/备份文件。 71 | 72 | 73 | 74 | 点击 **Search** 图标,可以搜索jar包的内容,如果是class jar将根据反编译的内容进行搜索 75 | 76 | 77 | 78 | 79 | 80 | ## 一些机制 81 | - 编译依赖的JDK是你的SDK列表中的JDK。您可以选择SDK和编译类的目标版本。 82 | - 编译java时所依赖的classpath就是项目的Libraries依赖。如果找不到依赖包,可以添加Libraries(File->Project Structure->Libraries)。 83 | - Save(Compile)会将修改后的文件保存到jar包所在目录的子目录**jar_edit_out**中,Build Jar会将修改的文件增量写入jar中,最后删除这个临时目录。 84 | 85 | ### SDK Default对应JDK版本 86 | 87 | 编译选择 **SDK Default** 时,使用的是Jetbrains集成的运行时JDK(JBR),如果不选SDK Default则是具体用户安装的JDK 88 | 89 | IDEA|JDK 90 | ---|--- 91 | IDEA 2020.3 - IDEA 2022.1 |JBR JDK11 92 | IDEA 2022.2 - IDEA 2024.1 |JBR JDK17 93 | IDEA 2024.2 及更高版本 |JBR JDK21 94 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | // Java support 4 | id("java") 5 | // gradle-intellij-plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin 6 | id("org.jetbrains.intellij") version "0.7.3" 7 | } 8 | 9 | // Import variables from gradle.properties file 10 | val pluginGroup: String by project 11 | val pluginName_: String by project 12 | val pluginVersion: String by project 13 | val pluginSinceBuild: String by project 14 | val pluginUntilBuild: String by project 15 | val pluginVerifierIdeVersions: String by project 16 | val platformType: String by project 17 | val platformVersion: String by project 18 | val platformPlugins: String by project 19 | val platformDownloadSources: String by project 20 | val sourceVersion: String by project 21 | val targetVersion: String by project 22 | 23 | group = pluginGroup 24 | 25 | // Configure project's dependencies 26 | repositories { 27 | mavenCentral() 28 | // jcenter() 29 | } 30 | dependencies { 31 | // https://mvnrepository.com/artifact/org.javassist/javassist 32 | implementation("org.javassist:javassist:3.30.2-GA") 33 | 34 | // https://mvnrepository.com/artifact/org.ow2.asm/asm 35 | implementation("org.ow2.asm:asm:9.7") 36 | 37 | // https://mvnrepository.com/artifact/org.ow2.asm/asm-commons 38 | implementation("org.ow2.asm:asm-commons:9.7") 39 | 40 | // https://mvnrepository.com/artifact/org.benf/cfr 41 | implementation("org.benf:cfr:0.152") 42 | 43 | // https://mvnrepository.com/artifact/org.bitbucket.mstrobel/procyon-compilertools 44 | implementation("org.bitbucket.mstrobel:procyon-compilertools:0.6.0") 45 | 46 | // https://mvnrepository.com/artifact/com.google.code.gson/gson 47 | implementation("com.google.code.gson:gson:2.8.9") 48 | } 49 | 50 | 51 | intellij { 52 | pluginName = pluginName_ 53 | version = platformVersion 54 | type = platformType 55 | downloadSources = platformDownloadSources.toBoolean() 56 | updateSinceUntilBuild = false 57 | 58 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. 59 | setPlugins(*platformPlugins.split(',').map(String::trim).filter(String::isNotEmpty).toTypedArray()) 60 | } 61 | 62 | 63 | tasks { 64 | withType { 65 | sourceCompatibility = sourceVersion 66 | targetCompatibility = targetVersion 67 | options.encoding = "UTF-8" 68 | } 69 | 70 | patchPluginXml { 71 | sinceBuild(pluginSinceBuild) 72 | untilBuild(pluginUntilBuild) 73 | } 74 | 75 | buildSearchableOptions{ 76 | enabled = false 77 | } 78 | 79 | publishPlugin { 80 | token(System.getenv("PUBLISH_TOKEN")) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /count_lines.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Count the number of lines of java code 4 | find ./ -name "*.java" -print0 | xargs -0 cat | wc -l -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories 2 | # -> https://www.jetbrains.org/intellij/sdk/docs/reference_guide/intellij_artifacts.html 3 | 4 | pluginGroup = com.liubs 5 | pluginName_ = JarEditor 6 | pluginSinceBuild = 203 7 | pluginUntilBuild = 8 | 9 | sourceVersion = 11 10 | targetVersion = 11 11 | 12 | platformType = IC 13 | platformVersion = 2020.3 14 | platformDownloadSources = false 15 | # Plugin Dependencies -> https://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_dependencies.html 16 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 17 | platformPlugins =com.intellij.java 18 | 19 | # Opt-out flag for bundling Kotlin standard library. 20 | # See https://kotlinlang.org/docs/reference/using-gradle.html#dependency-on-the-standard-library for details. 21 | kotlin.stdlib.default.dependency = false 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liubsyy/JarEditor/a47dbfcf6ac67d0add79402f9a9a38dd676ea917/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-6.7.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /img/JarEditor_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liubsyy/JarEditor/a47dbfcf6ac67d0add79402f9a9a38dd676ea917/img/JarEditor_demo.gif -------------------------------------------------------------------------------- /img/JarEditor_install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liubsyy/JarEditor/a47dbfcf6ac67d0add79402f9a9a38dd676ea917/img/JarEditor_install.png -------------------------------------------------------------------------------- /img/JarEditor_javassist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liubsyy/JarEditor/a47dbfcf6ac67d0add79402f9a9a38dd676ea917/img/JarEditor_javassist.png -------------------------------------------------------------------------------- /img/JarEditor_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liubsyy/JarEditor/a47dbfcf6ac67d0add79402f9a9a38dd676ea917/img/JarEditor_main.png -------------------------------------------------------------------------------- /img/JarEditor_new_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liubsyy/JarEditor/a47dbfcf6ac67d0add79402f9a9a38dd676ea917/img/JarEditor_new_delete.png -------------------------------------------------------------------------------- /img/JarEditor_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liubsyy/JarEditor/a47dbfcf6ac67d0add79402f9a9a38dd676ea917/img/JarEditor_search.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liubsyy/JarEditor/a47dbfcf6ac67d0add79402f9a9a38dd676ea917/img/logo.png -------------------------------------------------------------------------------- /img/vcb_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liubsyy/JarEditor/a47dbfcf6ac67d0add79402f9a9a38dd676ea917/img/vcb_main.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "JarEditor" 2 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/CompressionMethod.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.CommonDataKeys; 6 | import com.intellij.openapi.progress.ProgressIndicator; 7 | import com.intellij.openapi.progress.ProgressManager; 8 | import com.intellij.openapi.progress.Task; 9 | import com.intellij.openapi.project.Project; 10 | import com.intellij.openapi.ui.ComboBox; 11 | import com.intellij.openapi.ui.DialogWrapper; 12 | import com.intellij.openapi.vfs.VirtualFile; 13 | import com.intellij.openapi.vfs.VirtualFileManager; 14 | import com.intellij.util.PathUtil; 15 | import com.liubs.jareditor.structure.CompressionMethodDialog; 16 | import com.liubs.jareditor.structure.NestedJar; 17 | import com.liubs.jareditor.jarbuild.JarBuildResult; 18 | import com.liubs.jareditor.jarbuild.JarBuilder; 19 | import com.liubs.jareditor.sdk.NoticeInfo; 20 | import com.liubs.jareditor.util.MyPathUtil; 21 | import org.jetbrains.annotations.NotNull; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | import javax.swing.*; 25 | import java.awt.*; 26 | import java.io.IOException; 27 | import java.util.jar.JarEntry; 28 | import java.util.jar.JarFile; 29 | import java.util.zip.ZipEntry; 30 | 31 | /** 32 | * jar entry的压缩方式 33 | * @author Liubsyy 34 | * @date 2024/10/14 35 | */ 36 | public class CompressionMethod extends AnAction { 37 | @Override 38 | public void actionPerformed(@NotNull AnActionEvent e) { 39 | VirtualFile selectedFile = e.getData(CommonDataKeys.VIRTUAL_FILE); 40 | Project project = e.getProject(); 41 | if(project == null) { 42 | NoticeInfo.warning("Please open a project"); 43 | return; 44 | } 45 | if(null == selectedFile) { 46 | NoticeInfo.warning("No file selected"); 47 | return; 48 | } 49 | if("jar".equals(selectedFile.getExtension()) && selectedFile.getPath().contains(NestedJar.KEY)) { 50 | String originalPath = PathUtil.getLocalPath(selectedFile.getPath()).replaceFirst(NestedJar.KEY,".jar!"); 51 | selectedFile = VirtualFileManager.getInstance().findFileByUrl("jar://"+originalPath); 52 | if(null == selectedFile){ 53 | return; 54 | } 55 | } 56 | 57 | String entryPathFromJar = MyPathUtil.getEntryPathFromJar(selectedFile.getPath()); 58 | if(null == entryPathFromJar) { 59 | return; 60 | } 61 | final String jarPath = MyPathUtil.getJarPathFromJar(selectedFile.getPath()); 62 | 63 | int method = -1; 64 | try(JarFile jarFile = new JarFile(jarPath)){ 65 | ZipEntry entry = jarFile.getEntry(entryPathFromJar); 66 | method = entry.getMethod(); 67 | } catch (IOException ioException) { 68 | ioException.printStackTrace(); 69 | } 70 | CompressionMethodDialog dialog = new CompressionMethodDialog(entryPathFromJar,method); 71 | if(dialog.showAndGet()){ 72 | int selectedMethod = dialog.getSelectedMethod(); 73 | if(selectedMethod == method) { 74 | return; 75 | } 76 | 77 | ProgressManager.getInstance().run(new Task.Backgroundable(null, "Change compression method...", false) { 78 | @Override 79 | public void run(@NotNull ProgressIndicator progressIndicator) { 80 | try { 81 | JarBuilder jarBuilder = new JarBuilder(jarPath); 82 | JarBuildResult jarBuildResult = jarBuilder.setCompressionMethod(entryPathFromJar,selectedMethod); 83 | if(!jarBuildResult.isSuccess()) { 84 | NoticeInfo.error("Change Compression err: \n%s",jarBuildResult.getErr()); 85 | return; 86 | } 87 | VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); 88 | NoticeInfo.info("Change success, compression method=%s",selectedMethod == JarEntry.STORED ? "STORED" : "DEFLATED"); 89 | }catch (Throwable e) { 90 | NoticeInfo.error("Add file err",e); 91 | } 92 | } 93 | }); 94 | } 95 | } 96 | 97 | 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/ExpandNestedJarAction.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.ide.projectView.ProjectView; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import com.intellij.openapi.actionSystem.CommonDataKeys; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.LocalFileSystem; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import com.liubs.jareditor.sdk.NoticeInfo; 11 | import com.liubs.jareditor.util.JarUtil; 12 | import com.liubs.jareditor.util.MyPathUtil; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.io.IOException; 16 | import java.nio.file.Path; 17 | import java.nio.file.Paths; 18 | import java.util.ArrayList; 19 | import java.util.Enumeration; 20 | import java.util.List; 21 | import java.util.jar.JarEntry; 22 | import java.util.jar.JarFile; 23 | 24 | /** 25 | * 展开嵌套jar结构 26 | * @author Liubsyy 27 | * @date 2024/10/13 28 | */ 29 | public class ExpandNestedJarAction extends AnAction { 30 | @Override 31 | public void actionPerformed(@NotNull AnActionEvent e) { 32 | VirtualFile selectedFile = e.getData(CommonDataKeys.VIRTUAL_FILE); 33 | Project project = e.getProject(); 34 | if(project == null) { 35 | NoticeInfo.warning("Please open a project"); 36 | return; 37 | } 38 | if(null == selectedFile) { 39 | NoticeInfo.warning("No file selected"); 40 | return; 41 | } 42 | 43 | boolean isDirectory = selectedFile.isDirectory(); 44 | if(!isDirectory && !"jar".equalsIgnoreCase(selectedFile.getExtension())) { 45 | return; 46 | } 47 | 48 | String jarPath = MyPathUtil.getJarFullPath(selectedFile.getPath()); 49 | String entryPath = MyPathUtil.getEntryPathFromJar(selectedFile.getPath()); 50 | 51 | String nestedJarBasePath = MyPathUtil.getNestedJarPath(selectedFile.getPath()); 52 | if(null == jarPath || null == nestedJarBasePath) { 53 | return; 54 | } 55 | List destPaths = new ArrayList<>(); 56 | try (JarFile jarFile = new JarFile(jarPath)) { 57 | Enumeration entries = jarFile.entries(); 58 | while (entries.hasMoreElements()) { 59 | JarEntry entry = entries.nextElement(); 60 | String entryName = entry.getName(); 61 | 62 | //nested jar 63 | if(entryName.endsWith(".jar")) { 64 | 65 | boolean createDstJar; 66 | if(null == entryPath) { 67 | createDstJar = true; 68 | }else if(isDirectory) { 69 | createDstJar = entryName.startsWith(entryPath); 70 | }else { 71 | createDstJar = entryName.equals(entryPath); 72 | } 73 | 74 | if(createDstJar) { 75 | Path destPath = Paths.get(nestedJarBasePath, entry.toString()); 76 | JarUtil.createFile(jarFile,entry,destPath); 77 | destPaths.add(destPath.toString()); 78 | } 79 | } 80 | } 81 | 82 | } catch (IOException ioException) { 83 | ioException.printStackTrace(); 84 | } 85 | 86 | destPaths.forEach(c->{ 87 | VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(c); 88 | if(null != virtualFile) { 89 | virtualFile.refresh(false, false); 90 | } 91 | }); 92 | 93 | if(!destPaths.isEmpty()) { 94 | //这行代码会触发 JarTreeStructureProvider 重新刷新 95 | ProjectView.getInstance(project).refresh(); 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorAddDirectory.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.Messages; 5 | import com.liubs.jareditor.util.StringUtils; 6 | 7 | /** 8 | * 新增文件夹 9 | * @author Liubsyy 10 | * @date 2024/5/14 11 | */ 12 | public class JarEditorAddDirectory extends JavaEditorAddFile { 13 | @Override 14 | protected String preInput(Project project, String entryPathFromJar) { 15 | String userInput = Messages.showInputDialog( 16 | project, 17 | "Enter name for new directory:", 18 | "Create New Directory", 19 | Messages.getQuestionIcon() 20 | ); 21 | if(StringUtils.isEmpty(userInput)) { 22 | return null; 23 | } 24 | if(null == entryPathFromJar) { 25 | return userInput+"/"; 26 | } 27 | 28 | //文件夹以/结尾 29 | return entryPathFromJar+"/"+userInput+"/"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorAddJar.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.CommonDataKeys; 6 | import com.intellij.openapi.application.ApplicationManager; 7 | import com.intellij.openapi.progress.ProgressIndicator; 8 | import com.intellij.openapi.progress.ProgressManager; 9 | import com.intellij.openapi.progress.Task; 10 | import com.intellij.openapi.project.Project; 11 | import com.intellij.openapi.ui.Messages; 12 | import com.intellij.openapi.vfs.LocalFileSystem; 13 | import com.intellij.openapi.vfs.VirtualFile; 14 | import com.liubs.jareditor.jarbuild.JarBuilder; 15 | import com.liubs.jareditor.sdk.NoticeInfo; 16 | import com.liubs.jareditor.template.TemplateManager; 17 | import com.liubs.jareditor.util.StringUtils; 18 | import org.jetbrains.annotations.NotNull; 19 | 20 | import java.io.File; 21 | import java.io.FileOutputStream; 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.jar.JarEntry; 24 | import java.util.jar.JarOutputStream; 25 | 26 | /** 27 | * 新增一个jar文件 28 | * @author Liubsyy 29 | * @date 2024/11/25 30 | */ 31 | public class JarEditorAddJar extends AnAction { 32 | 33 | private JarEditorAddJarInJar jarEditorAddJarInJar = new JarEditorAddJarInJar(); 34 | 35 | @Override 36 | public void actionPerformed(@NotNull AnActionEvent e) { 37 | VirtualFile selectedFile = e.getData(CommonDataKeys.VIRTUAL_FILE); 38 | if(null == selectedFile) { 39 | NoticeInfo.warning("No file selected"); 40 | return; 41 | } 42 | 43 | //jar内新增jar 44 | if("jar".equals(selectedFile.getExtension()) || selectedFile.getPath().contains(".jar!/")) { 45 | jarEditorAddJarInJar.actionPerformed(e); 46 | return; 47 | } 48 | 49 | Project project = e.getProject(); 50 | if(project == null) { 51 | NoticeInfo.warning("Please open a project"); 52 | return; 53 | } 54 | 55 | String dir = selectedFile.isDirectory() ? selectedFile.getPath() : selectedFile.getParent().getPath(); 56 | 57 | String userInput = Messages.showInputDialog( 58 | project, 59 | "Enter JAR name:", 60 | "Create New JAR", 61 | Messages.getQuestionIcon(), 62 | ".jar", 63 | null 64 | ); 65 | if(StringUtils.isEmpty(userInput)) { 66 | return; 67 | } 68 | 69 | String jarPath = dir+"/"+userInput; 70 | 71 | ProgressManager.getInstance().run(new Task.Backgroundable(null, "New JAR ...", false) { 72 | @Override 73 | public void run(@NotNull ProgressIndicator progressIndicator) { 74 | try { 75 | File file = new File(jarPath); 76 | 77 | if(file.exists()) { 78 | NoticeInfo.error("Already exists: %s",jarPath); 79 | return; 80 | } 81 | file.getParentFile().mkdirs(); 82 | 83 | try (JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file))) { 84 | 85 | JarEntry META_INF = JarBuilder.createStoredEntry("META-INF/", "".getBytes()); 86 | jarOutputStream.putNextEntry(META_INF); 87 | jarOutputStream.closeEntry(); 88 | 89 | byte[] bytes = TemplateManager.getText("MF", jarPath).getBytes(StandardCharsets.UTF_8); 90 | JarEntry MANIFEST_MF = JarBuilder.createStoredEntry("META-INF/MANIFEST.MF",bytes); 91 | jarOutputStream.putNextEntry(MANIFEST_MF); 92 | jarOutputStream.write(bytes); 93 | jarOutputStream.closeEntry(); 94 | } 95 | 96 | ApplicationManager.getApplication().invokeLater(() -> { 97 | try{ 98 | VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(jarPath); 99 | if(null != virtualFile) { 100 | virtualFile.refresh(false, false); 101 | } 102 | }catch (Exception ex){ 103 | ex.printStackTrace(); 104 | } 105 | }); 106 | 107 | }catch (Throwable e) { 108 | NoticeInfo.error("New JAR err",e); 109 | } 110 | } 111 | }); 112 | 113 | } 114 | 115 | 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorAddJarInJar.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.Messages; 5 | import com.liubs.jareditor.backup.Backup; 6 | import com.liubs.jareditor.backup.ChangeData; 7 | import com.liubs.jareditor.backup.ChangeItem; 8 | import com.liubs.jareditor.backup.ChangeType; 9 | import com.liubs.jareditor.jarbuild.JarBuildResult; 10 | import com.liubs.jareditor.jarbuild.JarBuilder; 11 | import com.liubs.jareditor.persistent.BackupStorage; 12 | import com.liubs.jareditor.sdk.NoticeInfo; 13 | import com.liubs.jareditor.template.TemplateManager; 14 | import com.liubs.jareditor.util.DateUtil; 15 | import com.liubs.jareditor.util.StringUtils; 16 | 17 | import java.io.ByteArrayOutputStream; 18 | import java.io.IOException; 19 | import java.nio.charset.StandardCharsets; 20 | import java.util.ArrayList; 21 | import java.util.Date; 22 | import java.util.jar.JarEntry; 23 | import java.util.jar.JarOutputStream; 24 | 25 | /** 26 | * jar内新增jar 27 | * @author Liubsyy 28 | * @date 2024/11/26 29 | */ 30 | public class JarEditorAddJarInJar extends JavaEditorAddFile { 31 | 32 | @Override 33 | protected String preInput(Project project, String entryPathFromJar) { 34 | String userInput = Messages.showInputDialog( 35 | project, 36 | "Enter JAR name:", 37 | "Create New JAR", 38 | Messages.getQuestionIcon(), 39 | ".jar", 40 | null 41 | ); 42 | if(StringUtils.isEmpty(userInput)) { 43 | return null; 44 | } 45 | 46 | if(null == entryPathFromJar){ 47 | return userInput.endsWith(".jar") ? userInput : userInput+".jar"; 48 | } 49 | return userInput.endsWith(".jar") ? entryPathFromJar+"/"+userInput : entryPathFromJar+"/"+userInput+".jar"; 50 | } 51 | 52 | @Override 53 | protected boolean addFileInJar(String jarPath, String entryPath) { 54 | JarBuilder jarBuilder = new JarBuilder(jarPath); 55 | 56 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 57 | try (JarOutputStream jarOutputStream = new JarOutputStream(byteArrayOutputStream)) { 58 | JarEntry META_INF = JarBuilder.createStoredEntry("META-INF/", "".getBytes()); 59 | jarOutputStream.putNextEntry(META_INF); 60 | jarOutputStream.closeEntry(); 61 | 62 | byte[] bytes = TemplateManager.getText("MF", entryPath).getBytes(StandardCharsets.UTF_8); 63 | JarEntry MANIFEST_MF = JarBuilder.createStoredEntry("META-INF/MANIFEST.MF",bytes); 64 | jarOutputStream.putNextEntry(MANIFEST_MF); 65 | jarOutputStream.write(bytes); 66 | jarOutputStream.closeEntry(); 67 | } catch (IOException e) { 68 | e.printStackTrace(); 69 | } 70 | 71 | byte[] jarBytes = byteArrayOutputStream.toByteArray(); 72 | 73 | Backup backup = new Backup(); 74 | if(BackupStorage.getInstance().isEnableBackup()) { 75 | backup.checkBackupFirstVersion(jarPath); 76 | } 77 | JarBuildResult jarBuildResult = jarBuilder.addFile(entryPath,jarBytes); 78 | if(!jarBuildResult.isSuccess()) { 79 | NoticeInfo.error("Add file err: \n%s",jarBuildResult.getErr()); 80 | return false; 81 | } 82 | if(BackupStorage.getInstance().isEnableBackup() && !BackupStorage.getInstance().isBackupOnce()) { 83 | ChangeData changeData = new ChangeData(); 84 | changeData.setCreateTime(DateUtil.formatDate(new Date())); 85 | changeData.setChangeList(new ArrayList<>()); 86 | changeData.getChangeList().add(new ChangeItem(ChangeType.ADD.value,entryPath)); 87 | backup.backupJar(jarPath,changeData); 88 | } 89 | return true; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorAddJavaFile.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.Messages; 5 | import com.liubs.jareditor.util.StringUtils; 6 | 7 | /** 8 | * New Java file 9 | * @author Liubsyy 10 | * @date 2024/5/12 11 | */ 12 | public class JarEditorAddJavaFile extends JavaEditorAddFile { 13 | 14 | @Override 15 | protected String preInput(Project project, String entryPathFromJar) { 16 | String userInput = Messages.showInputDialog( 17 | project, 18 | "Enter name for new class:", 19 | "Create New Class", 20 | Messages.getQuestionIcon() 21 | ); 22 | if(StringUtils.isEmpty(userInput)) { 23 | return null; 24 | } 25 | 26 | //java package 27 | userInput = userInput.replace(".", "/"); 28 | 29 | if(null == entryPathFromJar){ 30 | return userInput+".class"; 31 | } 32 | return entryPathFromJar+"/"+userInput+".class"; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorAddKotlinFile.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.Messages; 5 | import com.liubs.jareditor.util.StringUtils; 6 | 7 | /** 8 | * 新增kotlin文件 9 | * @author Liubsyy 10 | * @date 2024/6/2 11 | */ 12 | public class JarEditorAddKotlinFile extends JavaEditorAddFile { 13 | 14 | @Override 15 | protected String preInput(Project project, String entryPathFromJar) { 16 | String userInput = Messages.showInputDialog( 17 | project, 18 | "Enter name for kotlin class:", 19 | "Create Kotlin Class", 20 | Messages.getQuestionIcon() 21 | ); 22 | if(StringUtils.isEmpty(userInput)) { 23 | return null; 24 | } 25 | if(null == entryPathFromJar){ 26 | return userInput+".kt"; 27 | } 28 | return entryPathFromJar+"/"+userInput+".kt"; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorAddManifest.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.project.Project; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2024/10/15 8 | */ 9 | public class JarEditorAddManifest extends JavaEditorAddFile { 10 | 11 | @Override 12 | protected String preInput(Project project, String entryPathFromJar) { 13 | if(null == entryPathFromJar) { 14 | return "META-INF/MANIFEST.MF"; 15 | } 16 | return entryPathFromJar+"/MANIFEST.MF"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorAddResourceFile.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.Messages; 5 | import com.liubs.jareditor.util.StringUtils; 6 | 7 | /** 8 | * 新增普通文件 9 | * @author Liubsyy 10 | * @date 2024/5/12 11 | */ 12 | public class JarEditorAddResourceFile extends JavaEditorAddFile { 13 | @Override 14 | protected String preInput(Project project, String entryPathFromJar) { 15 | String userInput = Messages.showInputDialog( 16 | project, 17 | "Enter name for new file:", 18 | "Create New File", 19 | Messages.getQuestionIcon() 20 | ); 21 | if(StringUtils.isEmpty(userInput)) { 22 | return null; 23 | } 24 | if(null == entryPathFromJar) { 25 | return userInput; 26 | } 27 | return entryPathFromJar+"/"+userInput; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorBackup.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.CommonDataKeys; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.liubs.jareditor.backup.BackupDialog; 9 | import com.liubs.jareditor.sdk.NoticeInfo; 10 | import com.liubs.jareditor.util.MyPathUtil; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | /** 14 | * @author Liubsyy 15 | * @date 2025/5/22 16 | */ 17 | public class JarEditorBackup extends AnAction { 18 | 19 | @Override 20 | public void actionPerformed(@NotNull AnActionEvent e) { 21 | Project project = e.getProject(); 22 | if(project == null) { 23 | NoticeInfo.warning("Please open a project"); 24 | return; 25 | } 26 | VirtualFile selectedFile = e.getData(CommonDataKeys.VIRTUAL_FILE); 27 | if(null == selectedFile) { 28 | NoticeInfo.warning("No file selected"); 29 | return; 30 | } 31 | 32 | boolean isJarRoot = "jar".equals(selectedFile.getExtension()); 33 | final String jarPath = isJarRoot ? 34 | selectedFile.getPath().replace(".jar!/",".jar") : MyPathUtil.getJarPathFromJar(selectedFile.getPath()); 35 | if(null == jarPath) { 36 | NoticeInfo.warning("This operation only in JAR !!!"); 37 | return; 38 | } 39 | 40 | BackupDialog dialog = new BackupDialog(project,jarPath); 41 | dialog.showAndGet(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorClear.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.progress.ProgressIndicator; 5 | import com.intellij.openapi.progress.ProgressManager; 6 | import com.intellij.openapi.progress.Task; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.liubs.jareditor.sdk.NoticeInfo; 9 | import com.liubs.jareditor.util.MyFileUtil; 10 | import com.liubs.jareditor.util.MyPathUtil; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | /** 14 | * 删除Save的临时文件夹 15 | * @author Liubsyy 16 | * @date 2024/6/26 17 | */ 18 | public class JarEditorClear extends MyToolbarAction { 19 | @Override 20 | public void actionPerformed(@NotNull AnActionEvent e) { 21 | VirtualFile selectedFile = super.currentEditorFile(e); 22 | if(null == selectedFile) { 23 | return; 24 | } 25 | 26 | final String jarPath = "jar".equals(selectedFile.getExtension()) ? 27 | selectedFile.getPath().replace(".jar!/",".jar") : MyPathUtil.getJarPathFromJar(selectedFile.getPath()); 28 | if(null == jarPath) { 29 | NoticeInfo.warning("This operation only in JAR !!!"); 30 | return; 31 | } 32 | 33 | ProgressManager.getInstance().run(new Task.Backgroundable(null, "Clear temp directory ...", false) { 34 | @Override 35 | public void run(@NotNull ProgressIndicator progressIndicator) { 36 | try { 37 | 38 | //删除临时保存的目录 39 | MyFileUtil.deleteDir(MyPathUtil.getJarEditTemp(selectedFile.getPath())); 40 | 41 | NoticeInfo.info("Clear success !"); 42 | }catch (Throwable e) { 43 | NoticeInfo.error("Clear files err",e); 44 | } 45 | } 46 | }); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorCopyFile.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.CommonDataKeys; 6 | import com.intellij.openapi.progress.ProgressIndicator; 7 | import com.intellij.openapi.progress.ProgressManager; 8 | import com.intellij.openapi.progress.Task; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import com.liubs.jareditor.clipboard.CopyResult; 11 | import com.liubs.jareditor.clipboard.FileToClipBoard; 12 | import com.liubs.jareditor.sdk.NoticeInfo; 13 | import com.liubs.jareditor.util.JarUtil; 14 | import com.liubs.jareditor.util.MyPathUtil; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.io.File; 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | 21 | /** 22 | * 拷贝文件到剪切板 23 | * @author Liubsyy 24 | * @date 2024/6/2 25 | */ 26 | public class JarEditorCopyFile extends AnAction { 27 | @Override 28 | public void actionPerformed(@NotNull AnActionEvent e) { 29 | //支持多选 30 | VirtualFile[] selectedFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY); 31 | 32 | if(e.getProject() == null) { 33 | NoticeInfo.warning("Please open a project"); 34 | return; 35 | } 36 | if(null == selectedFiles) { 37 | NoticeInfo.warning("No file selected"); 38 | return; 39 | } 40 | 41 | Set copyEntries = new HashSet<>(); 42 | for (VirtualFile file : selectedFiles) { 43 | if(!file.getPath().contains(".jar!/")) { 44 | NoticeInfo.warning("Ony files in JAR can be copy !!!"); 45 | return; 46 | } 47 | String entryPathFromJar = MyPathUtil.getEntryPathFromJar(file.getPath()); 48 | if(null != entryPathFromJar) { 49 | if(file.isDirectory()) { 50 | copyEntries.add(entryPathFromJar.replace("\\", "/")+"/"); 51 | }else { 52 | copyEntries.add(entryPathFromJar.replace("\\", "/")); 53 | } 54 | 55 | } 56 | } 57 | 58 | if(copyEntries.isEmpty()) { 59 | NoticeInfo.warning("Please select any file to copy!!"); 60 | return; 61 | } 62 | 63 | final String clipboardPath = MyPathUtil.getFILE_TO_CLIPBOARD(selectedFiles[0].getPath()); 64 | final String jarPath = MyPathUtil.getJarPathFromJar(selectedFiles[0].getPath()); 65 | 66 | ProgressManager.getInstance().run(new Task.Backgroundable(null, "Copy files to clipboard...", false) { 67 | @Override 68 | public void run(@NotNull ProgressIndicator progressIndicator) { 69 | try { 70 | java.util.List files = JarUtil.copyJarEntries(jarPath, clipboardPath, copyEntries); 71 | 72 | if(files.isEmpty()) { 73 | NoticeInfo.warning("Nothing copied !!!"); 74 | return; 75 | } 76 | 77 | CopyResult copyResult = FileToClipBoard.copyFilesToClipboard(files); 78 | if(!copyResult.isSuccess()) { 79 | NoticeInfo.error(copyResult.getError()); 80 | return; 81 | } 82 | NoticeInfo.info("Copy successfully, you can paste to another place from clipboard now !!!"); 83 | }catch (Throwable e) { 84 | NoticeInfo.error("Copy files to clipboard err",e.getMessage()); 85 | } 86 | } 87 | }); 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorExportSourceJar.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.CommonDataKeys; 6 | import com.intellij.openapi.application.ApplicationManager; 7 | import com.intellij.openapi.progress.ProgressIndicator; 8 | import com.intellij.openapi.progress.ProgressManager; 9 | import com.intellij.openapi.progress.Task; 10 | import com.intellij.openapi.project.Project; 11 | import com.intellij.openapi.ui.Messages; 12 | import com.intellij.openapi.vfs.VirtualFile; 13 | import com.intellij.openapi.vfs.VirtualFileManager; 14 | import com.liubs.jareditor.constant.PathConstant; 15 | import com.liubs.jareditor.editor.MyJarEditor; 16 | import com.liubs.jareditor.jarbuild.JarBuilder; 17 | import com.liubs.jareditor.sdk.NoticeInfo; 18 | import com.liubs.jareditor.util.MyPathUtil; 19 | import com.liubs.jareditor.util.StringUtils; 20 | import org.jetbrains.annotations.NotNull; 21 | 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.HashSet; 24 | import java.util.Set; 25 | 26 | /** 27 | * 导出源码source jar 28 | * @author Liubsyy 29 | * @date 2024/7/15 30 | */ 31 | public class JarEditorExportSourceJar extends AnAction { 32 | @Override 33 | public void actionPerformed(@NotNull AnActionEvent e) { 34 | Project project = e.getProject(); 35 | if(project == null) { 36 | NoticeInfo.warning("Please open a project"); 37 | return; 38 | } 39 | 40 | //支持多选导出 41 | VirtualFile[] selectedFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY); 42 | if(null == selectedFiles || selectedFiles.length==0) { 43 | NoticeInfo.warning("No file selected"); 44 | return; 45 | } 46 | 47 | Set fullJarFiles = new HashSet<>(); 48 | for(VirtualFile virtualFile : selectedFiles) { 49 | if(null != virtualFile) { 50 | String jarFullPath = MyPathUtil.getJarFullPath(virtualFile.getPath()); 51 | if(null != jarFullPath) { 52 | fullJarFiles.add(jarFullPath); 53 | } 54 | } 55 | } 56 | if(fullJarFiles.isEmpty()) { 57 | NoticeInfo.warning("No jar selected"); 58 | return; 59 | } 60 | 61 | String userInput = Messages.showInputDialog( 62 | e.getProject(), 63 | String.format("You are exporting %d jar, enter the suffix of target source jar.\n Demo: if you input \"-sources-export.jar\" of demo.jar ,\n you will get \"demo-sources-export.jar\"",fullJarFiles.size()), 64 | "Export source jar", 65 | Messages.getQuestionIcon(), 66 | PathConstant.EXPORT_SOURCE_NAME_SUFFIX, 67 | null 68 | ); 69 | 70 | if(StringUtils.isEmpty(userInput)) { 71 | return; 72 | } 73 | 74 | 75 | ProgressManager.getInstance().run(new Task.Backgroundable(null, "Export source jar ...", true) { 76 | @Override 77 | public void run(@NotNull ProgressIndicator progressIndicator) { 78 | try { 79 | 80 | if(!userInput.endsWith(".jar")) { 81 | NoticeInfo.warning("You must input end with .jar"); 82 | return; 83 | } 84 | 85 | 86 | fullJarFiles.forEach(jarPath->{ 87 | 88 | JarBuilder jarBuilder = new JarBuilder(jarPath); 89 | ApplicationManager.getApplication().runReadAction(() -> { 90 | jarBuilder.decompileContent(jarPath.replace(".jar",userInput), (entry)->{ 91 | VirtualFile virtualJar = VirtualFileManager.getInstance().findFileByUrl("jar://" + jarPath + "!/"+entry); 92 | if(null == virtualJar) { 93 | return null; 94 | }else { 95 | String allText = MyJarEditor.getDecompiledText(project, virtualJar); 96 | return null == allText ? null : allText.getBytes(StandardCharsets.UTF_8); 97 | } 98 | }); 99 | }); 100 | 101 | }); 102 | 103 | 104 | ApplicationManager.getApplication().invokeLater(() -> { 105 | for (VirtualFile refreshFile : selectedFiles) { 106 | refreshFile.refresh(false,true); 107 | } 108 | VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); 109 | }); 110 | 111 | NoticeInfo.info("Export source jar success !"); 112 | }catch (Throwable e) { 113 | NoticeInfo.error("Export source jar err",e); 114 | } 115 | } 116 | }); 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorPasteFile.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.CommonDataKeys; 6 | import com.intellij.openapi.progress.ProgressIndicator; 7 | import com.intellij.openapi.progress.ProgressManager; 8 | import com.intellij.openapi.progress.Task; 9 | import com.intellij.openapi.project.Project; 10 | import com.intellij.openapi.vfs.VirtualFile; 11 | import com.intellij.openapi.vfs.VirtualFileManager; 12 | import com.liubs.jareditor.backup.Backup; 13 | import com.liubs.jareditor.clipboard.ClipboardToFile; 14 | import com.liubs.jareditor.clipboard.CopyResult; 15 | import com.liubs.jareditor.jarbuild.JarBuildResult; 16 | import com.liubs.jareditor.jarbuild.JarBuilder; 17 | import com.liubs.jareditor.persistent.BackupStorage; 18 | import com.liubs.jareditor.sdk.NoticeInfo; 19 | import com.liubs.jareditor.util.MyPathUtil; 20 | import org.jetbrains.annotations.NotNull; 21 | 22 | 23 | /** 24 | * 从剪切板粘贴文件到jar包内 25 | * @author Liubsyy 26 | * @date 2024/6/2 27 | */ 28 | public class JarEditorPasteFile extends AnAction { 29 | 30 | @Override 31 | public void actionPerformed(@NotNull AnActionEvent e) { 32 | VirtualFile selectedFile = e.getData(CommonDataKeys.VIRTUAL_FILE); 33 | Project project = e.getProject(); 34 | if(project == null) { 35 | NoticeInfo.warning("Please open a project"); 36 | return; 37 | } 38 | 39 | pasteFile(selectedFile); 40 | } 41 | 42 | public static void pasteFile(VirtualFile selectedFile){ 43 | 44 | if(null == selectedFile) { 45 | NoticeInfo.warning("No file selected"); 46 | return; 47 | } 48 | 49 | if(!selectedFile.isDirectory()) { 50 | selectedFile = selectedFile.getParent(); 51 | if(null == selectedFile) { 52 | NoticeInfo.warning("You need choose a folder in jar !"); 53 | return; 54 | } 55 | } 56 | 57 | boolean isJarRoot = "jar".equals(selectedFile.getExtension()); 58 | final String jarPath = isJarRoot ? 59 | selectedFile.getPath().replace(".jar!/",".jar") : MyPathUtil.getJarPathFromJar(selectedFile.getPath()); 60 | final String filePath = selectedFile.getPath(); 61 | final String entryPathFromJar = MyPathUtil.getEntryPathFromJar(selectedFile.getPath()); 62 | if(null == jarPath) { 63 | NoticeInfo.warning("This operation only in JAR !!!"); 64 | return; 65 | } 66 | 67 | 68 | ProgressManager.getInstance().run(new Task.Backgroundable(null, "Paste files from clipboard...", false) { 69 | @Override 70 | public void run(@NotNull ProgressIndicator progressIndicator) { 71 | ClipboardToFile clipboardToFile = null; 72 | try { 73 | String clipboard_to_fileDir = MyPathUtil.getCLIPBOARD_TO_FILE(filePath); 74 | 75 | clipboardToFile = new ClipboardToFile(null == entryPathFromJar ? 76 | clipboard_to_fileDir 77 | : 78 | clipboard_to_fileDir+"/"+entryPathFromJar); 79 | CopyResult copyResult = clipboardToFile.copyFilesFromClipboard(); 80 | 81 | if(!copyResult.isSuccess()) { 82 | NoticeInfo.error(copyResult.getError()); 83 | return; 84 | } 85 | 86 | JarBuilder jarBuilder = new JarBuilder(clipboard_to_fileDir,jarPath); 87 | Backup backup = new Backup(); 88 | if(BackupStorage.getInstance().isEnableBackup()) { 89 | backup.checkBackupFirstVersion(jarPath); 90 | } 91 | JarBuildResult jarBuildResult = jarBuilder.writeJar(false); 92 | if(!jarBuildResult.isSuccess()) { 93 | NoticeInfo.error("Paste err: \n%s",jarBuildResult.getErr()); 94 | return; 95 | } 96 | if(BackupStorage.getInstance().isEnableBackup() && !BackupStorage.getInstance().isBackupOnce()) { 97 | backup.backupJar(jarPath,backup.getChangeDataFromDir(clipboard_to_fileDir)); 98 | } 99 | 100 | 101 | VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); 102 | 103 | NoticeInfo.info("Paste successfully!"); 104 | 105 | }catch (Throwable e) { 106 | NoticeInfo.error("Paste files from clipboard err",e.getMessage()); 107 | }finally { 108 | if(null != clipboardToFile) { 109 | clipboardToFile.deleteTargetDir(); 110 | } 111 | } 112 | } 113 | }); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorRenameFile.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.CommonDataKeys; 6 | import com.intellij.openapi.progress.ProgressIndicator; 7 | import com.intellij.openapi.progress.ProgressManager; 8 | import com.intellij.openapi.progress.Task; 9 | import com.intellij.openapi.ui.Messages; 10 | import com.intellij.openapi.vfs.VirtualFile; 11 | import com.intellij.openapi.vfs.VirtualFileManager; 12 | import com.liubs.jareditor.jarbuild.JarBuildResult; 13 | import com.liubs.jareditor.jarbuild.JarBuilder; 14 | import com.liubs.jareditor.sdk.NoticeInfo; 15 | import com.liubs.jareditor.util.MyPathUtil; 16 | import com.liubs.jareditor.util.StringUtils; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | /** 20 | * 重命名 21 | * @author Liubsyy 22 | * @date 2024/6/2 23 | */ 24 | public class JarEditorRenameFile extends AnAction { 25 | @Override 26 | public void actionPerformed(@NotNull AnActionEvent e) { 27 | VirtualFile selectedFile = e.getData(CommonDataKeys.VIRTUAL_FILE); 28 | 29 | if(e.getProject() == null) { 30 | NoticeInfo.warning("Please open a project"); 31 | return; 32 | } 33 | if(null == selectedFile) { 34 | NoticeInfo.warning("No file selected"); 35 | return; 36 | } 37 | 38 | final String jarPath = MyPathUtil.getJarPathFromJar(selectedFile.getPath()); 39 | if(null == jarPath) { 40 | NoticeInfo.warning("This operation only in JAR !!!"); 41 | return; 42 | } 43 | 44 | final boolean isDirectory = selectedFile.isDirectory(); 45 | final String oldEntry = isDirectory ? 46 | (MyPathUtil.getEntryPathFromJar(selectedFile.getPath()) + "/") : 47 | MyPathUtil.getEntryPathFromJar(selectedFile.getPath()); 48 | 49 | String userInput = Messages.showInputDialog( 50 | e.getProject(), 51 | "Enter new name:", 52 | "Rename File", 53 | Messages.getQuestionIcon(), 54 | selectedFile.getName(), 55 | null // 可选的输入校验器,如果没有则传 null 56 | ); 57 | if(StringUtils.isEmpty(userInput)) { 58 | return; 59 | } 60 | 61 | String newName = isDirectory ? 62 | (MyPathUtil.getEntryPathFromJar(selectedFile.getParent().getPath()+"/"+userInput)+"/") 63 | : 64 | (MyPathUtil.getEntryPathFromJar(selectedFile.getParent().getPath()+"/"+userInput)); 65 | 66 | final String newNameFinal = null != newName && newName.startsWith("/") ? newName.substring(1) : newName; 67 | ProgressManager.getInstance().run(new Task.Backgroundable(null, "Renaming files in JAR...", false) { 68 | @Override 69 | public void run(@NotNull ProgressIndicator progressIndicator) { 70 | try { 71 | JarBuilder jarBuilder = new JarBuilder(jarPath); 72 | JarBuildResult jarBuildResult = jarBuilder.renameFile(oldEntry, newNameFinal, isDirectory); 73 | if(!jarBuildResult.isSuccess()) { 74 | NoticeInfo.error("Rename err: \n%s",jarBuildResult.getErr()); 75 | return; 76 | } 77 | 78 | VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); 79 | 80 | NoticeInfo.info("Rename success !"); 81 | 82 | }catch (Throwable e) { 83 | NoticeInfo.error("Rename files err",e); 84 | } 85 | } 86 | }); 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorReset.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.fileEditor.FileEditor; 5 | import com.intellij.openapi.fileEditor.FileEditorManager; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import com.liubs.jareditor.editor.MyJarEditor; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * Reset Editor 16 | * @author Liubsyy 17 | * @date 2024/9/2 18 | */ 19 | public class JarEditorReset extends MyToolbarAction { 20 | 21 | @Override 22 | public void actionPerformed(@NotNull AnActionEvent e) { 23 | VirtualFile selectedFile = super.currentEditorFile(e); 24 | if(null == selectedFile) { 25 | return; 26 | } 27 | 28 | List editors = Arrays.stream(FileEditorManager.getInstance(e.getProject()).getEditors(selectedFile)) 29 | .filter(fileEditor -> fileEditor instanceof MyJarEditor) 30 | .collect(Collectors.toList()); 31 | if(editors.isEmpty()) { 32 | return; 33 | } 34 | MyJarEditor myJarEditor = (MyJarEditor)editors.get(0); 35 | myJarEditor.resetEditorContent(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JarEditorSearch.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.ActionPlaces; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import com.intellij.openapi.actionSystem.CommonDataKeys; 7 | import com.intellij.openapi.fileEditor.FileEditorManager; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import com.intellij.openapi.vfs.VirtualFileManager; 11 | import com.liubs.jareditor.search.JarFileSearchDialog; 12 | import com.liubs.jareditor.sdk.NoticeInfo; 13 | import com.liubs.jareditor.util.MyPathUtil; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | /** 17 | * jar包内搜索文件和字符串 18 | * @author Liubsyy 19 | * @date 2024/6/25 20 | */ 21 | public class JarEditorSearch extends AnAction { 22 | @Override 23 | public void actionPerformed(@NotNull AnActionEvent e) { 24 | 25 | Project project = e.getProject(); 26 | if(project == null) { 27 | NoticeInfo.warning("Please open a project"); 28 | return; 29 | } 30 | VirtualFile selectedFile = null; 31 | if( ActionPlaces.TOOLBAR.equals(e.getPlace())) { 32 | FileEditorManager fileEditorManager = FileEditorManager.getInstance(project); 33 | VirtualFile[] editorSelectFiles = fileEditorManager.getSelectedFiles(); 34 | if(editorSelectFiles.length > 0) { 35 | selectedFile = editorSelectFiles[0]; 36 | } 37 | }else { 38 | selectedFile = e.getData(CommonDataKeys.VIRTUAL_FILE); 39 | } 40 | 41 | VirtualFile jarRoot = null; 42 | if(null != selectedFile) { 43 | final String jarPath = "jar".equals(selectedFile.getExtension()) ? 44 | selectedFile.getPath().replace(".jar!/",".jar") : MyPathUtil.getJarPathFromJar(selectedFile.getPath()); 45 | if(null != jarPath) { 46 | jarRoot = VirtualFileManager.getInstance().findFileByUrl("jar://" + jarPath + "!/"); 47 | } 48 | } 49 | 50 | 51 | JarFileSearchDialog jarFileSearchDialog = new JarFileSearchDialog(e.getProject(),jarRoot); 52 | jarFileSearchDialog.show(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/JavassistAction.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.fileEditor.FileEditor; 5 | import com.intellij.openapi.fileEditor.FileEditorManager; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import com.liubs.jareditor.bytestool.javassist.JavassistDialog; 8 | import com.liubs.jareditor.editor.MyJarEditor; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * javassist修改字节码工具入口 17 | * @author Liubsyy 18 | * @date 2024/8/27 19 | */ 20 | public class JavassistAction extends MyToolbarAction { 21 | @Override 22 | public void actionPerformed(@NotNull AnActionEvent e) { 23 | VirtualFile selectedFile = super.currentEditorFile(e); 24 | if(null == selectedFile) { 25 | return; 26 | } 27 | 28 | List editors = Arrays.stream(FileEditorManager.getInstance(e.getProject()).getEditors(selectedFile)) 29 | .filter(fileEditor -> fileEditor instanceof MyJarEditor) 30 | .collect(Collectors.toList()); 31 | if(editors.isEmpty()) { 32 | return; 33 | } 34 | MyJarEditor myJarEditor = (MyJarEditor)editors.get(0); 35 | JavassistDialog dialog = new JavassistDialog(e.getProject(),selectedFile,myJarEditor); 36 | dialog.show(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/MyToolbarAction.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.fileEditor.FileEditorManager; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.liubs.jareditor.sdk.NoticeInfo; 9 | 10 | /** 11 | * @author Liubsyy 12 | * @date 2024/8/27 13 | */ 14 | public abstract class MyToolbarAction extends AnAction { 15 | 16 | public VirtualFile currentEditorFile(AnActionEvent e){ 17 | Project project = e.getProject(); 18 | if(project == null) { 19 | NoticeInfo.warning("Please open a project"); 20 | return null; 21 | } 22 | VirtualFile selectedFile = null; 23 | FileEditorManager fileEditorManager = FileEditorManager.getInstance(project); 24 | if(null != fileEditorManager) { 25 | VirtualFile[] editorSelectFiles = fileEditorManager.getSelectedFiles(); 26 | if(editorSelectFiles.length > 0) { 27 | selectedFile = editorSelectFiles[0]; 28 | } 29 | } 30 | if(null == selectedFile) { 31 | NoticeInfo.warning("No editor opened"); 32 | return null; 33 | } 34 | return selectedFile; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/RefreshFileTree.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.ide.projectView.ProjectView; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import com.intellij.openapi.project.Project; 7 | import com.liubs.jareditor.sdk.NoticeInfo; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | /** 11 | * @author Liubsyy 12 | * @date 2024/10/13 13 | */ 14 | public class RefreshFileTree extends AnAction { 15 | @Override 16 | public void actionPerformed(@NotNull AnActionEvent e) { 17 | Project project = e.getProject(); 18 | if(project == null) { 19 | NoticeInfo.warning("Please open a project"); 20 | return; 21 | } 22 | ProjectView.getInstance(project).refresh(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/action/VcbEditorAction.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.action; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import com.liubs.jareditor.bytestool.vcb.GotoVisualClassBytesEditor; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | /** 9 | * @author Liubsyy 10 | * @date 2024/11/15 11 | */ 12 | public class VcbEditorAction extends MyToolbarAction { 13 | @Override 14 | public void actionPerformed(@NotNull AnActionEvent e) { 15 | VirtualFile selectedFile = super.currentEditorFile(e); 16 | if(null == selectedFile) { 17 | return; 18 | } 19 | GotoVisualClassBytesEditor.openVCBEditor(e); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/backup/BackupData.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.backup; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2025/5/23 8 | */ 9 | public class BackupData { 10 | private Date createTime; 11 | private String backupJar; 12 | private ChangeData changeData; 13 | 14 | public String getBackupJar() { 15 | return backupJar; 16 | } 17 | 18 | public Date getCreateTime() { 19 | return createTime; 20 | } 21 | 22 | public void setCreateTime(Date createTime) { 23 | this.createTime = createTime; 24 | } 25 | 26 | public void setBackupJar(String backupJar) { 27 | this.backupJar = backupJar; 28 | } 29 | 30 | public ChangeData getChangeData() { 31 | return changeData; 32 | } 33 | 34 | public void setChangeData(ChangeData changeData) { 35 | this.changeData = changeData; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/backup/ChangeData.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.backup; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2025/5/23 8 | */ 9 | public class ChangeData { 10 | private String createTime; 11 | private List changeList; 12 | 13 | public String getCreateTime() { 14 | return createTime; 15 | } 16 | 17 | public void setCreateTime(String createTime) { 18 | this.createTime = createTime; 19 | } 20 | 21 | public List getChangeList() { 22 | return changeList; 23 | } 24 | 25 | public void setChangeList(List changeList) { 26 | this.changeList = changeList; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/backup/ChangeItem.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.backup; 2 | 3 | /** 4 | * @author Liubsyy 5 | * @date 2025/5/23 6 | */ 7 | public class ChangeItem { 8 | private int changeType; 9 | private String entry; 10 | 11 | public ChangeItem() { 12 | } 13 | 14 | public ChangeItem(int changeType, String entry) { 15 | this.changeType = changeType; 16 | this.entry = entry; 17 | } 18 | 19 | public int getChangeType() { 20 | return changeType; 21 | } 22 | 23 | public void setChangeType(int changeType) { 24 | this.changeType = changeType; 25 | } 26 | 27 | public String getEntry() { 28 | return entry; 29 | } 30 | 31 | public void setEntry(String entry) { 32 | this.entry = entry; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/backup/ChangeType.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.backup; 2 | 3 | /** 4 | * @author Liubsyy 5 | * @date 2025/5/23 6 | */ 7 | public enum ChangeType { 8 | ADD(1), 9 | MODIFY(2), 10 | DELETE(3), 11 | ; 12 | public int value; 13 | 14 | ChangeType(int value) { 15 | this.value = value; 16 | } 17 | 18 | public static ChangeType findByValue(int value){ 19 | for(ChangeType e : values()) { 20 | if(e.value == value) { 21 | return e; 22 | } 23 | } 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/bytestool/ToClassFile.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.bytestool; 2 | 3 | 4 | import com.liubs.jareditor.util.JarUtil; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * 生成的class文件,连同内部类文件一起生成 14 | * @author Liubsyy 15 | * @date 2024/9/15 16 | */ 17 | public class ToClassFile { 18 | 19 | /** 20 | * jar目录 21 | */ 22 | private String jarPath; 23 | 24 | /** 25 | * 类名路径 26 | */ 27 | private String classEntryName; 28 | 29 | /** 30 | * 生成目录 31 | */ 32 | private String targetPath; 33 | 34 | /** 35 | * 新文件覆盖拷贝文件 36 | */ 37 | private Map coverClass = new HashMap<>(); 38 | 39 | public ToClassFile(String jarPath, String classEntryName, String targetPath) { 40 | this.jarPath = jarPath; 41 | this.classEntryName = classEntryName; 42 | this.targetPath = targetPath; 43 | } 44 | 45 | 46 | public void addCoverFile(String className,byte[] bytes) { 47 | coverClass.put(className,bytes); 48 | } 49 | 50 | public void writeFiles() throws IOException { 51 | 52 | //拷贝jar内class和内部类到目标目录 53 | JarUtil.copyJarClassEntries(jarPath, targetPath,classEntryName); 54 | 55 | //拷贝新生成的coverClass到指定目录 56 | String relaDir = classEntryName.substring(0,classEntryName.lastIndexOf("/")+1); 57 | for(Map.Entry entry : coverClass.entrySet()) { 58 | String simpleClassName = entry.getKey(); 59 | int lastIndexOf = simpleClassName.lastIndexOf("."); 60 | if(lastIndexOf > 0) { 61 | simpleClassName = simpleClassName.substring(lastIndexOf+1); 62 | } 63 | 64 | Files.write(Paths.get(targetPath,relaDir,simpleClassName+".class"), 65 | entry.getValue()); 66 | } 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/bytestool/javassist/ClassInitializerSignature.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.bytestool.javassist; 2 | 3 | import com.intellij.psi.PsiClassInitializer; 4 | import com.intellij.psi.PsiElement; 5 | import com.intellij.psi.PsiFile; 6 | import com.intellij.psi.PsiMember; 7 | import com.liubs.jareditor.util.StringUtils; 8 | import javassist.CtConstructor; 9 | import javassist.CtMember; 10 | 11 | /** 12 | * 静态代码块 13 | * @author Liubsyy 14 | * @date 2024/9/13 15 | */ 16 | public class ClassInitializerSignature implements ISignature { 17 | 18 | private CtConstructor classInitializer; 19 | public ClassInitializerSignature(CtConstructor classInitializer){ 20 | this.classInitializer = classInitializer; 21 | } 22 | 23 | @Override 24 | public String show() { 25 | return "static {...}"; 26 | } 27 | 28 | @Override 29 | public boolean isSameTarget(PsiMember psiMember) { 30 | if( !(psiMember instanceof PsiClassInitializer) ) { 31 | return false; 32 | } 33 | 34 | return true; 35 | } 36 | 37 | @Override 38 | public CtMember getMember() { 39 | return classInitializer; 40 | } 41 | 42 | @Override 43 | public String convertToJavassistCode(PsiFile psiFile, PsiElement psiMember) { 44 | String text = psiMember.getText(); 45 | if(StringUtils.isEmpty(text)) { 46 | return "static{\n\t//Insert static code here...\n}"; 47 | } 48 | return text; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/bytestool/javassist/ConstructorSignature.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.bytestool.javassist; 2 | 3 | import com.intellij.psi.*; 4 | import com.liubs.jareditor.util.PsiFileUtil; 5 | import javassist.*; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * @author Liubsyy 13 | * @date 2024/8/28 14 | */ 15 | public class ConstructorSignature implements ISignature{ 16 | 17 | private int modifier; 18 | private String constructName; 19 | private String simpleMethodName; 20 | 21 | private List paramTypes; 22 | private CtConstructor constructor; 23 | 24 | public ConstructorSignature(CtConstructor constructor) throws NotFoundException { 25 | 26 | // 获取修饰符 27 | this.modifier = constructor.getModifiers(); 28 | 29 | // 获取构造函数名称 30 | this.constructName = this.simpleMethodName = constructor.getName(); 31 | if(constructName.contains("$")){ 32 | int lastIndexOf = constructName.lastIndexOf("$"); 33 | if( lastIndexOf+1 < constructName.length() ) { 34 | simpleMethodName = simpleMethodName.substring(lastIndexOf+1); 35 | } 36 | } 37 | 38 | // 获取参数类型 39 | this.paramTypes = new ArrayList<>(); 40 | for (CtClass parameterType : constructor.getParameterTypes()) { 41 | this.paramTypes.add(parameterType.getName()); 42 | } 43 | 44 | this.constructor = constructor; 45 | 46 | } 47 | 48 | @Override 49 | public CtMember getMember() { 50 | return constructor; 51 | } 52 | 53 | @Override 54 | public String show() { 55 | 56 | String modifiers = Modifier.toString(modifier); 57 | 58 | StringBuilder params = new StringBuilder(); 59 | for (int i = 0,len=paramTypes.size(); i < len; i++) { 60 | if (i > 0) { 61 | params.append(", "); 62 | } 63 | params.append(paramTypes.get(i)) 64 | .append(" $") 65 | .append(i + 1); 66 | } 67 | 68 | return String.format("%s %s(%s)", modifiers, constructName, params.toString()); 69 | } 70 | 71 | @Override 72 | public boolean isSameTarget(PsiMember psiMember) { 73 | if(!(psiMember instanceof PsiMethod)) { 74 | return false; 75 | } 76 | PsiMethod psiMethod = (PsiMethod)psiMember; 77 | if( !constructName.equals(psiMethod.getName()) && !simpleMethodName.equals(psiMethod.getName()) ){ 78 | return false; 79 | } 80 | PsiParameterList parameterList = psiMethod.getParameterList(); 81 | if(parameterList.getParametersCount() != paramTypes.size()){ 82 | return false; 83 | } 84 | for(int i = 0,len=paramTypes.size() ;i< len ;i++) { 85 | PsiParameter psiParameter = parameterList.getParameter(i); 86 | if(null == psiParameter) { 87 | return false; 88 | } 89 | 90 | PsiType type = psiParameter.getType(); 91 | String paramTypeText = PsiFileUtil.resoleGenericType(type); 92 | 93 | if(!paramTypes.get(i).equals(paramTypeText)){ 94 | return false; 95 | } 96 | } 97 | return true; 98 | } 99 | 100 | 101 | @Override 102 | public String convertToJavassistCode(PsiFile psiFile,PsiElement psiMember) { 103 | return MethodSignature.convertToJavassistCode0(psiFile,psiMember); 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/bytestool/javassist/FieldSignature.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.bytestool.javassist; 2 | 3 | import com.intellij.psi.*; 4 | import javassist.*; 5 | 6 | 7 | /** 8 | * @author Liubsyy 9 | * @date 2024/8/28 10 | */ 11 | public class FieldSignature implements ISignature{ 12 | 13 | private int modifier; 14 | private String fieldType; 15 | private String fieldName; 16 | private CtField ctField; 17 | 18 | public FieldSignature(CtField field) throws NotFoundException { 19 | // 获取修饰符 20 | this.modifier = field.getModifiers(); 21 | 22 | // 获取字段类型 23 | this.fieldType = field.getType().getName(); 24 | 25 | //获取字段名称 26 | this.fieldName = field.getName(); 27 | 28 | this.ctField = field; 29 | } 30 | 31 | @Override 32 | public CtMember getMember() { 33 | return ctField; 34 | } 35 | 36 | @Override 37 | public String show() { 38 | 39 | String modifiers = Modifier.toString(modifier); 40 | 41 | return String.format("%s %s %s", modifiers, fieldType,fieldName); 42 | } 43 | @Override 44 | public boolean isSameTarget(PsiMember psiMember) { 45 | if( !(psiMember instanceof PsiField) ){ 46 | return false; 47 | } 48 | PsiField psiField = (PsiField)psiMember; 49 | if( !fieldName.equals(psiField.getName()) ){ 50 | return false; 51 | } 52 | return true; 53 | } 54 | 55 | @Override 56 | public String convertToJavassistCode(PsiFile psiFile, PsiElement psiMember) { 57 | 58 | if( ! (psiMember instanceof PsiField) ) { 59 | return psiMember.getText(); 60 | } 61 | PsiField psiField = (PsiField)psiMember; 62 | 63 | /* 64 | // 获取字段的修饰符列表(包括注解) 65 | PsiModifierList modifierList = psiField.getModifierList(); 66 | 67 | // 构建字段声明部分 68 | StringBuilder fieldDeclarationBuilder = new StringBuilder(); 69 | 70 | // 获取修饰符(例如private, static等),并排除注解 71 | if (modifierList != null) { 72 | for (PsiElement element : modifierList.getChildren()) { 73 | if (!(element instanceof PsiAnnotation)) { 74 | fieldDeclarationBuilder.append(element.getText()).append(" "); 75 | } 76 | } 77 | } 78 | 79 | // 添加字段的类型和名称 80 | fieldDeclarationBuilder.append(psiField.getType().getPresentableText()).append(" "); 81 | fieldDeclarationBuilder.append(psiField.getName()); 82 | */ 83 | 84 | StringBuilder fieldDeclarationBuilder = new StringBuilder(this.show()); 85 | 86 | 87 | // 如果字段有初始化器,添加初始化部分 88 | if (psiField.getInitializer() != null) { 89 | fieldDeclarationBuilder.append(" = ").append(psiField.getInitializer().getText()); 90 | } 91 | 92 | // 结束字段声明 93 | fieldDeclarationBuilder.append(";"); 94 | 95 | // 返回去除注解后的字段声明字符串 96 | return fieldDeclarationBuilder.toString(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/bytestool/javassist/ISignature.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.bytestool.javassist; 2 | 3 | 4 | import com.intellij.psi.PsiElement; 5 | import com.intellij.psi.PsiFile; 6 | import com.intellij.psi.PsiMember; 7 | import javassist.CtMember; 8 | 9 | /** 10 | * @author Liubsyy 11 | * @date 2024/8/28 12 | */ 13 | 14 | public interface ISignature { 15 | 16 | enum Type{ 17 | FIELD("Field"), 18 | METHOD("Method"), 19 | CONSTRUCTOR("Constructor"), 20 | CLASS_INITIALIZER("ClassInitializer"); 21 | public String name; 22 | 23 | Type(String name) { 24 | this.name = name; 25 | } 26 | } 27 | 28 | String show(); 29 | 30 | 31 | boolean isSameTarget(PsiMember psiMember); 32 | 33 | default CtMember getMember(){ 34 | return null; 35 | } 36 | 37 | default String convertToJavassistCode(PsiFile psiFile,PsiElement psiMember){ 38 | return psiMember.getText(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/bytestool/javassist/TargetUnit.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.bytestool.javassist; 2 | 3 | import com.intellij.psi.PsiElement; 4 | import com.intellij.psi.PsiFile; 5 | import com.intellij.psi.PsiMember; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * @author Liubsyy 11 | * @date 2024/8/28 12 | */ 13 | public class TargetUnit { 14 | 15 | private ISignature.Type type; 16 | private ISignature targetSignature; 17 | 18 | public TargetUnit(ISignature.Type type, ISignature targetSignature) { 19 | this.type = type; 20 | this.targetSignature = targetSignature; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | if(null == targetSignature){ 26 | return type.name; 27 | } 28 | if(type == ISignature.Type.CLASS_INITIALIZER) { 29 | return targetSignature.show(); 30 | } 31 | return targetSignature.show()+";"; 32 | } 33 | 34 | public ISignature.Type getType() { 35 | return type; 36 | } 37 | 38 | public ISignature getTargetSignature() { 39 | return targetSignature; 40 | } 41 | 42 | public boolean isSameTarget(PsiMember psiMember) { 43 | if(null == psiMember || null == targetSignature){ 44 | return false; 45 | } 46 | return targetSignature.isSameTarget(psiMember); 47 | } 48 | 49 | public String convertToJavassistCode(PsiFile psiFile,PsiElement psiMember){ 50 | if(targetSignature == null) { 51 | return psiMember.getText(); 52 | } 53 | return targetSignature.convertToJavassistCode(psiFile,psiMember); 54 | } 55 | 56 | @Override 57 | public boolean equals(Object o) { 58 | if (this == o) return true; 59 | if (o == null || getClass() != o.getClass()) return false; 60 | TargetUnit that = (TargetUnit) o; 61 | if(type != that.type) { 62 | return false; 63 | } 64 | if(targetSignature == null && that.targetSignature == null) { 65 | return true; 66 | } 67 | if(null != targetSignature && null != that.targetSignature) { 68 | return targetSignature.getMember() == that.targetSignature.getMember(); 69 | } 70 | return false; 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | if(null == targetSignature) { 76 | return Objects.hash(type); 77 | } 78 | if(null != targetSignature.getMember()) { 79 | return targetSignature.getMember().hashCode(); 80 | } 81 | return Objects.hash(type, targetSignature); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/bytestool/vcb/GotoVisualClassBytesEditor.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.bytestool.vcb; 2 | 3 | import com.intellij.openapi.actionSystem.ActionManager; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import com.intellij.openapi.ui.Messages; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * @author Liubsyy 12 | * @date 2024/11/15 13 | */ 14 | public class GotoVisualClassBytesEditor { 15 | 16 | 17 | private static Method actionPerformed = null; 18 | public static void openVCBEditor(AnActionEvent e){ 19 | AnAction openVCBEditor = ActionManager.getInstance().getAction("vcb.openClassEditor"); 20 | if(null == openVCBEditor) { 21 | Messages.showMessageDialog( "You haven't install VisualClassBytes, please install VisualClassBytes from marketplace!!! ", 22 | "Install VisualClassBytes",Messages.getWarningIcon()); 23 | return; 24 | } 25 | 26 | 27 | //直击调用会被插件审核标记过期API 28 | //openVCBEditor.actionPerformed(e); 29 | 30 | if(null== actionPerformed) { 31 | try { 32 | actionPerformed = AnAction.class.getDeclaredMethod("actionPerformed", AnActionEvent.class); 33 | actionPerformed.setAccessible(true); 34 | } catch (Exception ex) { 35 | ex.printStackTrace(); 36 | } 37 | } 38 | 39 | if(null != actionPerformed){ 40 | try { 41 | actionPerformed.invoke(openVCBEditor,e); 42 | } catch (Exception ex) { 43 | ex.printStackTrace(); 44 | } 45 | } 46 | 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/clipboard/ClipboardToFile.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.clipboard; 2 | 3 | import com.liubs.jareditor.util.MyFileUtil; 4 | 5 | import java.awt.*; 6 | import java.awt.datatransfer.DataFlavor; 7 | import java.awt.datatransfer.Transferable; 8 | import java.awt.datatransfer.UnsupportedFlavorException; 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.nio.file.*; 12 | import java.nio.file.attribute.BasicFileAttributes; 13 | 14 | 15 | /** 16 | * @author Liubsyy 17 | * @date 2024/6/2 18 | */ 19 | public class ClipboardToFile { 20 | private String targetPath; 21 | 22 | public ClipboardToFile(String targetDirectory) { 23 | this.targetPath = targetDirectory; 24 | } 25 | 26 | public CopyResult copyFilesFromClipboard() { 27 | 28 | try{ 29 | Path targetDirectory = Paths.get(targetPath); 30 | if (Files.notExists(targetDirectory)) { 31 | try { 32 | Files.createDirectories(targetDirectory); 33 | } catch (IOException ex) { 34 | ex.printStackTrace(); 35 | return new CopyResult(false, "Can not create directory: " + ex.getMessage()); 36 | } 37 | } 38 | 39 | Transferable clipboardContent = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); 40 | if (clipboardContent != null && clipboardContent.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { 41 | try { 42 | java.util.List files = (java.util.List) clipboardContent.getTransferData(DataFlavor.javaFileListFlavor); 43 | 44 | // 遍历文件列表,检查是文件还是文件夹并相应地复制 45 | for (File file : files) { 46 | if (file.isDirectory()) { 47 | // 如果是目录,则递归复制 48 | copyDirectory(file.toPath(), targetDirectory.resolve(file.getName())); 49 | } else { 50 | // 如果是文件,直接复制 51 | Path targetPath = targetDirectory.resolve(file.getName()); 52 | Files.copy(file.toPath(), targetPath, StandardCopyOption.REPLACE_EXISTING); 53 | } 54 | } 55 | } catch (UnsupportedFlavorException | IOException e) { 56 | return new CopyResult(false, "Unable to get file from clipboard: " + e.getMessage()); 57 | } 58 | 59 | 60 | return new CopyResult(true,null); 61 | } 62 | 63 | return new CopyResult(false, "Clipboard does not contain file"); 64 | }catch (Exception e) { 65 | e.printStackTrace(); 66 | return new CopyResult(false, "copyFilesFromClipboard err: " + e.getMessage()); 67 | } 68 | } 69 | 70 | private void copyDirectory(Path source, Path target) throws IOException { 71 | Files.walkFileTree(source, new SimpleFileVisitor() { 72 | @Override 73 | public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 74 | // 在目标位置创建目录结构 75 | Files.createDirectories(target.resolve(source.relativize(dir))); 76 | return FileVisitResult.CONTINUE; 77 | } 78 | 79 | @Override 80 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 81 | // 复制文件 82 | Files.copy(file, target.resolve(source.relativize(file)), StandardCopyOption.REPLACE_EXISTING); 83 | return FileVisitResult.CONTINUE; 84 | } 85 | }); 86 | } 87 | 88 | 89 | public void deleteTargetDir(){ 90 | try{ 91 | if(null != targetPath) { 92 | MyFileUtil.deleteDir(targetPath); 93 | } 94 | }catch (Throwable fTh) {} 95 | } 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/clipboard/CopyResult.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.clipboard; 2 | 3 | 4 | /** 5 | * @author Liubsyy 6 | * @date 2024/5/9 7 | */ 8 | public class CopyResult { 9 | private final boolean success; 10 | private final String error; 11 | 12 | public CopyResult(boolean success, String error) { 13 | this.success = success; 14 | this.error = error; 15 | } 16 | 17 | public boolean isSuccess() { 18 | return success; 19 | } 20 | 21 | public String getError() { 22 | return error; 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/clipboard/FileToClipBoard.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.clipboard; 2 | 3 | import com.liubs.jareditor.util.MyFileUtil; 4 | import com.liubs.jareditor.util.ScheduleUtil; 5 | 6 | import java.awt.*; 7 | import java.awt.datatransfer.Clipboard; 8 | import java.awt.datatransfer.DataFlavor; 9 | import java.awt.datatransfer.Transferable; 10 | import java.awt.datatransfer.UnsupportedFlavorException; 11 | import java.io.File; 12 | import java.io.IOException; 13 | 14 | /** 15 | * @author Liubsyy 16 | * @date 2024/6/2 17 | */ 18 | public class FileToClipBoard { 19 | 20 | public static CopyResult copyFilesToClipboard(java.util.List files) { 21 | 22 | try{ 23 | // 获取系统剪切板 24 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 25 | 26 | // 创建一个Transferable对象,处理文件列表 27 | Transferable transferable = new Transferable() { 28 | @Override 29 | public DataFlavor[] getTransferDataFlavors() { 30 | return new DataFlavor[]{DataFlavor.javaFileListFlavor}; 31 | } 32 | 33 | @Override 34 | public boolean isDataFlavorSupported(DataFlavor flavor) { 35 | return DataFlavor.javaFileListFlavor.equals(flavor); 36 | } 37 | 38 | @Override 39 | public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { 40 | if (!isDataFlavorSupported(flavor)) { 41 | throw new UnsupportedFlavorException(flavor); 42 | } 43 | return files; 44 | } 45 | }; 46 | 47 | // 将Transferable对象放到剪切板上 48 | clipboard.setContents(transferable, null); 49 | 50 | return new CopyResult(true,null); 51 | }catch (Exception e) { 52 | e.printStackTrace(); 53 | return new CopyResult(false, "copyFilesFromClipboard err: " + e.getMessage()); 54 | }finally { 55 | //60秒后删除临时文件 56 | ScheduleUtil.schedule(()-> 57 | files.forEach(c-> { 58 | if(c.isDirectory()) { 59 | MyFileUtil.deleteDir(c.getAbsolutePath()); 60 | }else { 61 | c.delete(); 62 | } 63 | }), 60); 64 | } 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/CommandParam.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2024/6/1 8 | */ 9 | public class CommandParam { 10 | 11 | //source file path 12 | private List sourcePaths; 13 | 14 | //output dir 15 | private String outPutPath; 16 | 17 | public List getSourcePaths() { 18 | return sourcePaths; 19 | } 20 | 21 | public void setSourcePaths(List sourcePaths) { 22 | this.sourcePaths = sourcePaths; 23 | } 24 | 25 | public String getOutPutPath() { 26 | return outPutPath; 27 | } 28 | 29 | public void setOutPutPath(String outPutPath) { 30 | this.outPutPath = outPutPath; 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/CompilationResult.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2024/5/9 8 | */ 9 | public class CompilationResult { 10 | private final boolean success; 11 | private final List errors; 12 | private final List outputFiles; 13 | 14 | public CompilationResult(boolean success, List errors, List outputFiles) { 15 | this.success = success; 16 | this.errors = errors; 17 | this.outputFiles = outputFiles; 18 | } 19 | 20 | public boolean isSuccess() { 21 | return success; 22 | } 23 | 24 | public List getErrors() { 25 | return errors; 26 | } 27 | 28 | public List getOutputFiles() { 29 | return outputFiles; 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/IMyCompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | import java.util.Collection; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2024/5/17 8 | */ 9 | public interface IMyCompiler { 10 | 11 | /** 12 | * 增加classpath 13 | * @param classPaths 14 | */ 15 | void addClassPaths(Collection classPaths); 16 | 17 | /** 18 | * 设置输出目录 19 | * @param outputDirectory 20 | */ 21 | void setOutputDirectory(String outputDirectory); 22 | 23 | void addSourceCode(String className,String srcCode); 24 | 25 | /** 26 | * 编译目标版本 27 | * @param targetVersion 28 | */ 29 | void setTargetVersion(String targetVersion); 30 | 31 | 32 | /** 33 | * 编译结果 34 | * @return 35 | */ 36 | CompilationResult compile(); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/JavaSourceObject.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | import javax.tools.SimpleJavaFileObject; 4 | import java.net.URI; 5 | 6 | /** 7 | * @author Liubsyy 8 | * @date 2024/5/9 9 | */ 10 | class JavaSourceObject extends SimpleJavaFileObject { 11 | private final String sourceCode; 12 | 13 | protected JavaSourceObject(String className, String sourceCode) { 14 | super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); 15 | this.sourceCode = sourceCode; 16 | } 17 | 18 | @Override 19 | public CharSequence getCharContent(boolean ignoreEncodingErrors) { 20 | return sourceCode; 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/MyJBRJavacCompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | import com.liubs.jareditor.editor.LanguageType; 4 | import com.liubs.jareditor.util.MyFileUtil; 5 | 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | import java.util.List; 9 | 10 | /** 11 | * 使用JBR进行编译 12 | * 可用于编译:依赖SDK的class,IntelliJ插件 13 | * @author Liubsyy 14 | * @date 2024/6/25 15 | */ 16 | public class MyJBRJavacCompiler extends MyJavacCompiler{ 17 | public MyJBRJavacCompiler(String javaHome) { 18 | super(javaHome); 19 | } 20 | 21 | @Override 22 | protected List buildCommand(CommandParam commandParam) { 23 | 24 | //添加lib和plugins的jar作为依赖 25 | if(Files.exists(Paths.get(commandHome+"/lib"))) { 26 | classPaths.addAll(MyFileUtil.findAllJars(commandHome+"/lib")); 27 | } 28 | if(Files.exists(Paths.get(commandHome+"/plugins"))) { 29 | classPaths.addAll(MyFileUtil.findAllJars(commandHome+"/plugins")); 30 | } 31 | 32 | commandHome = commandHome + LanguageType.JAVAC_JBR.getSubCommandHome(); 33 | 34 | //这里遍历搜索bin/javac,mac和windows目录结构还不一样,懒得去枚举了 35 | List javacCommand = MyFileUtil 36 | .searchFile(commandHome, path -> path.toString().replace("\\", "/").contains("/bin/javac")); 37 | if(javacCommand.isEmpty()) { 38 | throw new RuntimeException("javac not found in : "+commandHome); 39 | } 40 | 41 | return super.buildCommand(javacCommand.get(0),commandParam); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/MyJavaFileManager.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | import javax.tools.*; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * @author Liubsyy 15 | * @date 2024/5/9 16 | */ 17 | class MyJavaFileManager extends ForwardingJavaFileManager { 18 | private final String outputDirectory; 19 | private final List compiledFiles; 20 | 21 | protected MyJavaFileManager(StandardJavaFileManager fileManager, String outputDirectory) { 22 | super(fileManager); 23 | this.outputDirectory = outputDirectory; 24 | this.compiledFiles = new ArrayList<>(); 25 | } 26 | 27 | @Override 28 | public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { 29 | Path outputDir = Paths.get(outputDirectory); 30 | if (Files.notExists(outputDir)) { 31 | Files.createDirectories(outputDir); 32 | } 33 | String relativePath = className.replace('.', File.separatorChar) + kind.extension; 34 | Path outputPath = outputDir.resolve(relativePath); 35 | Files.createDirectories(outputPath.getParent()); 36 | 37 | compiledFiles.add(outputPath.toString()); 38 | return new SimpleJavaFileObject(outputPath.toUri(), kind) { 39 | @Override 40 | public OutputStream openOutputStream() throws IOException { 41 | return Files.newOutputStream(outputPath); 42 | } 43 | }; 44 | } 45 | 46 | public List getCompiledFiles() { 47 | return compiledFiles; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/MyJavacCompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | import com.liubs.jareditor.persistent.SDKSettingStorage; 4 | import com.liubs.jareditor.util.StringUtils; 5 | 6 | import java.io.File; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * 调用外部javac命令进行编译 13 | * @author Liubsyy 14 | * @date 2024/5/17 15 | */ 16 | public class MyJavacCompiler extends ProcessCommandCompiler{ 17 | 18 | public MyJavacCompiler(String javaHome) { 19 | super(javaHome); 20 | sourceVersion = targetVersion = "8"; // 默认目标版本 21 | } 22 | 23 | @Override 24 | public void setTargetVersion(String targetVersion) { 25 | if("1.1".equals(targetVersion)){ 26 | this.sourceVersion = "1.3"; // -source 1.1已经不支持了 27 | }else { 28 | this.sourceVersion = targetVersion; 29 | } 30 | this.targetVersion = targetVersion; 31 | } 32 | 33 | @Override 34 | protected String fileType(){ 35 | return "java"; 36 | } 37 | 38 | @Override 39 | protected List buildCommand(CommandParam commandParam){ 40 | // 使用ProcessBuilder启动JDK的javac编译器 41 | String javacPath = commandHome + "/bin/javac"; //javac路径 安装路径 42 | return buildCommand(javacPath,commandParam); 43 | } 44 | 45 | protected List buildCommand(String javacPath,CommandParam commandParam){ 46 | List commands = new ArrayList<>(); 47 | commands.add(javacPath); 48 | commands.add("-d"); 49 | commands.add(commandParam.getOutPutPath()); 50 | commands.add("-source"); 51 | commands.add(sourceVersion); 52 | commands.add("-target"); 53 | commands.add(targetVersion); 54 | commands.add( "-Xlint:none"); 55 | commands.add("-encoding"); 56 | commands.add("UTF-8"); 57 | 58 | SDKSettingStorage sdkSetting = SDKSettingStorage.getInstance(); 59 | String genDebugInfos = sdkSetting.getGenDebugInfos(); 60 | if(StringUtils.isEmpty(genDebugInfos)) { 61 | commands.add("-g"); 62 | }else { 63 | commands.add("-g:"+genDebugInfos); 64 | } 65 | 66 | if(sdkSetting.isParameters() && Double.parseDouble(targetVersion)>=8) { 67 | commands.add("-parameters"); 68 | } 69 | 70 | if(sdkSetting.isProcNone()) { 71 | commands.add("-proc:none"); 72 | } 73 | 74 | if(!classPaths.isEmpty()) { 75 | commands.add( "-classpath"); 76 | commands.add(String.join(File.pathSeparator, classPaths)); 77 | } 78 | commands.add(String.join(" ",commandParam.getSourcePaths())); 79 | return commands; 80 | } 81 | 82 | @Override 83 | protected void putExtra(Map environment){ 84 | //windows下控制台javac中文乱码问题,改编码都不好使,干脆直接输出英文算了 85 | environment.put("JAVA_TOOL_OPTIONS", "-Dfile.encoding=UTF-8 -Duser.language=en"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/MyKotlincCompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | import com.liubs.jareditor.util.OSUtil; 4 | 5 | import java.io.File; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author Liubsyy 12 | * @date 2024/6/1 13 | */ 14 | public class MyKotlincCompiler extends ProcessCommandCompiler{ 15 | 16 | public MyKotlincCompiler(String javaHome) { 17 | super(javaHome); 18 | } 19 | 20 | @Override 21 | protected String fileType() { 22 | return "kt"; 23 | } 24 | 25 | @Override 26 | public void addSourceCode(String className, String srcCode) { 27 | if(className.endsWith(".kt")) { 28 | className = className.replace(".kt" ,""); 29 | } 30 | super.addSourceCode(className, srcCode); 31 | } 32 | 33 | /** 34 | * Kotlin supported versions: [1.6, 1.8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19...] 35 | * @param targetVersion 36 | */ 37 | @Override 38 | public void setTargetVersion(String targetVersion) { 39 | super.setTargetVersion(targetVersion); 40 | if("6".equals(targetVersion)) { 41 | this.targetVersion = "1.6"; 42 | } 43 | if("8".equals(targetVersion)) { 44 | this.targetVersion = "1.8"; 45 | } 46 | } 47 | 48 | @Override 49 | protected List buildCommand(CommandParam commandParam) { 50 | // kotlinc 51 | String kotlinc = commandHome + "/bin/kotlinc"; //kotlinc 52 | 53 | File file = new File(kotlinc+".bat"); 54 | if(OSUtil.isWindows() && file.exists()) { 55 | kotlinc = kotlinc+".bat"; 56 | } 57 | 58 | List commands = new ArrayList<>(); 59 | commands.add(kotlinc); 60 | commands.add("-d"); 61 | commands.add(commandParam.getOutPutPath()); 62 | commands.add("-jvm-target"); 63 | commands.add(targetVersion); 64 | // commands.add( "-Xlint:none"); 65 | // commands.add( "-g"); 66 | if(!classPaths.isEmpty()) { 67 | commands.add( "-classpath"); 68 | commands.add(String.join(File.pathSeparator, classPaths)); 69 | } 70 | commands.add(String.join(" ",commandParam.getSourcePaths())); 71 | return commands; 72 | } 73 | 74 | @Override 75 | protected void putExtra(Map environment) { 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/MyRuntimeCompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | 4 | 5 | import com.liubs.jareditor.persistent.SDKSettingStorage; 6 | import com.liubs.jareditor.util.StringUtils; 7 | 8 | import javax.tools.*; 9 | import java.io.File; 10 | import java.util.*; 11 | 12 | /** 13 | * 运行时编译器 14 | * @author Liubsyy 15 | * @date 2024/5/8 16 | */ 17 | public class MyRuntimeCompiler implements IMyCompiler { 18 | private JavaCompiler compiler; 19 | private List classPaths = new ArrayList<>(); 20 | private List sourceCodes = new ArrayList<>(); 21 | private String outputDirectory = "jar_edit_out"; 22 | 23 | private String sourceVersion = "8"; // 默认目标版本 24 | private String targetVersion = "8"; // 默认目标版本 25 | 26 | public MyRuntimeCompiler() { 27 | this(ToolProvider.getSystemJavaCompiler()); 28 | } 29 | public MyRuntimeCompiler(JavaCompiler compiler) { 30 | this.compiler = compiler; 31 | } 32 | 33 | 34 | @Override 35 | public void addClassPaths(Collection classPaths) { 36 | this.classPaths.addAll(classPaths); 37 | } 38 | 39 | @Override 40 | public void setOutputDirectory(String outputDirectory) { 41 | this.outputDirectory = outputDirectory; 42 | } 43 | 44 | @Override 45 | public void addSourceCode(String className,String srcCode){ 46 | sourceCodes.add(new JavaSourceObject(className,srcCode)); 47 | } 48 | 49 | @Override 50 | public void setTargetVersion(String targetVersion) { 51 | if("1.1".equals(targetVersion)){ 52 | this.sourceVersion = "1.3"; // -source 1.1已经不支持了 53 | }else { 54 | this.sourceVersion = targetVersion; 55 | } 56 | this.targetVersion = targetVersion; 57 | } 58 | 59 | 60 | 61 | @Override 62 | public CompilationResult compile() { 63 | if(null == this.compiler) { 64 | return new CompilationResult(false, 65 | Collections.singletonList("Cannot find Java compiler. Make sure to use a JDK, not a JRE."), null); 66 | } 67 | List options = new ArrayList<>(); 68 | if (!classPaths.isEmpty()) { 69 | options.add("-classpath"); 70 | options.add(String.join(File.pathSeparator, classPaths)); 71 | } 72 | 73 | options.add("-source"); 74 | options.add(sourceVersion); 75 | options.add("-target"); 76 | options.add(targetVersion); 77 | 78 | options.add("-Xlint:none"); 79 | 80 | options.add("-encoding"); 81 | options.add("UTF"); 82 | 83 | 84 | SDKSettingStorage sdkSetting = SDKSettingStorage.getInstance(); 85 | String genDebugInfos = sdkSetting.getGenDebugInfos(); 86 | if(StringUtils.isEmpty(genDebugInfos)) { 87 | options.add("-g"); 88 | }else { 89 | options.add("-g:"+genDebugInfos); 90 | } 91 | 92 | if(sdkSetting.isParameters() && Double.parseDouble(targetVersion)>=8) { 93 | options.add("-parameters"); 94 | } 95 | 96 | if(sdkSetting.isProcNone()) { 97 | options.add("-proc:none"); 98 | } 99 | 100 | DiagnosticCollector diagnostics = new DiagnosticCollector<>(); 101 | StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnostics, null, null); 102 | 103 | MyJavaFileManager fileManager = new MyJavaFileManager(standardFileManager, outputDirectory); 104 | JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, sourceCodes); 105 | 106 | boolean success = task.call(); 107 | List errors = new ArrayList<>(); 108 | for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { 109 | 110 | if(Diagnostic.Kind.ERROR != diagnostic.getKind()) { 111 | continue; 112 | } 113 | 114 | StringBuilder sb = new StringBuilder(); 115 | if(diagnostic.getLineNumber()>0) { 116 | sb.append("line:").append(diagnostic.getLineNumber()); 117 | } 118 | if(diagnostic.getColumnNumber()>0) { 119 | sb.append(" column:").append(diagnostic.getColumnNumber()); 120 | } 121 | sb.append("\n").append( diagnostic.getMessage(null)); 122 | errors.add(sb.toString()); 123 | } 124 | 125 | List outputFiles = fileManager.getCompiledFiles(); 126 | 127 | return new CompilationResult(success, errors, outputFiles); 128 | } 129 | 130 | 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/compile/MySDKDefaultKotlincCompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.compile; 2 | 3 | import com.liubs.jareditor.editor.LanguageType; 4 | 5 | /** 6 | * SDK Default编译kotlin时,使用/plugins/Kotlin/kotlinc进行编译 7 | * @author Liubsyy 8 | * @date 2024/6/25 9 | */ 10 | public class MySDKDefaultKotlincCompiler extends MyKotlincCompiler{ 11 | public MySDKDefaultKotlincCompiler(String javaHome) { 12 | super(javaHome + LanguageType.KOTLINC_SDK_DEFAULT.getSubCommandHome()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/constant/ClassVersion.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.constant; 2 | 3 | import com.intellij.openapi.vfs.VirtualFile; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.Map; 8 | import java.util.TreeMap; 9 | 10 | /** 11 | * 获取class版本 12 | * @author Liubsyy 13 | * @date 2024/5/9 14 | */ 15 | public class ClassVersion { 16 | 17 | //上古时期的版本 18 | public static final Map ELDEN_VERSIONS = new TreeMap<>(); 19 | 20 | //常用版本 21 | public static final Map JAVA_VERSIONS = new TreeMap<>(); 22 | 23 | static { 24 | ELDEN_VERSIONS.put(1,"1.1"); 25 | ELDEN_VERSIONS.put(2,"1.2"); 26 | ELDEN_VERSIONS.put(3,"1.3"); 27 | ELDEN_VERSIONS.put(4,"1.4"); 28 | 29 | JAVA_VERSIONS.put(45, "1.1"); 30 | JAVA_VERSIONS.put(46, "1.2"); 31 | JAVA_VERSIONS.put(47, "1.3"); 32 | JAVA_VERSIONS.put(48, "1.4"); 33 | JAVA_VERSIONS.put(49, "5"); //从java1.5开始命名为 java5 34 | JAVA_VERSIONS.put(50, "6"); 35 | JAVA_VERSIONS.put(51, "7"); 36 | JAVA_VERSIONS.put(52, "8"); 37 | JAVA_VERSIONS.put(53, "9"); 38 | JAVA_VERSIONS.put(54, "10"); 39 | JAVA_VERSIONS.put(55, "11"); 40 | JAVA_VERSIONS.put(56, "12"); 41 | JAVA_VERSIONS.put(57, "13"); 42 | JAVA_VERSIONS.put(58, "14"); 43 | JAVA_VERSIONS.put(59, "15"); 44 | JAVA_VERSIONS.put(60, "16"); 45 | JAVA_VERSIONS.put(61, "17"); 46 | JAVA_VERSIONS.put(62, "18"); 47 | JAVA_VERSIONS.put(63, "19"); 48 | JAVA_VERSIONS.put(64, "20"); 49 | JAVA_VERSIONS.put(65, "21"); 50 | } 51 | 52 | public static String detectClassVersion(VirtualFile file) { 53 | if(!"class".equals(file.getExtension())){ 54 | return null; 55 | } 56 | try (InputStream inputStream = file.getInputStream()) { 57 | 58 | // Skip first 4 bytes (magic number) 59 | inputStream.skip(4); 60 | 61 | // Read minor version (2 bytes, big-endian) 62 | int minorVersion = readTwoBytes(inputStream); 63 | 64 | // Read major version (2 bytes, big-endian) 65 | int majorVersion = readTwoBytes(inputStream); 66 | 67 | String javaVerion = JAVA_VERSIONS.get(majorVersion); 68 | if(null == javaVerion) { 69 | //找规律应该是相差44,如果未来jdk版本不讲武德随便命名的话再改 70 | //52=>8 71 | //53=>9 72 | //... 73 | //65=>21 74 | return String.valueOf(majorVersion - 44); 75 | } 76 | return javaVerion; 77 | } catch (IOException e) { 78 | e.printStackTrace(); 79 | return null; 80 | } 81 | } 82 | 83 | private static int readTwoBytes(InputStream inputStream) throws IOException { 84 | return (inputStream.read() << 8) | inputStream.read(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/constant/JarConstant.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.constant; 2 | 3 | /** 4 | * @author Liubsyy 5 | * @date 2024/10/14 6 | */ 7 | public interface JarConstant { 8 | String STORED = "STORED"; 9 | String DEFLATED = "DEFLATED"; 10 | String[] COMPRESSION_METHODS = {STORED, DEFLATED}; // Method options 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/constant/PathConstant.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.constant; 2 | 3 | /** 4 | * 路径常量 5 | * @author Liubsyy 6 | * @date 2024/6/22 7 | */ 8 | public interface PathConstant { 9 | 10 | //temp目录后缀 11 | String TEMP_SUFFIX = "_temp"; 12 | 13 | //编译保存存放的根目录 14 | String JAR_EDIT_CLASS_PATH = "jar_edit_out"; 15 | 16 | //从剪切版粘贴文件到临时目录 17 | String CLIPBOARD_TO_FILE = "clipboard_to_file"; 18 | 19 | //文件拷贝到剪切板临时目录 20 | String FILE_TO_CLIPBOARD = "file_to_clipboard"; 21 | 22 | //复杂jar依赖临时目录 23 | String DEPENDENCY_DIR = "dependency_temp"; 24 | 25 | //windows命令参数classpath临时文件(命令行最大上限问题) 26 | // String JAR_EDITOR_CLASSPATH_FILE = "/JAR_EDITOR_CLASSPATH.txt"; 27 | 28 | //命令行编译java时临时目录 29 | String JAVA_SOURCE_DIR = "jar_edit_java_source"; 30 | 31 | //导出source jar默认后缀 32 | String EXPORT_SOURCE_NAME_SUFFIX = "-sources-export.jar"; 33 | 34 | //嵌套jar临时目录 35 | String NESTED_JAR_DIR = "jar_nested"; 36 | 37 | //默认备份路径 38 | String DEFAULT_BACKUP_PATH = "/jareditor_backup"; 39 | 40 | //备份数据json文件 41 | String BACKUP_CHANGE_JSON = "change.json"; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/decompile/CFRDecompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.decompile; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VfsUtilCore; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.intellij.util.PathUtil; 7 | import com.liubs.jareditor.constant.PathConstant; 8 | import com.liubs.jareditor.sdk.ProjectDependency; 9 | import com.liubs.jareditor.util.JavaFileUtil; 10 | import org.benf.cfr.reader.api.CfrDriver; 11 | import org.benf.cfr.reader.api.OutputSinkFactory; 12 | import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; 13 | import org.benf.cfr.reader.state.ClassFileSourceImpl; 14 | import org.benf.cfr.reader.util.getopt.OptionsImpl; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.nio.file.Files; 19 | import java.nio.file.Paths; 20 | import java.util.*; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * CFR反编译器 25 | * @author Liubsyy 26 | * @date 2024/10/8 27 | */ 28 | public class CFRDecompiler implements IDecompiler{ 29 | 30 | public String decompile(Project project,Map classBytesMap) { 31 | final StringBuilder sb = new StringBuilder(); 32 | 33 | OutputSinkFactory mySink = new OutputSinkFactory() { 34 | @Override 35 | public List getSupportedSinks(SinkType sinkType, Collection collection) { 36 | return Arrays.asList(SinkClass.STRING, SinkClass.DECOMPILED, 37 | SinkClass.DECOMPILED_MULTIVER,SinkClass.EXCEPTION_MESSAGE); 38 | } 39 | 40 | @Override 41 | public Sink getSink(final SinkType sinkType, final SinkClass sinkClass) { 42 | return sinkable -> { 43 | if (sinkType != SinkType.PROGRESS) { 44 | sb.append(sinkable); 45 | } 46 | }; 47 | } 48 | }; 49 | 50 | HashMap options = new HashMap<>(); 51 | options.put("hideutf", "false"); 52 | options.put("trackbytecodeloc", "true"); 53 | options.put(OptionsImpl.EXTRA_CLASS_PATH.getName(), ProjectDependency.getDependentLib(project).stream() 54 | .map(c-> PathUtil.getLocalPath(c.getPath())) 55 | .collect(Collectors.joining(String.valueOf(File.pathSeparatorChar)))); 56 | 57 | CfrDriver driver = new CfrDriver.Builder() 58 | .withOptions(options) 59 | .withClassFileSource(new ClassFileSourceImpl(OptionsImpl.getFactory().create(options)){ 60 | @Override 61 | public Pair getClassFileContent(String classPath) throws IOException { 62 | if(classBytesMap.containsKey(classPath)) { 63 | return new Pair<>(classBytesMap.get(classPath),classPath); 64 | } 65 | return super.getClassFileContent(classPath); 66 | } 67 | }) 68 | .withOutputSink(mySink) 69 | .build(); 70 | 71 | List toAnalyse = new ArrayList<>(classBytesMap.keySet()); 72 | driver.analyse(toAnalyse); 73 | 74 | return sb.toString(); 75 | } 76 | 77 | @Override 78 | public String decompile(Project project, VirtualFile virtualFile) { 79 | try { 80 | Map classBytesMap = new HashMap<>(); 81 | if(virtualFile.getPath().contains("jar!/")) { 82 | String path = virtualFile.getPath().split("jar!/")[1]; 83 | classBytesMap.put(path, VfsUtilCore.loadBytes(virtualFile)); 84 | }else { 85 | List fullClassFiles = JavaFileUtil.getFullClassFiles(virtualFile.getPath()); 86 | for(String path : fullClassFiles) { 87 | byte[] bytes = Files.readAllBytes(Paths.get(path)); 88 | path = path.split(PathConstant.TEMP_SUFFIX+"/"+ PathConstant.JAR_EDIT_CLASS_PATH+"/")[1]; 89 | classBytesMap.put(path,bytes); 90 | } 91 | } 92 | return decompile(project,classBytesMap); 93 | } catch (Exception e) { 94 | e.printStackTrace(); 95 | } 96 | return ""; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/decompile/DecompiledEnum.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.decompile; 2 | 3 | /** 4 | * 所有反编译器 5 | * @author Liubsyy 6 | * @date 2024/10/7 7 | */ 8 | public enum DecompiledEnum { 9 | FERNFLOWER(0,"Fernflower(Default)", new IdeaDecompiler()), 10 | CFR(1,"CFR", new CFRDecompiler()), 11 | Procyon(2,"Procyon", new ProcyonDecompiler()), 12 | 13 | ; 14 | public int value; 15 | public String name; 16 | public IDecompiler decompiler; 17 | 18 | DecompiledEnum(int value, String name,IDecompiler decompiler) { 19 | this.value = value; 20 | this.name = name; 21 | this.decompiler = decompiler; 22 | } 23 | 24 | public static DecompiledEnum findByName(String name){ 25 | for(DecompiledEnum e : values()) { 26 | if(e.name.equals(name)) { 27 | return e; 28 | } 29 | } 30 | return FERNFLOWER; 31 | } 32 | public static DecompiledEnum findByValue(int value){ 33 | for(DecompiledEnum e : values()) { 34 | if(e.value == value) { 35 | return e; 36 | } 37 | } 38 | return FERNFLOWER; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/decompile/IDecompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.decompile; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | 6 | /** 7 | * @author Liubsyy 8 | * @date 2024/10/8 9 | */ 10 | public interface IDecompiler { 11 | String decompile(Project project,VirtualFile virtualFile); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/decompile/IdeaDecompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.decompile; 2 | 3 | import com.intellij.ide.plugins.IdeaPluginDescriptor; 4 | import com.intellij.ide.plugins.PluginManagerCore; 5 | import com.intellij.openapi.extensions.PluginId; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * IDEA自带反编译器 13 | * @author Liubsyy 14 | * @date 2024/10/8 15 | */ 16 | public class IdeaDecompiler implements IDecompiler{ 17 | 18 | private static ClassLoader pluginClassLoader; 19 | private static Object decompiler; 20 | private static Method decompileMethod; 21 | 22 | @Override 23 | public String decompile(Project project, VirtualFile virtualFile) { 24 | return decompileText(virtualFile); 25 | } 26 | 27 | private static String decompileText(VirtualFile file) { 28 | if(null == pluginClassLoader) { 29 | pluginClassLoader = getPluginClassLoader(); 30 | if(null == pluginClassLoader) { 31 | return ""; 32 | } 33 | } 34 | 35 | // org.jetbrains.java.decompiler.IdeaDecompiler ideaDecompiler = new org.jetbrains.java.decompiler.IdeaDecompiler(); 36 | // String text = (String) ideaDecompiler.decompile(file); 37 | try { 38 | if(null == decompiler) { 39 | Class decompilerCls = pluginClassLoader.loadClass("org.jetbrains.java.decompiler.IdeaDecompiler"); 40 | decompiler = decompilerCls.getConstructor().newInstance(); 41 | } 42 | if(null == decompileMethod) { 43 | decompileMethod = decompiler.getClass().getDeclaredMethod("decompile", VirtualFile.class); 44 | decompileMethod.setAccessible(true); 45 | } 46 | return (String)decompileMethod.invoke(decompiler, file); 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | } 50 | 51 | return ""; 52 | } 53 | 54 | private static ClassLoader getPluginClassLoader(){ 55 | try{ 56 | IdeaPluginDescriptor plugin = PluginManagerCore.getPlugin(PluginId.getId("org.jetbrains.java.decompiler")); 57 | if(null == plugin) { 58 | return null; 59 | } 60 | return plugin.getPluginClassLoader(); 61 | }catch (Throwable ex) { 62 | ex.printStackTrace(); 63 | } 64 | return null; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/decompile/MyDecompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.decompile; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import com.intellij.psi.PsiFile; 6 | import com.intellij.psi.PsiManager; 7 | import com.intellij.util.PsiErrorElementUtil; 8 | import com.liubs.jareditor.persistent.SDKSettingStorage; 9 | 10 | import java.util.Objects; 11 | 12 | /** 13 | * @author Liubsyy 14 | * @date 2024/6/1 15 | */ 16 | public class MyDecompiler { 17 | 18 | public static String getDecompiledText(Project project, VirtualFile file) { 19 | PsiFile psiFile = PsiManager.getInstance(project).findFile(file); 20 | if (psiFile != null && !PsiErrorElementUtil.hasErrors(project, file)) { 21 | if(Objects.equals(file.getExtension(), "class")){ 22 | boolean useDecompiler; 23 | if("java".equalsIgnoreCase(psiFile.getLanguage().getDisplayName())) { 24 | if(SDKSettingStorage.getInstance().getDecompiledTool() == DecompiledEnum.FERNFLOWER.value) { 25 | //如果是默认反编译,则不需要显式编译,psiFile.getText()就是默认IDEA自带反编译的内容 26 | useDecompiler = false; 27 | }else { 28 | useDecompiler = true; 29 | } 30 | }else { 31 | useDecompiler = true; 32 | } 33 | 34 | if(useDecompiler) { 35 | return DecompiledEnum.findByValue(SDKSettingStorage.getInstance().getDecompiledTool()) 36 | .decompiler.decompile(project,file); 37 | }else { 38 | return psiFile.getText(); 39 | } 40 | }else { 41 | return psiFile.getText(); 42 | } 43 | } 44 | return ""; 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/decompile/ProcyonDecompiler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.decompile; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VfsUtilCore; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.liubs.jareditor.constant.PathConstant; 7 | import com.liubs.jareditor.util.JavaFileUtil; 8 | import com.strobel.Procyon; 9 | import com.strobel.assembler.metadata.*; 10 | import com.strobel.decompiler.DecompilationOptions; 11 | import com.strobel.decompiler.DecompilerSettings; 12 | import com.strobel.decompiler.PlainTextOutput; 13 | 14 | import java.io.IOException; 15 | import java.io.StringWriter; 16 | import java.nio.file.Files; 17 | import java.nio.file.Paths; 18 | import java.util.List; 19 | import java.util.jar.JarFile; 20 | 21 | /** 22 | * Procyon反编译器 23 | * @author Liubsyy 24 | * @date 2024/10/8 25 | */ 26 | public class ProcyonDecompiler implements IDecompiler{ 27 | 28 | 29 | private static String decompileClass(String className, ITypeLoader typeLoader){ 30 | StringWriter stringWriter = new StringWriter(); 31 | PlainTextOutput output = new PlainTextOutput(stringWriter); 32 | // 配置反编译设置 33 | DecompilerSettings settings = DecompilerSettings.javaDefaults(); 34 | settings.setForceExplicitImports(true); 35 | settings.setOutputFileHeaderText("\nDecompiled by Procyon v" + Procyon.version() + "\n"); 36 | 37 | // 设置自定义的 TypeLoader 38 | settings.setTypeLoader(typeLoader); 39 | 40 | // 使用 MetadataSystem 来解析类型 41 | MetadataSystem metadataSystem = new MetadataSystem(typeLoader); 42 | TypeReference type = metadataSystem.lookupType(className); 43 | 44 | if (type == null) { 45 | throw new IllegalArgumentException("无法找到类型:" + className); 46 | } 47 | 48 | TypeDefinition resolvedType = null; 49 | try { 50 | resolvedType = type.resolve(); 51 | } catch (Exception e) { 52 | throw new RuntimeException("无法解析类型:" + className, e); 53 | } 54 | 55 | // 创建 DecompilationOptions 并设置反编译设置 56 | DecompilationOptions options = new DecompilationOptions(); 57 | options.setSettings(settings); 58 | options.setFullDecompilation(true); 59 | 60 | // 反编译类型并输出到 StringWriter 61 | settings.getLanguage().decompileType(resolvedType, output, options); 62 | 63 | // 返回反编译后的源代码 64 | return stringWriter.toString(); 65 | } 66 | 67 | 68 | @Override 69 | public String decompile(Project project, VirtualFile virtualFile) { 70 | 71 | JarFile jarFile = null; 72 | try { 73 | String className; 74 | ITypeLoader typeLoader; 75 | if(virtualFile.getPath().contains(".jar!/")) { 76 | String[] split = virtualFile.getPath().split(".jar!/"); 77 | String jarPath = split[0]+".jar"; 78 | className = split[1].replace(".class", ""); 79 | jarFile = new JarFile(jarPath); 80 | typeLoader = new CompositeTypeLoader(new JarTypeLoader(jarFile),new ArrayTypeLoader(VfsUtilCore.loadBytes(virtualFile))); 81 | }else { 82 | className = virtualFile.getPath().split(PathConstant.TEMP_SUFFIX+"/"+ PathConstant.JAR_EDIT_CLASS_PATH+"/")[1].replace(".class", ""); 83 | List fullClassFiles = JavaFileUtil.getFullClassFiles(virtualFile.getPath()); 84 | ITypeLoader[] typeLoaders = new ITypeLoader[fullClassFiles.size()]; 85 | for(int i = 0;i dependencyHandlerList = new ArrayList<>(); 17 | 18 | 19 | public String registryNotStandardJarHandlersWithPath(String packageName,String path){ 20 | String externalPrefix = ""; 21 | if(StringUtils.isNotEmpty(packageName)) { 22 | String entryPathFromJar = MyPathUtil.getEntryPathFromJar(path); 23 | String packagePath = packageName.replace(".", "/"); 24 | if(null != entryPathFromJar && !entryPathFromJar.startsWith(packagePath) ) { 25 | // 形如 /opt/TestDemo.jar!/BOOT-INF/classes/com/liubs/web/Test.class 26 | int i = entryPathFromJar.indexOf(packagePath); 27 | if(i > -1) { 28 | externalPrefix = entryPathFromJar.substring(0,i); 29 | if(!externalPrefix.startsWith("/")) { 30 | externalPrefix = "/"+externalPrefix; 31 | } 32 | registryNotStandardJarHandlersDefault(); 33 | } 34 | } 35 | } 36 | return externalPrefix; 37 | } 38 | 39 | public void registryNotStandardJarHandlersDefault(){ 40 | this.registryNotStandardJarHandler(new SpringBootDependency()); 41 | } 42 | public void registryNotStandardJarHandler(IDependencyHandler dependencyHandler){ 43 | dependencyHandlerList.add(dependencyHandler); 44 | } 45 | 46 | public List handleAndGetDependencyPaths(String jarPath, String tempPath){ 47 | tempPath = tempPath+"/"+ PathConstant.DEPENDENCY_DIR; 48 | List result = new ArrayList<>(); 49 | for(IDependencyHandler c: dependencyHandlerList) { 50 | result.addAll(c.dependentClassPaths(jarPath,tempPath)); 51 | } 52 | 53 | return result; 54 | } 55 | 56 | public String replacePackage(String filePath, String packageName){ 57 | for(IDependencyHandler c: dependencyHandlerList) { 58 | packageName = c.replacePackage(filePath,packageName); 59 | } 60 | 61 | return packageName; 62 | } 63 | 64 | 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/dependency/IDependencyHandler.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.dependency; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2024/5/19 8 | */ 9 | public interface IDependencyHandler { 10 | 11 | List dependentClassPaths(String jarPath,String dependencyRootPath); 12 | 13 | String replacePackage(String filePath,String packageName); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/dependency/NestedJarDependency.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.dependency; 2 | 3 | import com.liubs.jareditor.structure.NestedJar; 4 | import com.liubs.jareditor.util.JarUtil; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.ArrayList; 10 | import java.util.Enumeration; 11 | import java.util.List; 12 | import java.util.jar.JarEntry; 13 | import java.util.jar.JarFile; 14 | 15 | /** 16 | * 嵌套jar依赖,读取上层jar下面的所有嵌套jar作为依赖 17 | * @author Liubsyy 18 | * @date 2024/10/13 19 | */ 20 | public class NestedJarDependency implements IDependencyHandler{ 21 | private NestedJar nestedJar; 22 | 23 | public NestedJarDependency(NestedJar nestedJar) { 24 | this.nestedJar = nestedJar; 25 | } 26 | 27 | @Override 28 | public List dependentClassPaths(String jarPath, String dependencyRootPath) { 29 | List destPaths = new ArrayList<>(); 30 | try (JarFile jarFile = new JarFile(nestedJar.getParentPath())) { 31 | Enumeration entries = jarFile.entries(); 32 | while (entries.hasMoreElements()) { 33 | JarEntry entry = entries.nextElement(); 34 | String entryName = entry.getName(); 35 | 36 | if(entryName.endsWith(".jar")) { 37 | Path destPath = Paths.get(dependencyRootPath, entry.toString()); 38 | JarUtil.createFile(jarFile,entry,destPath); 39 | destPaths.add(destPath.toString()); 40 | } 41 | } 42 | 43 | } catch (IOException ioException) { 44 | ioException.printStackTrace(); 45 | } 46 | return destPaths; 47 | } 48 | 49 | @Override 50 | public String replacePackage(String filePath, String packageName) { 51 | return packageName; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/dependency/SpringBootDependency.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.dependency; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.ArrayList; 10 | import java.util.Enumeration; 11 | import java.util.List; 12 | import java.util.jar.JarEntry; 13 | import java.util.jar.JarFile; 14 | 15 | /** 16 | * FatJar的依赖:需要添加 BOOT-INF/classes和BOOT-INF/lib下的依赖 17 | * -MainBoot.jar 18 | * -BOOT-INF 19 | * -classes 20 | * -lib 21 | * -META-INF 22 | * -org.spring.framework.boot.loader 23 | * 24 | * 为什么不直接复用 FatJar的 URLStreamHandler实现动态添加依赖呢? 25 | * 首先本插件所有代码运行在IDEA自带JDK上,如果依赖的lib版本高于自带JDK的版本,是无法被动态加载的 26 | * 27 | * @author Liubsyy 28 | * @date 2024/5/19 29 | */ 30 | public class SpringBootDependency implements IDependencyHandler{ 31 | 32 | @Override 33 | public List dependentClassPaths(String jarPath, String dependencyRootPath) { 34 | List copiedFiles = new ArrayList<>(); 35 | try (JarFile jarFile = new JarFile(jarPath)) { 36 | Enumeration entries = jarFile.entries(); 37 | while (entries.hasMoreElements()) { 38 | JarEntry entry = entries.nextElement(); 39 | String entryName = entry.getName(); 40 | 41 | if (entryName.startsWith("BOOT-INF/classes/") || entryName.startsWith("BOOT-INF/lib/")) { 42 | Path targetPath = Paths.get(dependencyRootPath, entryName.substring("BOOT-INF/".length())); 43 | 44 | if (entry.isDirectory()) { 45 | Files.createDirectories(targetPath); 46 | } else { 47 | Files.createDirectories(targetPath.getParent()); 48 | try (InputStream inputStream = jarFile.getInputStream(entry); 49 | OutputStream outputStream = Files.newOutputStream(targetPath)) { 50 | byte[] buffer = new byte[4096]; 51 | int bytesRead; 52 | while ((bytesRead = inputStream.read(buffer)) != -1) { 53 | outputStream.write(buffer, 0, bytesRead); 54 | } 55 | } 56 | if(entryName.startsWith("BOOT-INF/lib/")) { 57 | copiedFiles.add(targetPath.toString()); 58 | } 59 | } 60 | } 61 | } 62 | copiedFiles.add(dependencyRootPath+"/classes/"); 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | } 66 | return copiedFiles; 67 | } 68 | 69 | @Override 70 | public String replacePackage(String filePath, String packageName) { 71 | if(filePath.contains("BOOT-INF/classes/")) { 72 | return packageName.replace("BOOT-INF.classes.", ""); 73 | } 74 | if(filePath.contains("BOOT-INF/lib/")) { 75 | return packageName.replace("BOOT-INF.lib.", ""); 76 | } 77 | return packageName; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/editor/BuildJarSelection.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.editor; 2 | 3 | /** 4 | * @author Liubsyy 5 | * @date 2024/10/13 6 | */ 7 | import com.intellij.openapi.ui.ComboBox; 8 | import com.intellij.openapi.ui.DialogWrapper; 9 | import com.liubs.jareditor.constant.JarConstant; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import javax.swing.*; 13 | import java.awt.*; 14 | import java.util.jar.JarEntry; 15 | 16 | public class BuildJarSelection extends DialogWrapper { 17 | 18 | private ComboBox jarComboBox; 19 | private ComboBox methodComboBox; 20 | private String[] nestedJars; 21 | 22 | public BuildJarSelection(String[] nestedJars) { 23 | super(true); 24 | this.nestedJars = nestedJars; 25 | setTitle("Nested Jar Build"); 26 | init(); 27 | } 28 | 29 | @Nullable 30 | @Override 31 | protected JComponent createCenterPanel() { 32 | // Create main panel with a BoxLayout for vertical alignment 33 | JPanel panel = new JPanel(); 34 | panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 35 | 36 | // Add padding/margin around the panel 37 | panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); 38 | 39 | // First row: "Build Jar" label and jarComboBox 40 | JPanel jarPanel = new JPanel(); 41 | jarPanel.setLayout(new BoxLayout(jarPanel, BoxLayout.X_AXIS)); // Horizontal alignment 42 | JLabel jarLabel = new JLabel("Build Jar : "); 43 | jarComboBox = new ComboBox<>(nestedJars); 44 | jarPanel.add(jarLabel); 45 | jarPanel.add(Box.createRigidArea(new Dimension(10, 0))); // Add horizontal spacing 46 | jarPanel.add(jarComboBox); 47 | 48 | // Set preferred size for the ComboBox to make it larger 49 | jarComboBox.setPreferredSize(new Dimension(200, 30)); 50 | panel.add(jarPanel); 51 | 52 | // Add vertical spacing between the first row and second row 53 | panel.add(Box.createRigidArea(new Dimension(0, 10))); // 10px vertical space 54 | 55 | // Second row: "Nested Jar Method" label and methodComboBox 56 | JPanel methodPanel = new JPanel(); 57 | methodPanel.setLayout(new BoxLayout(methodPanel, BoxLayout.X_AXIS)); // Horizontal alignment 58 | JLabel methodLabel = new JLabel("Nested Jar Method : "); 59 | methodComboBox = new ComboBox<>(JarConstant.COMPRESSION_METHODS); 60 | methodPanel.add(methodLabel); 61 | methodPanel.add(Box.createRigidArea(new Dimension(10, 0))); // Add horizontal spacing 62 | methodPanel.add(methodComboBox); 63 | 64 | // Set preferred size for the method ComboBox 65 | methodComboBox.setPreferredSize(new Dimension(200, 30)); 66 | panel.add(methodPanel); 67 | 68 | return panel; 69 | } 70 | 71 | // Getter method to retrieve the selected jar option 72 | public int getSelectedJar() { 73 | return jarComboBox.getSelectedIndex(); 74 | } 75 | 76 | // Getter method to retrieve the selected method option 77 | public int getSelectedMethod() { 78 | return methodComboBox.getSelectedIndex() == 0 ? JarEntry.STORED : JarEntry.DEFLATED; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/editor/LanguageType.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.editor; 2 | 3 | import com.liubs.jareditor.compile.*; 4 | import com.liubs.jareditor.util.StringUtils; 5 | 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | 9 | 10 | /** 11 | * @author Liubsyy 12 | * @date 2024/6/2 13 | */ 14 | public enum LanguageType { 15 | 16 | //javac编译 17 | JAVAC("class",null, MyJavacCompiler::new), 18 | 19 | //使用jbr进行javac编译 20 | JAVAC_JBR("class","/jbr", MyJBRJavacCompiler::new), 21 | 22 | //kotlin编译 23 | KOTLINC("kt", null, MyKotlincCompiler::new), 24 | 25 | //使用SDK Default编译时,自带的kotlinc 26 | KOTLINC_SDK_DEFAULT("kt", "/plugins/Kotlin/kotlinc", MySDKDefaultKotlincCompiler::new), 27 | 28 | 29 | //add more... 30 | 31 | ; 32 | 33 | private String fileExtension; 34 | 35 | /** 命令子目录 */ 36 | private String subCommandHome; 37 | 38 | /** 构造编译器 */ 39 | private ProcessCompilerFactory compilerFactory; 40 | 41 | 42 | 43 | LanguageType(String fileExtension,String subCommandHome, ProcessCompilerFactory compilerFactory) { 44 | this.fileExtension = fileExtension; 45 | this.subCommandHome = subCommandHome; 46 | this.compilerFactory = compilerFactory; 47 | } 48 | 49 | public ProcessCommandCompiler buildCompiler(String commandHome){ 50 | if(null == compilerFactory) { 51 | return null; 52 | } 53 | return compilerFactory.buildCompiler(commandHome); 54 | } 55 | 56 | 57 | public String getSubCommandHome() { 58 | return subCommandHome; 59 | } 60 | 61 | public static LanguageType matchType(String fileExtension, String commandHome){ 62 | LanguageType target = null; 63 | for(LanguageType type : LanguageType.values()) { 64 | if(fileExtension.equals(type.fileExtension)){ 65 | if(StringUtils.isEmpty(type.subCommandHome)){ 66 | target = type; 67 | }else { 68 | if(Files.exists(Paths.get(commandHome + type.subCommandHome))){ 69 | target = type; 70 | } 71 | } 72 | 73 | } 74 | } 75 | 76 | return target; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/editor/MyFileEditorProvider.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.editor; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.fileEditor.FileEditor; 5 | import com.intellij.openapi.fileEditor.FileEditorPolicy; 6 | import com.intellij.openapi.fileEditor.FileEditorProvider; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.concurrent.CompletableFuture; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * @author Liubsyy 16 | * @date 2024/5/8 17 | */ 18 | public class MyFileEditorProvider implements FileEditorProvider { 19 | public static final String EDITOR_TYPE_ID = "liubsyy-jar-editor"; 20 | 21 | @Override 22 | public boolean accept(@NotNull Project project, @NotNull VirtualFile file) { 23 | // 只扩展jar内文件 24 | //return "class".equalsIgnoreCase(file.getExtension()) 25 | return file.getPath().contains(".jar!/"); 26 | } 27 | 28 | @NotNull 29 | @Override 30 | public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) { 31 | try{ 32 | return new MyJarEditor(project, file); 33 | }catch (Throwable ex) { 34 | 35 | /** 36 | * 重新打开IDEA项目时,可能会打开上次的jar(如果上次关闭项目时打开了jar文件的话) 37 | * 而打开项目初始化IDEA项目的线程不一定是EDT线程会有报错,虽然不影响使用,但是还是兼容一下 38 | * 39 | * === 尽管大部分IDEA版本甚至最新版本都没有这个问题,也不影响使用,这里还是做一个容错 === 40 | * 目前发现IDEA2023.2有这个问题 41 | */ 42 | if(!ApplicationManager.getApplication().isDispatchThread()) { 43 | CompletableFuture editorFuture = new CompletableFuture<>(); 44 | 45 | // 在EDT线程中创建Editor,并在完成后将结果设置到editorFuture 46 | ApplicationManager.getApplication().invokeLater(() -> { 47 | try{ 48 | MyJarEditor myJarEditor = new MyJarEditor(project, file); 49 | editorFuture.complete(myJarEditor); 50 | }catch (Throwable e1){ 51 | e1.printStackTrace(); 52 | } 53 | }); 54 | try { 55 | return editorFuture.get(10, TimeUnit.SECONDS); 56 | } catch (Exception e2) { 57 | e2.printStackTrace(); 58 | } 59 | } 60 | throw ex; 61 | } 62 | } 63 | 64 | 65 | @NotNull 66 | @Override 67 | public String getEditorTypeId() { 68 | return EDITOR_TYPE_ID; 69 | } 70 | 71 | @Override 72 | public @NotNull FileEditorPolicy getPolicy() { 73 | return FileEditorPolicy.PLACE_AFTER_DEFAULT_EDITOR; 74 | } 75 | 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/editor/ProcessCompilerFactory.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.editor; 2 | 3 | import com.liubs.jareditor.compile.ProcessCommandCompiler; 4 | 5 | /** 6 | * 编译器构造工程 7 | * @author Liubsyy 8 | * @date 2024/6/2 9 | */ 10 | public interface ProcessCompilerFactory { 11 | ProcessCommandCompiler buildCompiler(String commandHome); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/jarbuild/ChangedItemCallBack.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.jarbuild; 2 | 3 | import java.nio.file.Path; 4 | import java.util.jar.JarOutputStream; 5 | 6 | /** 7 | * @author Liubsyy 8 | * @date 2024/10/13 9 | */ 10 | public interface ChangedItemCallBack { 11 | void writeStream(Path jarEditOutDir, JarOutputStream tempJarOutputStream); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/jarbuild/JarBuildResult.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.jarbuild; 2 | 3 | /** 4 | * @author Liubsyy 5 | * @date 2024/5/9 6 | */ 7 | public class JarBuildResult { 8 | private final boolean success; 9 | private final String err; 10 | 11 | public JarBuildResult(boolean success, String err) { 12 | this.success = success; 13 | this.err = err; 14 | } 15 | 16 | public boolean isSuccess() { 17 | return success; 18 | } 19 | 20 | public String getErr() { 21 | return err; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/jarbuild/PackageRemapper.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.jarbuild; 2 | 3 | import org.objectweb.asm.commons.Remapper; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2024/9/13 8 | */ 9 | public class PackageRemapper extends Remapper { 10 | private String oldPackage; 11 | private String newPackage; 12 | 13 | public PackageRemapper(String oldPackage, String newPackage) { 14 | this.oldPackage = oldPackage; 15 | this.newPackage = newPackage; 16 | } 17 | 18 | @Override 19 | public String map(String internalName) { 20 | if(internalName.startsWith(oldPackage)) { 21 | return internalName.replaceFirst(oldPackage 22 | ,newPackage); 23 | } 24 | return internalName; 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/persistent/BackupStorage.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.persistent; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.components.PersistentStateComponent; 5 | import com.intellij.openapi.components.State; 6 | import com.intellij.openapi.components.Storage; 7 | import com.intellij.util.xmlb.XmlSerializerUtil; 8 | import com.liubs.jareditor.constant.PathConstant; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | /** 13 | * @author Liubsyy 14 | * @date 2025/5/22 15 | */ 16 | @State( 17 | name = "JarEditorBackupStorage", 18 | storages = @Storage("JarEditorBackupStorage.xml") 19 | ) 20 | public class BackupStorage implements PersistentStateComponent { 21 | private boolean enableBackup; 22 | private boolean backupOnce; //只备份第一版 23 | private String backupPath; 24 | 25 | @Nullable 26 | @Override 27 | public BackupStorage getState() { 28 | return this; 29 | } 30 | 31 | @Override 32 | public void loadState(@NotNull BackupStorage state) { 33 | XmlSerializerUtil.copyBean(state, this); 34 | } 35 | 36 | public static BackupStorage getInstance() { 37 | return ApplicationManager.getApplication().getService(BackupStorage.class); 38 | } 39 | 40 | public boolean isEnableBackup() { 41 | return enableBackup; 42 | } 43 | 44 | public void setEnableBackup(boolean enableBackup) { 45 | this.enableBackup = enableBackup; 46 | } 47 | 48 | public boolean isBackupOnce() { 49 | return backupOnce; 50 | } 51 | 52 | public void setBackupOnce(boolean backupOnce) { 53 | this.backupOnce = backupOnce; 54 | } 55 | 56 | public String getBackupPath() { 57 | if(null == backupPath || backupPath.isEmpty()) { 58 | String home = System.getProperty("user.home"); 59 | backupPath = home + PathConstant.DEFAULT_BACKUP_PATH; 60 | } 61 | return backupPath; 62 | } 63 | 64 | public void setBackupPath(String backupPath) { 65 | this.backupPath = backupPath; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/persistent/SDKSettingStorage.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.persistent; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.components.PersistentStateComponent; 5 | import com.intellij.openapi.components.State; 6 | import com.intellij.openapi.components.Storage; 7 | import com.intellij.util.xmlb.XmlSerializerUtil; 8 | import com.intellij.util.xmlb.annotations.Tag; 9 | import com.intellij.util.xmlb.annotations.XCollection; 10 | import com.liubs.jareditor.sdk.SDKManager; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Objects; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * @author Liubsyy 21 | * @date 2024/6/2 22 | */ 23 | @State( 24 | name = "JarEditorSDKSettings", 25 | storages = @Storage("JarEditorSDKSettings.xml") 26 | ) 27 | public class SDKSettingStorage implements PersistentStateComponent { 28 | @XCollection(elementName = "items") 29 | private List mySdks = new ArrayList<>(); 30 | 31 | private String genDebugInfos; 32 | 33 | //最大的jdk版本,1,2,3,4...21,对应1.1, 1.2, 1.3 ... 21 34 | private int maxJavaVersion; 35 | 36 | /** 37 | * 反编译工具 38 | * @see com.liubs.jareditor.decompile.DecompiledEnum 39 | */ 40 | private int decompiledTool; 41 | 42 | //-parameters; 43 | private boolean parameters; 44 | 45 | //-proc:none 46 | private boolean procNone = true; 47 | 48 | @Nullable 49 | @Override 50 | public SDKSettingStorage getState() { 51 | return this; 52 | } 53 | 54 | @Override 55 | public void loadState(@NotNull SDKSettingStorage state) { 56 | XmlSerializerUtil.copyBean(state, this); 57 | } 58 | 59 | public static SDKSettingStorage getInstance() { 60 | return ApplicationManager.getApplication().getService(SDKSettingStorage.class); 61 | } 62 | 63 | 64 | public String getGenDebugInfos() { 65 | return genDebugInfos; 66 | } 67 | 68 | public void setGenDebugInfos(String genDebugInfos) { 69 | this.genDebugInfos = genDebugInfos; 70 | } 71 | 72 | public List getMySdks() { 73 | return mySdks; 74 | } 75 | 76 | public void setMySdks(List mySdks) { 77 | this.mySdks = mySdks; 78 | } 79 | 80 | 81 | public boolean isParameters() { 82 | return parameters; 83 | } 84 | 85 | public void setParameters(boolean parameters) { 86 | this.parameters = parameters; 87 | } 88 | 89 | public static List getMySdksDefaultProjectSdks(){ 90 | List mySdks = SDKSettingStorage.getInstance().getMySdks(); 91 | if(mySdks.isEmpty()) { 92 | List defaultSDKs = new ArrayList<>(); 93 | for(SDKManager.JDKItem jdkItem : SDKManager.getAllJDKs()){ 94 | MyItem myItem = new MyItem(); 95 | myItem.setName(jdkItem.name); 96 | myItem.setPath(jdkItem.javaHome); 97 | defaultSDKs.add(myItem); 98 | } 99 | return defaultSDKs; 100 | } 101 | 102 | return mySdks.stream().filter(Objects::nonNull).collect(Collectors.toList()); 103 | } 104 | 105 | public int getMaxJavaVersion() { 106 | return maxJavaVersion; 107 | } 108 | 109 | public void setMaxJavaVersion(int maxJavaVersion) { 110 | this.maxJavaVersion = maxJavaVersion; 111 | } 112 | 113 | 114 | public int getDecompiledTool() { 115 | return decompiledTool; 116 | } 117 | 118 | public void setDecompiledTool(int decompiledTool) { 119 | this.decompiledTool = decompiledTool; 120 | } 121 | 122 | 123 | public boolean isProcNone() { 124 | return procNone; 125 | } 126 | 127 | public void setProcNone(boolean procNone) { 128 | this.procNone = procNone; 129 | } 130 | 131 | @Tag("item") 132 | public static class MyItem { 133 | private String name; 134 | private String path; 135 | 136 | public String getName() { 137 | return name; 138 | } 139 | 140 | public void setName(String name) { 141 | this.name = name; 142 | } 143 | 144 | public String getPath() { 145 | return path; 146 | } 147 | 148 | public void setPath(String path) { 149 | this.path = path; 150 | } 151 | 152 | 153 | @Override 154 | public boolean equals(Object o) { 155 | if (this == o) return true; 156 | if (o == null || getClass() != o.getClass()) return false; 157 | MyItem myItem = (MyItem) o; 158 | return Objects.equals(name, myItem.name) && Objects.equals(path, myItem.path); 159 | } 160 | 161 | @Override 162 | public int hashCode() { 163 | return Objects.hash(name, path); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/sdk/JavacToolProvider.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.sdk; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.projectRoots.JavaSdk; 5 | import com.intellij.openapi.projectRoots.ProjectJdkTable; 6 | import com.intellij.openapi.projectRoots.Sdk; 7 | import com.intellij.openapi.roots.ProjectRootManager; 8 | import com.liubs.jareditor.persistent.SDKSettingStorage; 9 | 10 | import javax.tools.JavaCompiler; 11 | import javax.tools.ToolProvider; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | /** 16 | * 获取项目JDK的 JavaCompiler 17 | * @author Liubsyy 18 | * @date 2024/5/9 19 | */ 20 | public class JavacToolProvider { 21 | 22 | // 定义正则表达式模式以提取版本号 23 | private static final Pattern JAVA_VERSION_PATTERN = Pattern.compile("(\\d+)(\\.\\d+)*"); 24 | 25 | public static JavaCompiler getJavaCompilerFromProjectSdk() { 26 | 27 | try { 28 | Class javacToolClass = Class.forName("com.sun.tools.javac.api.JavacTool"); 29 | return (JavaCompiler) javacToolClass.getDeclaredConstructor().newInstance(); 30 | } catch (Exception e) { 31 | e.printStackTrace(); 32 | } 33 | 34 | return ToolProvider.getSystemJavaCompiler(); 35 | } 36 | 37 | /** 38 | * 获取项目使用的 JDK 版本 39 | * 40 | * @param project IntelliJ IDEA 项目 41 | * @return JDK 大版本号,例如 8, 11, 17 等 42 | */ 43 | public static int getProjectJdkVersion(Project project) { 44 | // 获取项目使用的 JDK 45 | Sdk projectSdk = ProjectRootManager.getInstance(project).getProjectSdk(); 46 | if (projectSdk != null && projectSdk.getSdkType() instanceof JavaSdk) { 47 | return parseJavaVersion(projectSdk.getVersionString()); 48 | } 49 | 50 | return -1; 51 | } 52 | public static int getMaxJdkVersion() { 53 | 54 | //偏好设置有最大值的话取最大值 55 | int maxJavaVersion = SDKSettingStorage.getInstance().getMaxJavaVersion(); 56 | if(maxJavaVersion > 0) { 57 | return maxJavaVersion; 58 | } 59 | 60 | //从sdk列表中取最大值 61 | int maxVersion = -1; 62 | Sdk[] allJdks = ProjectJdkTable.getInstance().getAllJdks(); 63 | for(Sdk sdk : allJdks) { 64 | if(sdk!=null && sdk.getSdkType() instanceof JavaSdk) { 65 | maxVersion = Math.max(maxVersion, parseJavaVersion(sdk.getVersionString())); 66 | } 67 | } 68 | return maxVersion; 69 | } 70 | 71 | public static int parseJavaVersion(String versionString) { 72 | Matcher matcher = JAVA_VERSION_PATTERN.matcher(versionString); 73 | if (matcher.find()) { 74 | String[] versionParts = matcher.group().split("\\."); 75 | if (versionParts[0].equals("1")) { 76 | // 处理 Java 8 及之前的版本 77 | return Integer.parseInt(versionParts[1]); 78 | } else { 79 | // 处理 Java 9 及之后的版本 80 | return Integer.parseInt(versionParts[0]); 81 | } 82 | } 83 | 84 | // 如果未能匹配版本号,返回 -1 表示未知版本 85 | return -1; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/sdk/MessageDialog.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.sdk; 2 | 3 | import com.intellij.openapi.ui.Messages; 4 | 5 | public class MessageDialog { 6 | 7 | public static String addLineBreaks(String text, int maxLength) { 8 | StringBuilder result = new StringBuilder(); 9 | int index = 0; 10 | while (index < text.length()) { 11 | // 找到下一个要插入换行的位置 12 | int nextBreak = Math.min(index + maxLength, text.length()); 13 | result.append(text, index, nextBreak); 14 | // 如果不是最后一行,添加换行符 15 | if (nextBreak < text.length()) { 16 | result.append("\n"); 17 | } 18 | index = nextBreak; 19 | } 20 | return result.toString(); 21 | } 22 | 23 | public static void showMessageDialog(String title,String message){ 24 | Messages.showMessageDialog( addLineBreaks(message,50), title,Messages.getInformationIcon()); 25 | } 26 | public static void showErrorMessageDialog(String title,String message){ 27 | Messages.showMessageDialog( addLineBreaks(message,50), title,Messages.getErrorIcon()); 28 | } 29 | 30 | public static String showInputDialog(String title,String message) { 31 | String inputValue = Messages.showInputDialog(addLineBreaks(message,50), title, Messages.getQuestionIcon()); 32 | return inputValue; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/sdk/NoticeInfo.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.sdk; 2 | 3 | /** 4 | * @author Liubsyy 5 | * @date 2024/5/9 6 | */ 7 | import com.intellij.notification.*; 8 | import com.intellij.openapi.ui.MessageType; 9 | 10 | import java.text.SimpleDateFormat; 11 | import java.util.Date; 12 | 13 | public class NoticeInfo { 14 | private static NotificationGroup notificationGroup = NotificationGroupManager.getInstance() 15 | .getNotificationGroup("JarEditorNotice"); 16 | 17 | 18 | public static void info(String message){ 19 | Notification notification = notificationGroup.createNotification(message, MessageType.INFO); 20 | Notifications.Bus.notify(notification); 21 | } 22 | public static void info(String message,Object ...param){ 23 | if(message == null || message.isEmpty()) { 24 | return; 25 | } 26 | message = String.format(message,param); 27 | Notification notification = notificationGroup.createNotification(message, MessageType.INFO); 28 | Notifications.Bus.notify(notification); 29 | } 30 | public static void errorWithoutFormat(String message){ 31 | Notification notification = notificationGroup.createNotification(message, MessageType.ERROR); 32 | Notifications.Bus.notify(notification); 33 | } 34 | public static void error(String message){ 35 | Notification notification = notificationGroup.createNotification(message, MessageType.ERROR); 36 | Notifications.Bus.notify(notification); 37 | } 38 | 39 | public static void error(String message,Object ...param){ 40 | if(message == null || message.isEmpty()) { 41 | return; 42 | } 43 | message = String.format(message,param); 44 | Notification notification = notificationGroup.createNotification(message, MessageType.ERROR); 45 | Notifications.Bus.notify(notification); 46 | } 47 | 48 | public static void warning(String message){ 49 | Notification notification = notificationGroup.createNotification(message, MessageType.WARNING); 50 | Notifications.Bus.notify(notification); 51 | } 52 | public static void warning(String message,Object ...param){ 53 | if(message == null || message.isEmpty()) { 54 | return; 55 | } 56 | message = String.format(message,param); 57 | Notification notification = notificationGroup.createNotification(message, MessageType.WARNING); 58 | Notifications.Bus.notify(notification); 59 | } 60 | 61 | public static void auto(String message,boolean enable){ 62 | Notification notification = notificationGroup.createNotification(message, enable ? MessageType.INFO: MessageType.ERROR); 63 | Notifications.Bus.notify(notification); 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/sdk/ProjectDependency.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.sdk; 2 | 3 | import com.intellij.openapi.module.Module; 4 | import com.intellij.openapi.module.ModuleManager; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.projectRoots.Sdk; 7 | import com.intellij.openapi.roots.*; 8 | import com.intellij.openapi.roots.libraries.Library; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import com.intellij.util.PathUtil; 11 | 12 | import java.util.*; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * @author Liubsyy 17 | * @date 2024/5/9 18 | */ 19 | public class ProjectDependency { 20 | 21 | public static List getDependentLib(Project project) { 22 | 23 | List virtualFiles = new ArrayList<>(); 24 | Module[] modules = ModuleManager.getInstance(project).getModules(); 25 | 26 | for (Module module : modules) { 27 | // 获取模块的根模型 28 | ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); 29 | 30 | // 获取模块的条目(类路径、库依赖等) 31 | for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) { 32 | if (orderEntry instanceof LibraryOrderEntry) { 33 | LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry) orderEntry; 34 | Library library = libraryOrderEntry.getLibrary(); 35 | if (library != null) { 36 | virtualFiles.addAll(Arrays.asList(library.getFiles(OrderRootType.CLASSES))); 37 | } 38 | } else if (orderEntry instanceof ModuleSourceOrderEntry) { 39 | virtualFiles.addAll(Arrays.asList(moduleRootManager.getSourceRoots())); 40 | } else if (orderEntry instanceof JdkOrderEntry) { 41 | // 获取 JDK 的类路径依赖 42 | Sdk sdk = ((JdkOrderEntry) orderEntry).getJdk(); 43 | if (sdk != null) { 44 | virtualFiles.addAll(Arrays.asList(sdk.getRootProvider().getFiles(OrderRootType.CLASSES))); 45 | } 46 | } 47 | } 48 | } 49 | 50 | return virtualFiles; 51 | } 52 | 53 | public static List getDependentJar(Project project) { 54 | 55 | List virtualFiles = new ArrayList<>(); 56 | Module[] modules = ModuleManager.getInstance(project).getModules(); 57 | 58 | for (Module module : modules) { 59 | // 获取模块的根模型 60 | ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); 61 | 62 | // 获取模块的条目(类路径、库依赖等) 63 | for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) { 64 | if (orderEntry instanceof LibraryOrderEntry) { 65 | LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry) orderEntry; 66 | Library library = libraryOrderEntry.getLibrary(); 67 | if (library != null) { 68 | VirtualFile[] files = library.getFiles(OrderRootType.CLASSES); 69 | for(VirtualFile file : files) { 70 | if("jar".equals(file.getExtension())){ 71 | virtualFiles.add(file); 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | return virtualFiles; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/sdk/SDKManager.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.sdk; 2 | 3 | import com.intellij.openapi.projectRoots.ProjectJdkTable; 4 | import com.intellij.openapi.projectRoots.Sdk; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Liubsyy 11 | * @date 2024/5/17 12 | */ 13 | public class SDKManager { 14 | 15 | public static class JDKItem{ 16 | public String name; 17 | public String javaHome; 18 | } 19 | 20 | public static List getAllJDKs(){ 21 | Sdk[] allJdks = ProjectJdkTable.getInstance().getAllJdks(); 22 | List jdkItems = new ArrayList<>(); 23 | for(Sdk sdk : allJdks) { 24 | JDKItem item = new JDKItem(); 25 | item.name = sdk.getName(); 26 | item.javaHome = sdk.getHomePath(); 27 | jdkItems.add(item); 28 | } 29 | return jdkItems; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/search/JarFileSearchDialog.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.search; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.intellij.ui.components.JBTabbedPane; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import javax.swing.*; 11 | import java.awt.*; 12 | 13 | /** 14 | * jar包内搜索 15 | * @author Liubsyy 16 | * @date 2024/6/25 17 | */ 18 | public class JarFileSearchDialog extends DialogWrapper { 19 | private final Project project; 20 | 21 | //允许为null,如果为null则只有全局搜索 22 | private final VirtualFile jarFile; 23 | 24 | private SearchInJarPanel searchInJarPanel; 25 | private SearchAllJarPanel searchAllJarPanel; 26 | 27 | public JarFileSearchDialog(@NotNull Project project,VirtualFile jarFile) { 28 | super(true); 29 | 30 | this.project = project; 31 | this.jarFile = jarFile; 32 | 33 | init(); 34 | setTitle("Search in jar"); 35 | pack(); //调整窗口大小以适应其子组件 36 | setModal(false); 37 | } 38 | 39 | @Override 40 | protected @Nullable JComponent createCenterPanel() { 41 | 42 | Dimension dimension = new Dimension(800, 500); 43 | 44 | if(null != jarFile) { 45 | searchInJarPanel = new SearchInJarPanel(project,jarFile); 46 | searchInJarPanel.setPreferredSize(dimension); 47 | } 48 | 49 | searchAllJarPanel = new SearchAllJarPanel(project); 50 | searchAllJarPanel.setPreferredSize(dimension); 51 | 52 | JBTabbedPane tabbedPane = new JBTabbedPane(); 53 | 54 | if(null != jarFile) { 55 | tabbedPane.addTab(jarFile.getName(), searchInJarPanel); 56 | } 57 | tabbedPane.addTab("All Jar", searchAllJarPanel); 58 | return tabbedPane; 59 | } 60 | 61 | 62 | @Override 63 | protected Action [] createActions() { 64 | // Return an empty array to hide OK and Cancel buttons 65 | return new Action[0]; 66 | } 67 | 68 | @Override 69 | public void doCancelAction() { 70 | if(null != searchInJarPanel) { 71 | searchInJarPanel.exit(); 72 | } 73 | 74 | if(null != searchAllJarPanel) { 75 | searchAllJarPanel.exit(); 76 | } 77 | super.doCancelAction(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/search/SearchResultItem.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.search; 2 | 3 | import com.intellij.openapi.vfs.VirtualFile; 4 | import com.liubs.jareditor.util.MyPathUtil; 5 | 6 | /** 7 | * @author Liubsyy 8 | * @date 2024/7/17 9 | */ 10 | public class SearchResultItem { 11 | private VirtualFile file; 12 | private String entryPath; 13 | 14 | public SearchResultItem(VirtualFile file, String entryPath) { 15 | this.file = file; 16 | this.entryPath = entryPath; 17 | } 18 | 19 | public VirtualFile getFile() { 20 | return file; 21 | } 22 | 23 | /** 24 | * 这个toString就是搜索结果展示的行 25 | * @return 26 | */ 27 | @Override 28 | public String toString() { 29 | return String.format("[%s] %s", MyPathUtil.getJarSingleName(file.getPath()),entryPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/structure/CompressionMethodDialog.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.structure; 2 | 3 | import com.intellij.openapi.ui.ComboBox; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import com.liubs.jareditor.constant.JarConstant; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import javax.swing.*; 9 | import java.awt.*; 10 | import java.util.jar.JarEntry; 11 | 12 | /** 13 | * @author Liubsyy 14 | * @date 2024/10/14 15 | */ 16 | public class CompressionMethodDialog extends DialogWrapper { 17 | 18 | private ComboBox methodComboBox; 19 | private String entryName; 20 | private int method; 21 | 22 | public CompressionMethodDialog(String entryName,int method) { 23 | super(true); 24 | this.entryName = entryName; 25 | this.method = method; 26 | setTitle("Entry Compression Method"); 27 | init(); 28 | } 29 | 30 | @Nullable 31 | @Override 32 | protected JComponent createCenterPanel() { 33 | // Create main panel with a BoxLayout for vertical alignment 34 | JPanel panel = new JPanel(); 35 | panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); 36 | 37 | panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); 38 | 39 | JPanel jarPanel = new JPanel(); 40 | jarPanel.setLayout(new BoxLayout(jarPanel, BoxLayout.X_AXIS)); // Horizontal alignment 41 | JLabel jarLabel = new JLabel("Jar Entry : "); 42 | JLabel jarEntry = new JLabel(entryName); 43 | jarPanel.add(jarLabel); 44 | jarPanel.add(Box.createRigidArea(new Dimension(10, 0))); // Add horizontal spacing 45 | jarPanel.add(jarEntry); 46 | 47 | jarEntry.setPreferredSize(new Dimension(200, 30)); 48 | panel.add(jarPanel); 49 | 50 | panel.add(Box.createRigidArea(new Dimension(0, 10))); // 10px vertical space 51 | 52 | JPanel methodPanel = new JPanel(); 53 | methodPanel.setLayout(new BoxLayout(methodPanel, BoxLayout.X_AXIS)); // Horizontal alignment 54 | JLabel methodLabel = new JLabel("Compression Method : "); 55 | methodComboBox = new ComboBox<>(JarConstant.COMPRESSION_METHODS); 56 | methodPanel.add(methodLabel); 57 | methodPanel.add(Box.createRigidArea(new Dimension(10, 0))); // Add horizontal spacing 58 | methodPanel.add(methodComboBox); 59 | methodComboBox.setPreferredSize(new Dimension(200, 30)); 60 | if(method == JarEntry.STORED) { 61 | methodComboBox.setSelectedIndex(0); 62 | }else { 63 | methodComboBox.setSelectedIndex(1); 64 | } 65 | 66 | panel.add(methodPanel); 67 | 68 | return panel; 69 | } 70 | 71 | public int getSelectedMethod() { 72 | return methodComboBox.getSelectedIndex() == 0 ? JarEntry.STORED : JarEntry.DEFLATED; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/structure/JarTreeStructureProvider.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.structure; 2 | 3 | import com.intellij.ide.projectView.TreeStructureProvider; 4 | import com.intellij.ide.projectView.ViewSettings; 5 | import com.intellij.ide.projectView.impl.nodes.PsiFileNode; 6 | import com.intellij.ide.util.treeView.AbstractTreeNode; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.JarFileSystem; 9 | import com.intellij.openapi.vfs.LocalFileSystem; 10 | import com.intellij.openapi.vfs.VirtualFile; 11 | import com.intellij.psi.PsiDirectory; 12 | import com.intellij.psi.PsiManager; 13 | import com.liubs.jareditor.util.MyPathUtil; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.nio.file.Path; 17 | import java.nio.file.Paths; 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.List; 21 | 22 | 23 | /** 24 | * jar文件树扩展,主要兼容嵌套jar的文件树展开 25 | * @author Liubsyy 26 | * @date 2024/10/11 27 | */ 28 | public class JarTreeStructureProvider implements TreeStructureProvider { 29 | 30 | @NotNull 31 | @Override 32 | public Collection> modify(@NotNull AbstractTreeNode parent, 33 | @NotNull Collection> children, 34 | @NotNull ViewSettings settings) { 35 | Project project = parent.getProject(); 36 | if(project == null) { 37 | return children; 38 | } 39 | 40 | NestedJarHolder nestedJarHolder = NestedJarHolder.getInstance(project); 41 | 42 | 43 | List> newChildren = new ArrayList<>(); 44 | final PsiManager psiManager = PsiManager.getInstance(project); 45 | for (AbstractTreeNode child : children) { 46 | boolean isEffectNestedJar = false; 47 | 48 | if(child instanceof PsiFileNode) { 49 | PsiFileNode psiFileNode = (PsiFileNode)child; 50 | VirtualFile file = psiFileNode.getVirtualFile(); 51 | 52 | if (isNestedJar(file)) { 53 | String nestedJarBasePath = MyPathUtil.getNestedJarPath(file.getPath()); 54 | String relatePath = file.getPath().substring(file.getPath().indexOf(".jar!") + ".jar!".length()); 55 | if(null != nestedJarBasePath) { 56 | Path destinationPath = Paths.get(nestedJarBasePath, relatePath); 57 | 58 | VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(destinationPath.toString()); 59 | if(null != virtualFile) { 60 | VirtualFile nestedJarVirtualFile = JarFileSystem.getInstance().getJarRootForLocalFile(virtualFile); 61 | if(null != nestedJarVirtualFile) { 62 | final PsiDirectory psiDir = psiManager.findDirectory(nestedJarVirtualFile); 63 | if(null != psiDir) { 64 | newChildren.add(new NestedJarDirNode(nestedJarVirtualFile,project,psiDir,settings)); 65 | nestedJarHolder.addExpandPath(destinationPath.toString()); 66 | 67 | isEffectNestedJar = true; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | if(!isEffectNestedJar) { 75 | newChildren.add(child); 76 | } 77 | } 78 | 79 | return newChildren; 80 | } 81 | 82 | private boolean isNestedJar(VirtualFile file){ 83 | ////包含.jar!/并且当前文件是.jar,那么一定是嵌套jar 84 | return null != file && file.getPath().contains(".jar!/") 85 | && "jar".equalsIgnoreCase(file.getExtension()); 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/structure/NestedJar.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.structure; 2 | 3 | import com.liubs.jareditor.constant.PathConstant; 4 | import com.liubs.jareditor.util.MyPathUtil; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * 嵌套jar:可支持多层嵌套 11 | * 12 | * /path/a.jar 13 | * /class 14 | * /lib/b.jar 15 | * /class2 16 | * /lib/c1.jar 17 | * /lib/c2.jar 18 | * 19 | * @author Liubsyy 20 | * @date 2024/10/13 21 | */ 22 | public class NestedJar { 23 | public static final String KEY = PathConstant.TEMP_SUFFIX+"/"+PathConstant.NESTED_JAR_DIR; 24 | 25 | /** 26 | * 父节点路径 27 | * 比如 /path/a.jar内嵌套/path/a.jar!/lib/b.jar 28 | * 那么这里的b.jar的parentPath就是 /path/a.jar 29 | */ 30 | private String parentPath; 31 | 32 | /** 33 | * 嵌套jar当前路径 34 | */ 35 | private String currentPath; 36 | 37 | /** 38 | * 原路径,形如/path/a.jar!/lib/b.jar 39 | */ 40 | private String originalPath; 41 | 42 | 43 | public NestedJar(String currentPath) { 44 | if(!currentPath.endsWith(".jar")){ 45 | currentPath = MyPathUtil.getJarFullPath(currentPath); 46 | } 47 | this.currentPath = currentPath; 48 | if(null != currentPath) { 49 | int lastIndexOfKey = currentPath.lastIndexOf(KEY); 50 | if(lastIndexOfKey >0){ 51 | this.parentPath = currentPath.substring(0,lastIndexOfKey)+".jar"; 52 | this.originalPath = this.parentPath+"!"+currentPath.substring(lastIndexOfKey+KEY.length()); 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * 依次列出多层嵌套jar的每一层jar 59 | * @return 60 | */ 61 | public List listDepthJars(){ 62 | List result = new ArrayList<>(); 63 | String currentPath; 64 | NestedJar nestedJar = this; 65 | do{ 66 | result.add(nestedJar); 67 | currentPath = nestedJar.getParentPath(); 68 | if(null != nestedJar.getParentPath()) { 69 | nestedJar = new NestedJar(nestedJar.getParentPath()); 70 | } 71 | }while(null != currentPath); 72 | return result; 73 | } 74 | 75 | 76 | public boolean isNested(){ 77 | return parentPath != null; 78 | } 79 | 80 | public String getParentPath() { 81 | return parentPath; 82 | } 83 | 84 | public String getCurrentPath() { 85 | return currentPath; 86 | } 87 | 88 | public String getOriginalPath() { 89 | return originalPath; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/structure/NestedJarChangedListener.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.structure; 2 | 3 | import com.intellij.ide.projectView.ProjectView; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.vfs.newvfs.BulkFileListener; 6 | import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent; 7 | import com.intellij.openapi.vfs.newvfs.events.VFileEvent; 8 | import com.intellij.util.PathUtil; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @author Liubsyy 15 | * @date 2024/10/13 16 | */ 17 | public class NestedJarChangedListener implements BulkFileListener { 18 | 19 | private final Project project; 20 | public NestedJarChangedListener(Project project) { 21 | this.project = project; 22 | } 23 | 24 | 25 | @Override 26 | public void after(@NotNull List events) { 27 | 28 | NestedJarHolder nestedJarHolder = NestedJarHolder.getInstance(project); 29 | if(null == nestedJarHolder) { 30 | return; 31 | } 32 | boolean refreshFileTree = false; 33 | for(VFileEvent vFileEvent : events) { 34 | 35 | //如果删除了嵌套jar的目标路径,重新刷新一下文件树 36 | if(vFileEvent instanceof VFileDeleteEvent) { 37 | String localPath = PathUtil.getLocalPath(vFileEvent.getPath()); 38 | if(nestedJarHolder.containsPath(localPath)){ 39 | nestedJarHolder.removeExpandPath(localPath); 40 | refreshFileTree = true; 41 | break; 42 | } 43 | } 44 | } 45 | if(refreshFileTree){ 46 | //这行代码会触发 JarTreeStructureProvider 重新刷新 47 | ProjectView.getInstance(project).refresh(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/structure/NestedJarDirNode.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.structure; 2 | 3 | import com.intellij.ide.projectView.ViewSettings; 4 | import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; 5 | import com.intellij.ide.projectView.impl.nodes.PsiFileNode; 6 | import com.intellij.ide.util.treeView.AbstractTreeNode; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import com.intellij.psi.PsiDirectory; 10 | import com.intellij.psi.PsiFile; 11 | import com.intellij.psi.PsiManager; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | 17 | /** 18 | * @author Liubsyy 19 | * @date 2024/10/13 20 | */ 21 | public class NestedJarDirNode extends PsiDirectoryNode { 22 | 23 | private VirtualFile entryFile; 24 | private PsiManager psiManager; 25 | 26 | public NestedJarDirNode(VirtualFile entryFile, Project project, @NotNull PsiDirectory value, ViewSettings viewSettings) { 27 | super(project, value, viewSettings); 28 | this.entryFile = entryFile; 29 | this.psiManager = PsiManager.getInstance(project); 30 | } 31 | 32 | @Override 33 | public Collection> getChildrenImpl() { 34 | VirtualFile[] subChildren = entryFile.getChildren(); 35 | Collection> children = new ArrayList<>(subChildren.length); 36 | for(VirtualFile sub : subChildren) { 37 | if(sub.isDirectory()) { 38 | final PsiDirectory psiDir = psiManager.findDirectory(sub); 39 | if(null != psiDir) { 40 | children.add(new NestedJarDirNode(sub,getProject(),psiDir,getSettings())); 41 | } 42 | }else { 43 | final PsiFile psiFile = psiManager.findFile(sub); 44 | if (psiFile != null) { 45 | children.add(new PsiFileNode(getProject(), psiFile, getSettings())); 46 | } 47 | } 48 | } 49 | return children; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/structure/NestedJarHolder.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.structure; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.Collections; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | /** 11 | * @author Liubsyy 12 | * @date 2024/10/13 13 | */ 14 | public class NestedJarHolder { 15 | 16 | //所有展开的嵌套jar路径 17 | private Set allExpandJars = Collections.synchronizedSet(new HashSet<>()); 18 | 19 | public static NestedJarHolder getInstance(@NotNull Project project) { 20 | return project.getService(NestedJarHolder.class); 21 | } 22 | 23 | public void addExpandPath(String path){ 24 | allExpandJars.add(path); 25 | } 26 | public void removeExpandPath(String path){ 27 | allExpandJars.remove(path); 28 | } 29 | public boolean containsPath(String path){ 30 | return allExpandJars.contains(path); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/template/DefaultParser.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.template; 2 | 3 | /** 4 | * @author Liubsyy 5 | * @date 2024/5/15 6 | */ 7 | public class DefaultParser implements ITextParser{ 8 | public static DefaultParser INSTANCE = new DefaultParser(); 9 | @Override 10 | public String[] parseParams(String filePath) { 11 | return new String[]{filePath}; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/template/ITextParser.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.template; 2 | 3 | /** 4 | * @author Liubsyy 5 | * @date 2024/5/15 6 | */ 7 | public interface ITextParser { 8 | String[] parseParams(String filePath); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/template/JavaTextParser.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.template; 2 | 3 | import com.liubs.jareditor.dependency.ExtraDependencyManager; 4 | import com.liubs.jareditor.util.MyPathUtil; 5 | 6 | /** 7 | * @author Liubsyy 8 | * @date 2024/5/15 9 | */ 10 | 11 | public class JavaTextParser implements ITextParser{ 12 | 13 | @Override 14 | public String[] parseParams(String filePath) { 15 | String classNameFromJar = MyPathUtil.getClassNameFromJar(filePath); 16 | int lastPoint = classNameFromJar.lastIndexOf("."); 17 | String packageName = lastPoint>0 ? classNameFromJar.substring(0,lastPoint) : ""; 18 | String className =classNameFromJar.substring(classNameFromJar.lastIndexOf(".")+1); 19 | 20 | ExtraDependencyManager extraDependencyManager = new ExtraDependencyManager(); 21 | extraDependencyManager.registryNotStandardJarHandlersDefault(); 22 | packageName = extraDependencyManager.replacePackage(filePath,packageName); 23 | 24 | packageName = packageName.isEmpty() ? packageName : ("package "+packageName); 25 | return new String[]{packageName,className}; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/template/KotlinTextParser.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.template; 2 | 3 | import com.liubs.jareditor.dependency.ExtraDependencyManager; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2024/6/3 8 | */ 9 | public class KotlinTextParser implements ITextParser{ 10 | 11 | @Override 12 | public String[] parseParams(String filePath) { 13 | String[] split = filePath.split(".jar!/"); 14 | if(split.length!=2) { 15 | return new String[]{""}; 16 | } 17 | String replace = split[1].replace("/", "."); 18 | String classNameFromJar = replace.endsWith(".kt") ? replace.substring(0,replace.lastIndexOf(".kt")) : replace; 19 | int lastPoint = classNameFromJar.lastIndexOf("."); 20 | String packageName = lastPoint>0 ? classNameFromJar.substring(0,lastPoint) : ""; 21 | 22 | ExtraDependencyManager extraDependencyManager = new ExtraDependencyManager(); 23 | extraDependencyManager.registryNotStandardJarHandlersDefault(); 24 | packageName = extraDependencyManager.replacePackage(filePath,packageName); 25 | 26 | packageName = packageName.isEmpty() ? packageName : ("package "+packageName); 27 | return new String[]{packageName}; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/template/TemplateManager.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.template; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author Liubsyy 11 | * @date 2024/5/14 12 | */ 13 | public class TemplateManager { 14 | 15 | private static final Map templateMap = new HashMap<>(); 16 | 17 | static { 18 | try{ 19 | initTemplates(); 20 | }catch (Throwable e){} 21 | } 22 | 23 | public static void initTemplates() { 24 | ClassLoader classLoader = TemplateType.class.getClassLoader(); 25 | 26 | for(TemplateType templateType : TemplateType.values()) { 27 | try (InputStream inputStream = classLoader.getResourceAsStream(templateType.getTemplateFile())) { 28 | templateMap.put(templateType,new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)); 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | } 34 | 35 | public static String getText(String fileExtension,String filePath) { 36 | if(null == fileExtension || null == filePath) { 37 | return ""; 38 | } 39 | TemplateType templateType = TemplateType.getTemplateType(fileExtension); 40 | if(null != templateType){ 41 | String text = templateMap.get(templateType); 42 | String []params = templateType.getTextParser().parseParams(filePath); 43 | for(int i = 0;i templateTypeMap = new HashMap<>(); 28 | static { 29 | for(TemplateType e : values()) { 30 | templateTypeMap.put(e.fileExtension,e); 31 | } 32 | } 33 | 34 | TemplateType(String fileExtension, String templateFile,ITextParser textParser,boolean addContentWhenCreate) { 35 | this.fileExtension = fileExtension; 36 | this.templateFile = templateFile; 37 | this.textParser = textParser; 38 | this.addContentWhenCreate = addContentWhenCreate; 39 | } 40 | 41 | public static TemplateType getTemplateType(String fileExtension) { 42 | return templateTypeMap.get(fileExtension); 43 | } 44 | 45 | public String getTemplateFile() { 46 | return templateFile; 47 | } 48 | 49 | public ITextParser getTextParser() { 50 | return textParser; 51 | } 52 | 53 | public boolean isAddContentWhenCreate() { 54 | return addContentWhenCreate; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/CommandTools.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | /** 7 | * @author Liubsyy 8 | * @date 2024/6/25 9 | */ 10 | public class CommandTools { 11 | 12 | private static String LINE_SEPARATOR = System.getProperty("line.separator"); 13 | static { 14 | if(null == LINE_SEPARATOR) { 15 | LINE_SEPARATOR = "\n"; 16 | } 17 | } 18 | 19 | public static String exec(String... commands){ 20 | ProcessBuilder processBuilder = new ProcessBuilder(commands); 21 | try { 22 | Process process = processBuilder.start(); 23 | StringBuilder resultBuilder = new StringBuilder(); 24 | try (BufferedReader stdOutReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { 25 | String line; 26 | while ((line = stdOutReader.readLine()) != null) { 27 | resultBuilder.append(line).append(LINE_SEPARATOR); 28 | } 29 | } catch (IOException ex) { 30 | ex.printStackTrace(); 31 | } 32 | int exitCode = process.waitFor(); 33 | if(exitCode == 0) { 34 | return resultBuilder.toString(); 35 | } 36 | } catch (Throwable e) { 37 | e.printStackTrace(); 38 | } 39 | return null; 40 | } 41 | 42 | public static void main(String[] args) { 43 | System.out.println(exec("/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/bin/javac","-version")); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | 7 | /** 8 | * @author Liubsyy 9 | * @date 2025/5/23 10 | */ 11 | public class DateUtil { 12 | public static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; 13 | 14 | public static String formatDate(Date date) { 15 | return formatDate(date,DATE_PATTERN); 16 | } 17 | public static String formatDate(Date date,String pattern) { 18 | SimpleDateFormat sdf = new SimpleDateFormat(pattern); 19 | String returnValue = sdf.format(date); 20 | return returnValue; 21 | } 22 | 23 | public static Date parseDate(String dateStr) throws Exception { 24 | SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN); 25 | return sdf.parse(dateStr); 26 | } 27 | 28 | public static Date addSecond(Date date, int second){ 29 | Calendar calendar = Calendar.getInstance(); 30 | calendar.setTime(date); 31 | calendar.add(Calendar.SECOND, second); 32 | return calendar.getTime(); 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | 6 | /** 7 | * @author Liubsyy 8 | * @date 2023/8/29 9 | */ 10 | public class ExceptionUtil { 11 | 12 | public static String getExceptionTracing(Throwable ex){ 13 | if(ex==null) 14 | return ""; 15 | StringWriter sw = new StringWriter(); 16 | PrintWriter pw = new PrintWriter(sw, true); 17 | ex.printStackTrace(pw); 18 | String stackTraceString = sw.getBuffer().toString(); 19 | return stackTraceString; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/JavaFileUtil.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | import org.objectweb.asm.ClassReader; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * @author Liubsyy 15 | * @date 2024/5/19 16 | */ 17 | public class JavaFileUtil { 18 | 19 | // private static Pattern packagePattern = Pattern.compile("^\\s*package\\s+([\\w.]+)\\s*;", Pattern.MULTILINE); 20 | 21 | //[\p{L}\p{N}_.] 匹配任何语言的字母、数字、下划线 22 | private static Pattern packagePattern = Pattern.compile("^\\s*package\\s+([\\p{L}\\p{N}_.]+)\\s*;", Pattern.MULTILINE); 23 | 24 | /** 25 | * 根据源码获取包名 26 | * @param src 27 | * @return 28 | */ 29 | public static String extractPackageName(String src) { 30 | Matcher matcher = packagePattern.matcher(src); 31 | if (matcher.find()) { 32 | return matcher.group(1); 33 | } 34 | return null; 35 | } 36 | 37 | 38 | /** 39 | * 根据class文件获取全名 40 | * @param classFilePath class文件路径 41 | * @return a/b/c这种格式 42 | */ 43 | public static String getFullClassName(String classFilePath) { 44 | try (FileInputStream fis = new FileInputStream(classFilePath)){ 45 | ClassReader classReader = new ClassReader(fis); 46 | return classReader.getClassName(); 47 | } catch (IOException e) { 48 | e.printStackTrace(); 49 | } 50 | return null; 51 | } 52 | 53 | /** 54 | * 根据class获取内部类+本类 55 | * @param classFilePath 56 | * @return 57 | */ 58 | public static List getFullClassFiles(String classFilePath) { 59 | String extractClassEntryPath = classFilePath.substring(0,classFilePath.length()-".class".length()); 60 | List result = new ArrayList<>(); 61 | File file = new File(classFilePath).getParentFile(); 62 | if(file.isDirectory()) { 63 | File[] files = file.listFiles(); 64 | if(null != files) { 65 | for(File subFile : files) { 66 | String path = subFile.getPath().replace("\\", "/"); 67 | if(path.endsWith(".class") ) { 68 | if(path.equals(classFilePath) || path.startsWith(extractClassEntryPath+"$")) { 69 | result.add(subFile.getPath()); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | return result; 77 | } 78 | 79 | public static void main(String[] args) { 80 | System.out.println(extractPackageName("package com.example;")); 81 | System.out.println(extractPackageName("package com.中文包.example;")); 82 | System.out.println(extractPackageName("package com.中文包23.example;")); 83 | System.out.println(extractPackageName("package com.中文包23_1.example;")); 84 | System.out.println(extractPackageName("package com.example_233.test;")); 85 | System.out.println(extractPackageName("package com._2example.test;")); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | /** 8 | * @author Liubsyy 9 | * @date 2025/5/23 10 | */ 11 | public class JsonUtil { 12 | private static Gson gson = new Gson(); 13 | 14 | public static String toJson(Object o) { 15 | return gson.toJson(o); 16 | } 17 | 18 | 19 | public static final V parse(String json, Class type) { 20 | return gson.fromJson(json, type); 21 | } 22 | 23 | public static final V parse(String json, Type type) { 24 | return gson.fromJson(json, type); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/Md5Util.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | /** 7 | * @author Liubsyy 8 | * @date 2024/5/11 9 | */ 10 | public class Md5Util { 11 | public static String md5(String input) { 12 | return md5(input.getBytes()); 13 | } 14 | public static String md5(byte[] inputBytes) { 15 | try { 16 | MessageDigest md = MessageDigest.getInstance("MD5"); 17 | byte[] digest = md.digest(inputBytes); 18 | return bytesToHex(digest); 19 | } catch (NoSuchAlgorithmException e) { 20 | e.printStackTrace(); 21 | return null; 22 | } 23 | } 24 | 25 | private static String bytesToHex(byte[] bytes) { 26 | StringBuilder sb = new StringBuilder(); 27 | for (byte b : bytes) { 28 | sb.append(String.format("%02x", b)); 29 | } 30 | return sb.toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/MyFileUtil.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.*; 5 | import java.nio.file.attribute.BasicFileAttributes; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.function.Consumer; 9 | import java.util.function.Predicate; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | /** 14 | * @author Liubsyy 15 | * @date 2024/5/9 16 | */ 17 | public class MyFileUtil { 18 | 19 | public static void deleteDir(String dir) { 20 | if(null ==dir) { 21 | return; 22 | } 23 | try { 24 | Path path = Paths.get(dir); 25 | if(!Files.exists(path )) { 26 | return; 27 | } 28 | Files.walkFileTree(path, new SimpleFileVisitor<>() { 29 | @Override 30 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 31 | Files.delete(file); 32 | return FileVisitResult.CONTINUE; 33 | } 34 | 35 | @Override 36 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 37 | Files.delete(dir); 38 | return FileVisitResult.CONTINUE; 39 | } 40 | }); 41 | 42 | } catch (Throwable e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | 47 | 48 | public static List findAllJars(String path) { 49 | return searchFile(path,p -> p.toString().endsWith(".jar")); // 过滤出所有.jar文件 50 | } 51 | 52 | public static List searchFile(String path, Predicate filter) { 53 | List targets = new ArrayList<>(); 54 | if(null ==path) { 55 | return targets; 56 | } 57 | 58 | // 使用try-with-resources确保流会被正确关闭 59 | try (Stream files = Files.walk(Paths.get(path))) { 60 | targets = files 61 | .filter(filter) 62 | .map(p -> p.toAbsolutePath().toString()) // 转换为绝对路径的字符串 63 | .collect(Collectors.toList()); // 将结果收集到列表中 64 | 65 | } catch (IOException e) { 66 | e.printStackTrace(); // 打印异常信息 67 | } 68 | 69 | return targets; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/OSUtil.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | /** 4 | * @author Liubsyy 5 | * @date 2024/5/10 6 | */ 7 | public class OSUtil { 8 | 9 | public static String getOS(){ 10 | String osName = System.getProperty("os.name").toLowerCase(); 11 | return osName; 12 | } 13 | 14 | public static boolean isWindows(){ 15 | return getOS().contains("windows"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/ScheduleUtil.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | import java.util.concurrent.Executors; 4 | import java.util.concurrent.ScheduledExecutorService; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * @author Liubsyy 9 | * @date 2024/6/3 10 | */ 11 | public class ScheduleUtil { 12 | private static ScheduledExecutorService 13 | scheduledExecutor = Executors.newScheduledThreadPool(5); 14 | 15 | 16 | public static void schedule(Runnable runnable,long seconds) { 17 | scheduledExecutor.schedule(runnable,seconds, TimeUnit.SECONDS); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/liubs/jareditor/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.liubs.jareditor.util; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * @author Liubsyy 7 | * @date 2024/5/14 8 | */ 9 | public class StringUtils { 10 | public static boolean isEmpty(String str) { 11 | return null == str || str.isEmpty(); 12 | } 13 | public static boolean isNotEmpty(String str) { 14 | return !isEmpty(str); 15 | } 16 | 17 | 18 | public static String getUUID(){ 19 | return UUID.randomUUID().toString().replace("-", ""); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/icons/MyIcons.java: -------------------------------------------------------------------------------- 1 | package icons; 2 | 3 | import com.intellij.openapi.util.IconLoader; 4 | 5 | import javax.swing.*; 6 | 7 | /** 8 | * @author Liubsyy 9 | * @date 2024/6/26 10 | */ 11 | public interface MyIcons { 12 | Icon KOTLIN_CLASS = IconLoader.getIcon("/icons/classKotlin.svg", MyIcons.class); 13 | Icon CLEAN = IconLoader.getIcon("/icons/clean.svg", MyIcons.class); 14 | Icon RESET = IconLoader.getIcon("/icons/reset.svg", MyIcons.class); 15 | Icon TOOL = IconLoader.getIcon("/icons/tool.svg", MyIcons.class); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/icons/classKotlin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/icons/clean.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/icons/reset.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/icons/tool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/template/MANIFEST.template: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: JarEditor 3 | -------------------------------------------------------------------------------- /src/main/resources/template/java.template: -------------------------------------------------------------------------------- 1 | $1$; 2 | 3 | public class $2$ { 4 | 5 | } -------------------------------------------------------------------------------- /src/main/resources/template/kotlin.template: -------------------------------------------------------------------------------- 1 | $1$ -------------------------------------------------------------------------------- /src/main/resources/template/xml.template: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/java/javassist/target/TestClass.java: -------------------------------------------------------------------------------- 1 | package javassist.target; 2 | 3 | /** 4 | * jar内新增此class,然后用javassist进行增删改测试 内部类和字段重名问题和内部类修改问题 5 | * @author Liubsyy 6 | * @date 2024/9/15 7 | */ 8 | public class TestClass { 9 | private static String staticName = "TestClass"; 10 | private String name; 11 | private int value=100; 12 | private int value1; 13 | private int value2; 14 | 15 | static { 16 | System.out.println("TestClass static..."); 17 | } 18 | 19 | public TestClass() { 20 | name = "Tom"; 21 | } 22 | 23 | public TestClass(String name, int value) { 24 | this.name = name; 25 | this.value = value; 26 | } 27 | 28 | public void setValue1(int value1) { 29 | this.value1 = value1; 30 | } 31 | 32 | public void setValue2(int value2) { 33 | this.value2 = value2; 34 | } 35 | 36 | static class InnerClass1{ 37 | private static String staticName = "InnerClass1"; 38 | private String name; 39 | private int value=1; 40 | private int innerClass1_value1; 41 | private int innerClass1_value2; 42 | 43 | public InnerClass1(String name, int value) { 44 | this.name = name; 45 | this.value = value; 46 | } 47 | 48 | public void setInnerClass1_value1(int innerClass1_value1) { 49 | this.innerClass1_value1 = innerClass1_value1; 50 | } 51 | 52 | public void setInnerClass1_value2(int innerClass1_value2) { 53 | this.innerClass1_value2 = innerClass1_value2; 54 | } 55 | 56 | static class InnerClass11{ 57 | private static String staticName = "InnerClass11"; 58 | private String name; 59 | private int value=11; 60 | 61 | public InnerClass11(String name, int value) { 62 | this.name = name; 63 | this.value = value; 64 | } 65 | } 66 | static class InnerClass12{ 67 | private static String staticName = "InnerClass12"; 68 | private int value=12; 69 | private String name; 70 | } 71 | static class InnerClass13{ 72 | private static String staticName = "InnerClass13"; 73 | private int value=13; 74 | private String name; 75 | } 76 | 77 | } 78 | 79 | static class InnerClass2{ 80 | private static String staticName = "InnerClass2"; 81 | private String name; 82 | private int value=2; 83 | private int innerClass2_value1; 84 | private int innerClass2_value2; 85 | 86 | public InnerClass2(String name, int value) { 87 | this.name = name; 88 | this.value = value; 89 | } 90 | 91 | public int getInnerClass2_value1() { 92 | return innerClass2_value1; 93 | } 94 | 95 | public int getInnerClass2_value2() { 96 | return innerClass2_value2; 97 | } 98 | } 99 | 100 | static class InnerClass3{ 101 | private static String staticName = "InnerClass3"; 102 | private String name; 103 | private int value=3; 104 | private int innerClass3_value1; 105 | private int innerClass3_value2; 106 | 107 | public InnerClass3(String name, int value) { 108 | this.name = name; 109 | this.value = value; 110 | } 111 | 112 | public void setInnerClass3_value1(int innerClass3_value1) { 113 | this.innerClass3_value1 = innerClass3_value1; 114 | } 115 | 116 | public void setInnerClass3_value2(int innerClass3_value2) { 117 | this.innerClass3_value2 = innerClass3_value2; 118 | } 119 | 120 | static class InnerClass31{ 121 | private static String staticName = "InnerClass31"; 122 | private String name; 123 | private int value=31; 124 | private int innerClass31_value1; 125 | 126 | public InnerClass31(String name, int value) { 127 | this.name = name; 128 | this.value = value; 129 | } 130 | 131 | public void setInnerClass31_value1(int innerClass31_value1) { 132 | this.innerClass31_value1 = innerClass31_value1; 133 | } 134 | } 135 | static class InnerClass32{ 136 | private static String staticName = "InnerClass32"; 137 | private int value=32; 138 | private String name; 139 | private int innerClass32_value1; 140 | 141 | public void setInnerClass32_value1(int innerClass32_value1) { 142 | this.innerClass32_value1 = innerClass32_value1; 143 | } 144 | } 145 | static class InnerClass33{ 146 | private static String staticName = "InnerClass33"; 147 | private int value=33; 148 | private String name; 149 | private int innerClass33_value1; 150 | 151 | public void setInnerClass33_value1(int innerClass33_value1) { 152 | this.innerClass33_value1 = innerClass33_value1; 153 | } 154 | } 155 | 156 | } 157 | 158 | } 159 | --------------------------------------------------------------------------------