├── .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)
11 | [](https://plugins.jetbrains.com/plugin/24397-jareditor)
12 | [](https://plugins.jetbrains.com/plugin/24397-jareditor)
13 | 
14 |
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 |
9 |
10 | [](./LICENSE)
11 | [](https://plugins.jetbrains.com/plugin/24397-jareditor)
12 | [](https://plugins.jetbrains.com/plugin/24397-jareditor)
13 | 
14 |
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 extends JavaFileObject> 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 extends VFileEvent> 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 |
--------------------------------------------------------------------------------