├── .gitignore
├── LICENSE
├── README.md
├── README_CN.md
├── articles
└── introduction.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jdk.md
├── libs
└── velocity-1.7.jar
├── screenShot
├── DebugWithHotSwap.png
├── HotSwapHelperChangeCodeWork.gif
├── RunAndDebugIcon.png
└── SupportRuoyiVuePlus.gif
├── settings.gradle
└── src
└── main
├── java
└── org
│ └── hotswap
│ └── hotswaphelper
│ ├── CheckResult.java
│ ├── HotSwapAction.java
│ ├── HotSwapDebugAction.java
│ ├── HotSwapDebugExecutor.java
│ ├── HotSwapExecutor.java
│ ├── IconUtils.java
│ ├── JdkManager.java
│ ├── ReloadChangedClassAction.java
│ ├── StartUpListener.java
│ ├── actions
│ └── GenerateHotswapPropertiesAction.java
│ ├── runner
│ ├── HotSwapDebugRunner.java
│ ├── HotSwapRunner.java
│ ├── HotswapPatcher.java
│ └── MyRunner.java
│ ├── settings
│ ├── HotSwapHelperPluginSettingsConfigurable.kt
│ └── HotSwapHelperPluginSettingsProvider.kt
│ ├── ui
│ ├── CopyTextDialog.form
│ ├── CopyTextDialog.java
│ ├── HotSwapAgentPluginSettingsForm.form
│ ├── HotSwapAgentPluginSettingsForm.java
│ ├── JdkNotSupportedDialog.form
│ └── JdkNotSupportedDialog.java
│ └── utils
│ ├── ArchiveUtils.java
│ ├── DownloadTask.java
│ ├── FilePermissionUtils.java
│ ├── FileUtils.java
│ ├── HotswapPropertiesGenerator.java
│ ├── JdkConfigurationUtils.java
│ └── MyUtils.java
└── resources
├── HotSwapHelperIntellijPluginBundle.properties
├── META-INF
├── conf_gradle.xml
├── plugin.xml
└── pluginIcon.svg
├── debugger-agent.jar
├── hotswap-agent.jar
├── icons
├── PluginIcons.java
├── icon.png
├── icon@2x.png
├── icon_16.png
├── icon_64.png
├── rotate.png
├── rotate@2x.png
└── rotate_64.png
├── pluginIcon.svg
├── string.properties
├── string_zh_CN.properties
└── template
└── hotswap-agent.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | ### IntelliJ IDEA ###
2 |
3 | ## 忽略其他配置文件,防止冲突
4 | .idea/
5 | .gradle/
6 | *.iml
7 |
8 | gradle.properties
9 |
10 | ##忽略输出文件夹
11 | out/
12 | build/
13 | idea-sandbox
14 | jvmargs.txt
15 | /.fastRequest/config/fastRequestCurrentProjectConfig.json
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## HotSwapHelper - IntelliJ Plugin Based on HotSwapAgent for Hot Code Swapping
2 |
3 | [][plugin]
4 | [中文文档](https://github.com/gejun123456/HotSwapHelper/blob/master/README_CN.md)
5 | ### Usage Instructions
6 |
7 | 1. Visit [HotSwapHelper Releases](https://github.com/gejun123456/HotSwapHelper/releases) and download the JDK corresponding to your version.
8 | 2. After downloading, extract the files and configure IntelliJ IDEA to use the corresponding JDK.
9 | 3. Once HotSwapHelper is installed, two icons will appear next to the Debug button in IDEA. Click "Debug with HotSwap" to perform hot swap.
10 | 4. Check the logs for the appearance of `org.hotswap.agent.HotswapAgent`. If it appears, the agent has been successfully loaded.
11 | 5. After change code, need recompile the file or build the project(Ctrl+Shift+F9 or Ctrl+F9), then it will automatically hot swap.
12 |
13 | ### Supported Frameworks
14 |
15 | #### DO not use rebuild module or rebuild project it will reload all classes and slow. only rebuild file or build module or build project.
16 |
17 | #### Compile code not work. no error no log
18 |
19 | 1. make sure intellij setting `Build, Execution, Deployment/Debugger/HotSwap/Reload class after compilation is Always.
20 |
21 | #### All plugins supported by HotSwapAgent are also supported by HotSwapHelper, including:
22 | Spring Boot, Spring MVC, Hibernate, MyBatis, MyBatis-Plus, Log4j, etc. For more information, visit the [HotSwapAgent GitHub page](https://github.com/HotswapProjects/HotswapAgent).
23 |
24 |
25 | #### java.lang.NoClassDefFoundError: org/hotswap/agent/config/PluginManager
26 | Edit Configuration -> Shorten Command Line -> Jar manifest
27 |
28 | ### Open-Source Projects Tested Locally
29 | Project Name | URL | Supported Features | Additional Notes
30 | ----- |---| -----| -----
31 | RuoYi | [https://github.com/yangzongzhuan/RuoYi](https://github.com/yangzongzhuan/RuoYi) | Supports MyBatis XML hot-swapping and Java hot-swapping |
32 | Jeecg | [https://github.com/jeecgboot/JeecgBoot](https://github.com/jeecgboot/JeecgBoot) | Supports MyBatis XML hot-swapping and Java hot-swapping |
33 |
34 |
35 | ### Common Question?
36 |
37 | #### Why need to download a jdk?
38 | The jdk provided by [HotSwapHelper Releases](https://github.com/gejun123456/HotSwapHelper/releases) has package
39 | dcevm with hotswap folder into it, so you don't need to install dcevm separately.
40 | If you don't like it, you can always install by yourself from doc:[HotSwapAgent GitHub page](https://github.com/HotswapProjects/HotswapAgent).
41 |
42 | #### IDEA 2024.3 meet NoClassFound Exception
43 | 1. "Build, Execution, Deployment" → "Debugger" → "Async Stack Traces" and uncheck the "Instrumenting agent".
44 |
45 | ### Encountering Issues?
46 | Just issue on github or contact me following:
47 | You can contact me by joining the QQ group: [HotSwapHelper Plugin User Group](https://qm.qq.com/q/JQKyhlt4ke)
48 | Or send an email to: gejun123456@gmail.com
49 |
50 | ### mac aarch64 m serials java8 jdk which to use
51 | Current there is no jdk for mac aarch64 m serials java8.
52 | macAdoptJdk-1.8-x64 will need emulation, the speed is slow.
53 | or use jbr11-aarch version most case will work with no problem.
54 |
55 | ### ScreenShot
56 | 
57 | 
58 |
59 | [plugin]: https://plugins.jetbrains.com/plugin/25171
60 |
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | ## HotSwapHelper 基于HotSwapAgent热更新的Intellij插件,修改代码后无需重启服务器
2 | ### 完美兼容国内常见的springboot+mybatis+mybatisplus等项目的热加载,完美兼容若依,ruoyi vue pro,jeecg等项目
3 |
4 | [][plugin]
5 | [](https://qm.qq.com/q/JQKyhlt4ke)
6 |
7 | ### 使用说明
8 |
9 | 1. https://github.com/gejun123456/HotSwapHelper/releases 根据自己的jdk版本去下载对应的jdk或者去qq群下载
10 | 2. 下载后解压,idea配置为对应的jdk
11 | 3. Intellij安装了HotSwapHelper插件后在idea的debug按钮旁边有个图标,使用Debug With HotSwap 即可热更新
12 | 4. 查看日志中是否出现org.hotswap.agent.HotswapAgent字样,出现则代表加载成功了
13 | 5. 修改代码后,需要编译一下Ctrl F9(build project),多模块的项目只用编译修改了代码的模块就行Build module.
14 |
15 | ### 支持框架
16 |
17 | #### hotSwapAgent支持的插件都支持 包括以下
18 | springboot,springmvc,hibernate,mybatis,mybatis-plus,log4j等等,可以参考https://github.com/HotswapProjects/HotswapAgent
19 | 目前已支持国内常见的springboot+mybatis项目
20 |
21 |
22 | #### HotSwapAgent支持的操作
23 | 1. 修改java方法,增减java方法字段等等,修改xml,增减xml等,唯一不支持的操作是修改类继承关系比如改变父类或者删除接口
24 |
25 | #### Ctrl+F9速度很慢
26 | 1. 修改一个文件可以用ctrl shift F9 修改单个module的话在Build菜单 Build Module或者文件夹上build module
27 | 2. 不要使用Rebuild module 或者 Rebuild project 会导致去加载所有的类
28 |
29 | #### 建议给Build module加一个快捷键 比如ctrl alt shift F9,使用起来更方便
30 | #### 多模块项目 ctrl F9编译后导致很多类 reload导致程序或者热加载失败
31 | 如果你没有修改resource文件比如xml或者properties,yml等,但是target目录里的文件夹时间更新了,比如依赖的module的target文件夹里面的目录
32 | 这个问题是Intellij第二次ctrl F9去更新了target目录的resource文件导致的。
33 | 解决方法:
34 | 1. 在maven或gradle clean后请用ctrl F9编译项目两次,然后再启动项目。
35 | 2. 可以使用Build module编译单个module。
36 | 3. 如果还有问题,可以查看intellij的版本是否是2023的版本,升级到2024版本。
37 | 4. 禁用掉springboot插件
38 | 5. 其他问题请联系我来看看
39 | #### 修改了xml后用ctrl shift F9报错了 File Not Found
40 | 可以使用ctrl F9或者在xml的文件夹上用ctrl Shift F9或者build module
41 |
42 | #### 支持的框架的版本
43 |
44 | 参考文章:https://github.com/HotswapProjects/HotswapAgent?tab=readme-ov-file#java-frameworks-plugins
45 | mybatis最好在3.5.0以上
46 | mybatisplus需要3.2.0以上
47 | pageHelper正在兼容中 插件1.0.3版本已兼容
48 | dynamic-datasource最好在3.6.1以上的版本,低于该版本看启动是否会报错
49 | dubbo正在兼容中
50 |
51 | #### mybatisplus修改xml后分页sql有问题
52 | 1. 升级插件到最新版本1.0.4 qq群文件有
53 |
54 | #### dynamic datasource加了注解后不生效
55 | 1. 升级插件到最新版本1.0.4 qq群文件有
56 |
57 | ### 本地测试已通过的一些开源项目
58 | 项目名称 | 地址 | 支持的地方 |更多说明
59 | ----- |---| -----| -----
60 | 若依 | https://github.com/yangzongzhuan/RuoYi | 支持mybatis xml热加载 java热加载
61 | RuoYi-Vue-Plus | https://gitee.com/dromara/RuoYi-Vue-Plus | 支持mybatis xml热加载 java热加载
62 | jeecg |https://github.com/jeecgboot/JeecgBoot | 支持mybatis xml热加载 java热加载
63 | mall |https://gitee.com/macrozheng/mall| 支持mybatis xml热加载 java热加载
64 | ruoyi-vue-pro|https://gitee.com/zhijiantianya/ruoyi-vue-pro|支持mybatis xml热加载 java热加载
65 | ruoyi_flowable|https://gitee.com/tony2y/RuoYi-flowable.git|支持mybatis xml热加载 java热加载
66 |
67 |
68 | ### 和其他方案对比
69 |
70 | 名称 | 是否收费 | 优点 | 缺点
71 | ----- |--------|----------------------| -----
72 | Jrebel| 是 收费很贵 | 兼容框架较多 兼容一些老版本的框架 | 测试不兼容mybatis实体类加减字段,不支持部分spring aop
73 | HotSwapAgent| 免费开源 | 开源可自己修改代码兼容一些框架,目前已完美兼容常见的springboot+mybatis+mybatisplus等项目 | 部分框架还未兼容
74 | Spring dev tool | 免费开源 | 依赖springboot,使用重启技术,兼容性好 | 使用重启技术 对于大点的项目速度较慢
75 |
76 |
77 | ### 常见问题
78 |
79 | #### 使用run修改java代码不生效
80 | 需要使用Debug with hotSwap
81 |
82 | #### 编译代码后没反应 日志中没有错误也没有新日志
83 |
84 | 1. 确保intellij配置 `Build, Execution, Deployment/Debugger/HotSwap/Reload class after compilation 为Always.
85 | 2. 使用Debug with HotSwapAgent启动
86 |
87 | #### maven热加载失败 程序直接退出了
88 |
89 | 1. 确保intellij配置 `Build, Execution, Deployment/Build tools/Maven/Runner/Delegate IDE build to maven 不要勾选
90 |
91 | #### spring getBean找不到类
92 | 1. 如果这个bean使用了接口,请使用接口来找或者用bean的名字来查找bean
93 |
94 |
95 | #### 升级到IDEA 2024.3版本出现NoClassFoundException或其他报错
96 | 1. 升级下插件 如果还不行则配置设置"Build, Execution, Deployment" → "Debugger" → "Async Stack Traces" 关闭 "Instrumenting agent"选项
97 |
98 | #### 修改类后A fatal error has been detected by the Java Runtime Environment:
99 | 1. 一般是jdk8的问题,可以用jdk11看看,有问题也可联系我来看看
100 |
101 |
102 | #### tomcat或者jetty等项目如何热加载:
103 | 1. 1.1.5版本的插件在设置->HotSwapHelper界面可以导出vm参数,然后在tomcat或者jetty的vm参数中加入这个参数,修改代码或xml文件需要使用
104 | 点击update按钮使用 update classes and resources选项即可热加载
105 |
106 | #### tomcat多模块项目无法热加载xml等
107 | 1. 需要resource目录添加 hotswap-agent.properties文件 里面有个选项 extraClasspath=C:/Users/Administrator/Downloads/SpringMVCForTomcat/SpringMVCForTomcat/target/classes 添加其他模块的classes路径就能热加载了
108 |
109 | #### Intellij新ui 按钮看不到 怎么拖出来
110 |
111 | 1.在IDEA debug 按钮旁边空白的地方鼠标右键 有个 customize Toolbar -> Add Actions -> 搜索Hotswap 然后添加到General Actions中
112 |
113 |
114 | #### java17启动报错 java.nio.channels.ReadableByteChannel sun.nio.ch.ChannelInputStream.ch accessible: module java.base does not "opens sun.nio.ch" to unnamed module @8297b3a
115 |
116 | 使用插件新版本即
117 | #### java.lang.NoClassDefFoundError: org/hotswap/agent/config/PluginManager
118 | Edit Configuration -> Shorten Command Line -> Jar manifest
119 |
120 | #### 为什么需要下载一个 jdk?
121 | [HotSwapHelper Releases](https://github.com/gejun123456/HotSwapHelper/releases)这里提供的 jdk 包含了dcevm 和hotswap 文件夹,统一在一个位置,
122 | dcevm用来支持更好的热加载功能,比如类加减字段和方法等。
123 | 无需自己再去安装dcevm或者根据jdk版本去查对应的jdk加入agent文件等,方便用户使用。
124 | 如果您不喜欢这种方式,您可以根据文档[HotSwapAgent GitHub page](https://github.com/HotswapProjects/HotswapAgent)自行安装
125 |
126 | #### 适用于Mac Aarch64 M芯片的 Java 8 JDK 选择
127 | 目前dcevm没有专门为 Mac Aarch64 M 系列设计的 Java 8 JDK。
128 | 如果使用 **macAdoptJdk-1.8-x64**,则需要通过转译运行,速度较慢。
129 | 或者可以使用 **jbr11-aarch** 版本,大多数情况下可以正常运行,没有问题。
130 |
131 |
132 | #### 其他问题也可以查看HotSwapAgent 看看是否支持或者去提交issue https://github.com/HotswapProjects/HotswapAgent
133 |
134 | ### 碰到问题
135 | 可以联系我,加入qq群: [](https://qm.qq.com/q/JQKyhlt4ke)
136 |
137 | 或者发邮件给:gejun123456@gmail.com
138 |
139 |
140 | [plugin]: https://plugins.jetbrains.com/plugin/25171
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/articles/introduction.md:
--------------------------------------------------------------------------------
1 | ## 免费开源的java热部署工具HotSwapAgent替代jrebel
2 |
3 | ### 现在推出的是HotSwapHelper,一个支持HotSwapAgent的Intellij插件,相比之前HotSwapAgent需要手动配置,HotSwapHelper只需要安装JDK即可
4 |
5 | ### 目前已支持众多框架比如若依,jeecg,国内常见的springboot+mybatis项目都是支持的
6 |
7 | 下载对应的jdk后,启动即可:
8 |
9 | ### 图示:
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.9.22'
3 | ext.kotlin_version = '1.5.0'
4 | repositories {
5 | mavenCentral()
6 | maven { url 'https://plugins.gradle.org/m2/' }
7 | maven { url 'https://oss.sonatype.org/content/repositories/releases/' }
8 | }
9 | // https://github.com/JetBrains/gradle-intellij-plugin/ 关注开发插件版本
10 | dependencies {
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | classpath "org.jetbrains.intellij.plugins:gradle-intellij-plugin:0.7.3"
13 | classpath "com.guardsquare:proguard-gradle:7.3.0"
14 | }
15 | }
16 |
17 | //2.1 插件配置
18 | // 这两个插件是必备
19 | plugins {
20 | id 'java'
21 | id 'org.jetbrains.intellij' version '0.7.3'
22 | }
23 |
24 | apply plugin: 'kotlin'
25 |
26 |
27 |
28 | group 'com.bruce.plugins'
29 | version '1.1.6'
30 |
31 | sourceCompatibility = JavaVersion.VERSION_1_8
32 | targetCompatibility = JavaVersion.VERSION_1_8
33 | compileJava.options.encoding = "UTF-8"
34 | compileTestJava.options.encoding = "UTF-8"
35 |
36 | repositories {
37 | mavenCentral()
38 | }
39 |
40 | // http://www.jetbrains.org/intellij/sdk/docs/tutorials/build_system/prerequisites.html
41 | // https://plugins.jetbrains.com/docs/intellij/gradle-guide.html 官网gradle构建教程
42 |
43 | intellij {
44 | // 插件名称
45 | pluginName 'HotSwapHelper'
46 | // 沙箱目录位置,用于保存IDEA的设置,默认在build文件下面,防止clean,放在根目录下。
47 | sandboxDirectory = "${rootProject.rootDir}/idea-sandbox"
48 | // 开发环境运行时使用的版本
49 | version '2021.2.2'
50 | // 社区版
51 | // type 'IC'
52 | // 企业版
53 | type 'IU'
54 | // 各种IDEA版本去这里找
55 | // https://www.jetbrains.com/intellij-repository/releases
56 | // 依赖的插件
57 | plugins = ['java','PsiViewer:212-SNAPSHOT','maven','gradle']
58 | //Disables updating since-build attribute in plugin.xml
59 | updateSinceUntilBuild false
60 | downloadSources = true
61 | }
62 |
63 | runIde {
64 | //check if jvm args file exist.
65 | if (file("jvmArgs.txt").exists()) {
66 | //read jvm args from file.
67 | jvmArgs = file("jvmArgs.txt").readLines()
68 | jvmArgs(jvmArgs)
69 | }
70 | }
71 |
72 | dependencies {
73 | // jackson工具
74 | compile 'com.fasterxml.jackson.core:jackson-databind:2.9.6'
75 | // guava工具
76 | // compile 'com.google.guava:guava:29.0-jre'
77 | // velocity支持
78 | // compile 'org.apache.velocity:velocity:1.7'
79 |
80 | // compile fileTree(dir: 'libs', includes: ['*.jar'])
81 |
82 | //add slf4j
83 | // compile 'org.slf4j:slf4j-api:1.7.30'
84 |
85 | // groovy支持
86 | // compile 'org.codehaus.groovy:groovy:3.0.2'
87 | // beetl支持
88 | // compile 'com.ibeetl:beetl:3.3.0.RELEASE'
89 | // freemarker支持
90 | // compile 'org.freemarker:freemarker:2.3.30'
91 | // lombok 支持
92 | compileOnly 'org.projectlombok:lombok:1.18.2'
93 | compileOnly group: 'com.alibaba', name: 'fastjson', version: '1.2.83'
94 |
95 | annotationProcessor 'org.projectlombok:lombok:1.18.2'
96 | testAnnotationProcessor 'org.projectlombok:lombok:1.18.2'
97 | // 测试用例
98 | testCompile 'junit:junit:4.12'
99 | //hutool
100 | implementation 'cn.hutool:hutool-all:5.8.21'
101 | //xml
102 | implementation 'org.jdom:jdom2:2.0.6'
103 | // Apache Commons Compress(支持 ZIP/TAR/GZIP/BZIP2 等)
104 | implementation 'org.apache.commons:commons-compress:1.26.1'
105 | // 可选:支持 XZ/LZMA 压缩(如 .tar.xz)
106 | implementation 'org.tukaani:xz:1.9'
107 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
108 | }
109 | compileKotlin {
110 | kotlinOptions {
111 | jvmTarget = "11"
112 | }
113 | }
114 | compileTestKotlin {
115 | kotlinOptions {
116 | jvmTarget = "11"
117 | }
118 | }
119 |
120 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/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.9-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/jdk.md:
--------------------------------------------------------------------------------
1 | #### Why need to download a jdk?
2 | The jdk provided by [HotSwapHelper Releases][link] has package
3 | dcevm with hotswap folder into it, so you don't need to install dcevm separately.
4 | If you don't like it, you can always install by yourself from doc:[HotSwapAgent GitHub page](https://github.com/HotswapProjects/HotswapAgent).
5 | Jdk Download url: [download][link]
6 |
7 | #### 为什么需要下载一个 jdk?
8 | [HotSwapHelper Releases][link]这里提供的 jdk 包含了dcevm 和hotswap 文件夹,统一在一个位置,
9 | dcevm用来支持更好的热加载功能,比如类加减字段和方法等。
10 | 无需自己再去安装dcevm或者根据jdk版本去查对应的jdk加入agent文件等,方便用户使用。
11 | 如果您不喜欢这种方式,您可以根据文档[HotSwapAgent GitHub page](https://github.com/HotswapProjects/HotswapAgent)自行安装
12 |
13 | 下载地址:[download][link]或者加入qq群下载[HotSwapHelper插件交流群](https://qm.qq.com/q/JQKyhlt4ke)
14 |
15 |
16 | [link]:https://github.com/gejun123456/HotSwapHelper/releases/tag/1.0
17 |
--------------------------------------------------------------------------------
/libs/velocity-1.7.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/libs/velocity-1.7.jar
--------------------------------------------------------------------------------
/screenShot/DebugWithHotSwap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/screenShot/DebugWithHotSwap.png
--------------------------------------------------------------------------------
/screenShot/HotSwapHelperChangeCodeWork.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/screenShot/HotSwapHelperChangeCodeWork.gif
--------------------------------------------------------------------------------
/screenShot/RunAndDebugIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/screenShot/RunAndDebugIcon.png
--------------------------------------------------------------------------------
/screenShot/SupportRuoyiVuePlus.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/screenShot/SupportRuoyiVuePlus.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'HotSwapHelper'
2 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/CheckResult.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper;
2 |
3 | /**
4 | * @author bruce ge 2024/8/19
5 | */
6 | public class CheckResult {
7 | private boolean hasFound;
8 |
9 | private String errorText;
10 |
11 | private int javaVersion;
12 |
13 | public boolean jbr = false;
14 |
15 |
16 | public boolean isJbr() {
17 | return jbr;
18 | }
19 |
20 | public void setJbr(boolean jbr) {
21 | this.jbr = jbr;
22 | }
23 |
24 | public int getJavaVersion() {
25 | return javaVersion;
26 | }
27 |
28 | public void setJavaVersion(int javaVersion) {
29 | this.javaVersion = javaVersion;
30 | }
31 |
32 | public boolean isHasFound() {
33 | return hasFound;
34 | }
35 |
36 | public void setHasFound(boolean hasFound) {
37 | this.hasFound = hasFound;
38 | }
39 |
40 | public String getErrorText() {
41 | return errorText;
42 | }
43 |
44 | public void setErrorText(String errorText) {
45 | this.errorText = errorText;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/HotSwapAction.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper;
2 |
3 | import com.intellij.execution.Executor;
4 | import com.intellij.execution.ExecutorRegistry;
5 | import com.intellij.execution.dashboard.actions.ExecutorAction;
6 | import com.intellij.icons.AllIcons;
7 | import com.intellij.openapi.actionSystem.AnActionEvent;
8 | import com.intellij.openapi.actionSystem.Presentation;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | /**
12 | * @author bruce ge 2024/8/19
13 | */
14 | public class HotSwapAction extends ExecutorAction {
15 | @Override
16 | protected Executor getExecutor() {
17 | return ExecutorRegistry.getInstance().getExecutorById("HotSwap Executor");
18 | }
19 |
20 | @Override
21 | protected void update(@NotNull AnActionEvent e, boolean running) {
22 | Presentation presentation = e.getPresentation();
23 | if (running) {
24 | presentation.setText("ReRun with Hotswap");
25 | presentation.setDescription("ReRun with hotswap");
26 | presentation.setIcon(IconUtils.hotswapIcon);
27 | }
28 | else {
29 | presentation.setText("Run with hotswap agent");
30 | presentation.setDescription("Run with hotswap agent");
31 | presentation.setIcon(IconUtils.hotswapIcon);
32 | }
33 | }
34 |
35 | @Override
36 | public void actionPerformed(@NotNull AnActionEvent e) {
37 | //check if the jdk is from decvm or jbr. // when first run the action, ask user to set with the jdk?
38 | //get the jre path.
39 | super.actionPerformed(e);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/HotSwapDebugAction.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper;
2 |
3 | import com.intellij.execution.Executor;
4 | import com.intellij.execution.ExecutorRegistry;
5 | import com.intellij.execution.dashboard.actions.ExecutorAction;
6 | import com.intellij.icons.AllIcons;
7 | import com.intellij.openapi.actionSystem.AnActionEvent;
8 | import com.intellij.openapi.actionSystem.Presentation;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | /**
12 | * @author bruce ge 2024/8/20
13 | */
14 | public class HotSwapDebugAction extends ExecutorAction {
15 | @Override
16 | protected Executor getExecutor() {
17 | return ExecutorRegistry.getInstance().getExecutorById(HotSwapDebugExecutor.EXECUTOR_ID);
18 | }
19 |
20 | @Override
21 | protected void update(@NotNull AnActionEvent e, boolean running) {
22 | Presentation presentation = e.getPresentation();
23 | if (running) {
24 | presentation.setText("ReRun Debug with Hotswap");
25 | presentation.setDescription("ReRun Debug with hotswap");
26 | presentation.setIcon(IconUtils.hotSwapDebugIcon);
27 | }
28 | else {
29 | presentation.setText("Debug with Hotswap Agent");
30 | presentation.setDescription("Debug with hotswap agent");
31 | presentation.setIcon(IconUtils.hotSwapDebugIcon);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/HotSwapDebugExecutor.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper;
2 |
3 | import com.intellij.execution.Executor;
4 | import com.intellij.openapi.util.text.StringUtil;
5 | import com.intellij.openapi.util.text.TextWithMnemonic;
6 | import org.jetbrains.annotations.NonNls;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | import javax.swing.*;
10 |
11 | /**
12 | * @author bruce ge 2024/8/20
13 | */
14 | public class HotSwapDebugExecutor extends Executor {
15 | @NonNls
16 | public static final String EXECUTOR_ID = "HotSwap Debug Executor";
17 |
18 | public HotSwapDebugExecutor() {
19 | super();
20 | }
21 |
22 | @NotNull
23 | public String getStartActionText() {
24 | return "HotSwap Debug";
25 | }
26 |
27 | @NotNull
28 | public String getStartActionText(@NotNull String configurationName) {
29 | String configName = StringUtil.isEmpty(configurationName) ? "" : " '" + shortenNameIfNeeded(configurationName) + "'";
30 | return TextWithMnemonic.parse("Run%s with hotSwap debug").replaceFirst("%s", configName).toString();
31 | }
32 |
33 | @NotNull
34 | public String getToolWindowId() {
35 | return "Debug";
36 | }
37 |
38 | @NotNull
39 | public Icon getToolWindowIcon() {
40 | return IconUtils.hotSwapDebugIcon;
41 | }
42 |
43 | @NotNull
44 | public Icon getIcon() {
45 | return IconUtils.hotSwapDebugIcon;
46 | }
47 |
48 | public Icon getDisabledIcon() {
49 | return null;
50 | }
51 |
52 | public String getDescription() {
53 | return "Run With HotSwap Debug";
54 | }
55 |
56 | @NotNull
57 | public String getActionName() {
58 | return EXECUTOR_ID;
59 | }
60 |
61 | @NotNull
62 | public String getId() {
63 | return EXECUTOR_ID;
64 | }
65 |
66 | public String getContextActionId() {
67 | return "HotSwap Debug Context id";
68 | }
69 |
70 | public String getHelpId() {
71 | return null;
72 | }
73 |
74 | public boolean isSupportedOnTarget() {
75 | return EXECUTOR_ID.equalsIgnoreCase(this.getId());
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/HotSwapExecutor.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper;
2 |
3 | import com.intellij.execution.Executor;
4 | import com.intellij.openapi.util.text.StringUtil;
5 | import com.intellij.openapi.util.text.TextWithMnemonic;
6 | import org.jetbrains.annotations.NonNls;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | import javax.swing.*;
10 |
11 | public class HotSwapExecutor extends Executor {
12 | @NonNls
13 | public static final String EXECUTOR_ID = "HotSwap Executor";
14 |
15 | public HotSwapExecutor() {
16 | super();
17 | }
18 |
19 | @NotNull
20 | public String getStartActionText() {
21 | return "HotSwap";
22 | }
23 |
24 | @NotNull
25 | public String getStartActionText(@NotNull String configurationName) {
26 | String configName = StringUtil.isEmpty(configurationName) ? "" : " '" + shortenNameIfNeeded(configurationName) + "'";
27 | return TextWithMnemonic.parse("Run%s with hotSwap").replaceFirst("%s", configName).toString();
28 | }
29 |
30 | @NotNull
31 | public String getToolWindowId() {
32 | return "Run";
33 | }
34 |
35 | @NotNull
36 | public Icon getToolWindowIcon() {
37 | return IconUtils.hotswapIcon;
38 | }
39 |
40 | @NotNull
41 | public Icon getIcon() {
42 | return IconUtils.hotswapIcon;
43 | }
44 |
45 | public Icon getDisabledIcon() {
46 | return null;
47 | }
48 |
49 | public String getDescription() {
50 | return "Run With HotSwap";
51 | }
52 |
53 | @NotNull
54 | public String getActionName() {
55 | return EXECUTOR_ID;
56 | }
57 |
58 | @NotNull
59 | public String getId() {
60 | return EXECUTOR_ID;
61 | }
62 |
63 | public String getContextActionId() {
64 | return "HotSwap Context id";
65 | }
66 |
67 | public String getHelpId() {
68 | return null;
69 | }
70 |
71 | public boolean isSupportedOnTarget() {
72 | return EXECUTOR_ID.equalsIgnoreCase(this.getId());
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/IconUtils.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper;
2 |
3 | import com.intellij.openapi.util.IconLoader;
4 |
5 | import javax.swing.*;
6 |
7 | /**
8 | * @author bruce ge 2024/8/19
9 | */
10 | public class IconUtils {
11 | public static final Icon hotswapIcon = IconLoader.getIcon("/icons/icon.png", IconUtils.class);
12 |
13 | public static final Icon hotSwapDebugIcon = IconLoader.getIcon("/icons/rotate.png", IconUtils.class);
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/JdkManager.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper;
2 |
3 | import com.intellij.util.lang.JavaVersion;
4 | import org.hotswap.hotswaphelper.utils.MyUtils;
5 |
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 | import java.io.IOException;
9 | import java.util.Properties;
10 |
11 | /**
12 | * @author bruce ge 2024/8/19
13 | */
14 | public class JdkManager {
15 | public static CheckResult checkJdkHome(String jdkhome,boolean dontCheckJdk){
16 | //get java version from jdk home.
17 | //get the release file.
18 | String downloadJdkInGithubRelease = " please download jdk in github release";
19 | String theVersion = "1.8";
20 | File release = new File(jdkhome, "release");
21 | CheckResult result = new CheckResult();
22 | if (release.exists()) {
23 | //read the file.
24 | //read the file
25 | Properties properties = new Properties();
26 | try (FileInputStream fis = new FileInputStream(release)) {
27 | properties.load(fis);
28 | String javaVersion = properties.getProperty("JAVA_VERSION");
29 | JavaVersion parse = JavaVersion.parse(javaVersion);
30 | int feature = parse.feature;
31 | String implementor = properties.getProperty("IMPLEMENTOR");
32 | if(implementor!=null&&implementor.toLowerCase().contains("jetbrain")){
33 | result.setJbr(true);
34 | }
35 | if(feature<8){
36 | //not supported.
37 | result.setHasFound(false);
38 | result.setErrorText("before jdk1.8 is not supported");
39 | return result;
40 | }
41 | if(dontCheckJdk){
42 | result.setHasFound(true);
43 | result.setJavaVersion(feature);
44 | return result;
45 | }
46 | if(feature==8){
47 | if(MyUtils.isWindows()) {
48 | //check if dcevm exist or not.
49 | File file = new File(jdkhome, "jre/bin/dcevm");
50 | if (file.exists()) {
51 | //dcevm exist.
52 | result.setHasFound(true);
53 | result.setErrorText("");
54 | result.setJavaVersion(8);
55 | return result;
56 | }
57 | else{
58 | result.setHasFound(false);
59 | result.setErrorText("dcevm not found in your jdk home:"+file.getAbsolutePath()+","+downloadJdkInGithubRelease);
60 | return result;
61 | }
62 | } else {
63 | File thepath1 = new File(jdkhome, "jre/lib/dcevm");
64 | File thePath2 = new File(jdkhome, "jre/lib/amd64/dcevm");
65 | if (thepath1.exists() || thePath2.exists()) {
66 | //dcevm exist.
67 | result.setHasFound(true);
68 | result.setErrorText("");
69 | result.setJavaVersion(8);
70 | return result;
71 | } else{
72 | result.setHasFound(false);
73 | result.setErrorText("dcevm not found in your path:"+thepath1.getAbsolutePath()
74 | +" or path:"+thePath2.getAbsolutePath()+","+downloadJdkInGithubRelease);
75 | return result;
76 | }
77 | }
78 |
79 | } else {
80 | File file = new File(jdkhome, "lib/hotswap/hotswap-agent.jar");
81 | if(feature == 11){
82 | if(file.exists()){
83 | result.setHasFound(true);
84 | result.setJavaVersion(11);
85 | return result;
86 | } else {
87 | if(result.isJbr()){
88 | result.setHasFound(true);
89 | result.setJavaVersion(11);
90 | return result;
91 | }
92 | result.setHasFound(false);
93 | result.setErrorText("hotSwap file not exist in your jdk home," +
94 | "the path is" + file.getAbsolutePath() + downloadJdkInGithubRelease);
95 | return result;
96 | }
97 | } else if(feature>=17){
98 | //todo maybe just check if current is jbr?
99 | if(file.exists()){
100 | result.setHasFound(true);
101 | result.setJavaVersion(feature);
102 | return result;
103 | } else {
104 | if(result.isJbr()){
105 | result.setHasFound(true);
106 | result.setJavaVersion(feature);
107 | return result;
108 | }
109 | result.setHasFound(false);
110 | result.setErrorText("hotSwap file not exist in your jdk home,"+file.getAbsolutePath()+"please download jdk in github release");
111 | return result;
112 | }
113 | } else {
114 | result.setHasFound(false);
115 | result.setErrorText("jdk version is not supported, please download jdk");
116 | return result;
117 | }
118 | }
119 | } catch (IOException e) {
120 | throw new RuntimeException(e);
121 | }
122 | } else {
123 | result.setHasFound(false);
124 | result.setErrorText("release file not found in path:"+release.getAbsolutePath()+" Please download jdk");
125 | return result;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/ReloadChangedClassAction.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper;
2 |
3 | import com.intellij.debugger.actions.HotSwapAction;
4 | import com.intellij.openapi.actionSystem.AnActionEvent;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | /**
8 | * @author bruce ge 2024/9/27
9 | */
10 | public class ReloadChangedClassAction extends HotSwapAction {
11 | public ReloadChangedClassAction(){
12 | super();
13 | }
14 |
15 | @Override
16 | public void update(@NotNull AnActionEvent e) {
17 | e.getPresentation().setIcon(IconUtils.hotSwapDebugIcon);
18 | super.update(e);
19 | if(e.getPresentation().isEnabled()){
20 | e.getPresentation().setVisible(true);
21 | } else {
22 | e.getPresentation().setVisible(false);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/StartUpListener.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper;
2 |
3 | import com.intellij.ide.AppLifecycleListener;
4 | import com.intellij.openapi.application.ApplicationManager;
5 | import org.hotswap.hotswaphelper.utils.MyUtils;
6 | import org.apache.commons.io.FileUtils;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | import java.io.File;
10 | import java.io.InputStream;
11 | import java.util.List;
12 |
13 | public class StartUpListener implements AppLifecycleListener {
14 | public StartUpListener() {
15 | super();
16 | }
17 |
18 | public void appFrameCreated(@NotNull List commandLineArgs) {
19 | ApplicationManager.getApplication().executeOnPooledThread(() -> {
20 | try {
21 | File folder = MyUtils.getHotSwapFolder();
22 | if (!folder.exists()) {
23 | folder.mkdirs();
24 | }
25 |
26 | File agentFile = MyUtils.getHotSwapJarPath();
27 | if (agentFile.exists()) {
28 | agentFile.delete();
29 | }
30 | InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("hotswap-agent.jar");
31 | //copy resource to the file
32 | FileUtils.copyInputStreamToFile(resourceAsStream, agentFile);
33 | resourceAsStream.close();
34 |
35 | File debuggerAgentFile = MyUtils.getDebuggerAgentFile();
36 | if(debuggerAgentFile.exists()){
37 | debuggerAgentFile.delete();
38 | }
39 |
40 | InputStream debuggerAgentResourceFile = this.getClass().getClassLoader().getResourceAsStream("debugger-agent.jar");
41 | //copy resource to the file
42 | FileUtils.copyInputStreamToFile(debuggerAgentResourceFile, debuggerAgentFile);
43 | debuggerAgentResourceFile.close();
44 | } catch (Exception var3) {
45 | throw new RuntimeException(var3);
46 | }
47 | });
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/actions/GenerateHotswapPropertiesAction.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.actions;
2 |
3 | import org.hotswap.hotswaphelper.utils.HotswapPropertiesGenerator;
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.roots.ProjectRootManager;
10 | import com.intellij.openapi.vfs.VirtualFile;
11 | import com.intellij.openapi.vfs.VirtualFileManager;
12 | import org.jetbrains.annotations.NotNull;
13 |
14 | public class GenerateHotswapPropertiesAction extends AnAction {
15 |
16 | @Override
17 | public void actionPerformed(@NotNull AnActionEvent e) {
18 | Project project = e.getProject();
19 | VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE);
20 | if (project == null || file == null) {
21 | return;
22 | }
23 |
24 | com.intellij.openapi.application.WriteAction.runAndWait(() -> {
25 | VirtualFile propertiesFile = HotswapPropertiesGenerator.generateHotswapProperties(file);
26 | if (propertiesFile != null) {
27 | // 刷新文件系统
28 | VirtualFileManager.getInstance().syncRefresh();
29 |
30 | // 打开并跳转到新生成的文件
31 | FileEditorManager.getInstance(project).openFile(propertiesFile, true);
32 | }
33 | });
34 | }
35 |
36 | @Override
37 | public void update(@NotNull AnActionEvent e) {
38 | Project project = e.getProject();
39 | VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE);
40 | if (project == null || file == null || !file.isDirectory()) {
41 | e.getPresentation().setEnabledAndVisible(false);
42 | return;
43 | }
44 |
45 | boolean isResourceFolder = ProjectRootManager.getInstance(project)
46 | .getFileIndex()
47 | .isInSourceContent(file) &&
48 | "resources".equals(file.getName());
49 |
50 | String path = file.getPath();
51 | if (isResourceFolder) {
52 | isResourceFolder = path.contains("/resources") &&
53 | (path.contains("/main/") || path.contains("/test/"));
54 | }
55 |
56 | VirtualFile existingFile = file.findChild("hotswap-agent.properties");
57 |
58 | e.getPresentation().setEnabledAndVisible(isResourceFolder && existingFile == null);
59 | }
60 | }
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/runner/HotSwapDebugRunner.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.runner;
2 |
3 | import com.intellij.debugger.impl.GenericDebuggerRunner;
4 | import com.intellij.debugger.settings.DebuggerSettings;
5 | import com.intellij.execution.ExecutionException;
6 | import com.intellij.execution.configurations.*;
7 | import com.intellij.execution.runners.ExecutionEnvironment;
8 | import com.intellij.execution.ui.RunContentDescriptor;
9 | import com.intellij.openapi.application.ApplicationInfo;
10 | import com.intellij.openapi.application.ApplicationManager;
11 | import com.intellij.openapi.project.Project;
12 | import com.intellij.util.text.VersionComparatorUtil;
13 | import org.hotswap.hotswaphelper.CheckResult;
14 | import org.hotswap.hotswaphelper.HotSwapDebugExecutor;
15 | import org.hotswap.hotswaphelper.JdkManager;
16 | import org.hotswap.hotswaphelper.settings.HotSwapHelperPluginSettingsProvider;
17 | import org.hotswap.hotswaphelper.utils.MyUtils;
18 | import org.jetbrains.annotations.NotNull;
19 | import org.jetbrains.annotations.Nullable;
20 |
21 | import java.io.File;
22 | import java.util.List;
23 |
24 | /**
25 | * @author bruce ge 2024/8/20
26 | */
27 | public class HotSwapDebugRunner extends GenericDebuggerRunner implements MyRunner{
28 |
29 |
30 | @Override
31 | public void patch(@NotNull JavaParameters javaParameters, @Nullable RunnerSettings settings, @NotNull RunProfile runProfile, boolean beforeExecution) throws ExecutionException {
32 | super.patch(javaParameters, settings, runProfile, beforeExecution);
33 | MyRunner.patchProfile(javaParameters, runProfile);
34 | }
35 |
36 | @Override
37 | public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
38 | return executorId.equals(HotSwapDebugExecutor.EXECUTOR_ID) &&
39 | profile instanceof ModuleRunProfile &&
40 | !(profile instanceof RunConfigurationWithSuppressedDefaultDebugAction);
41 | }
42 |
43 | @Override
44 | public void execute(@NotNull ExecutionEnvironment environment) throws ExecutionException {
45 | //check if reload classes is open.
46 | DebuggerSettings debuggerSettings = DebuggerSettings.getInstance();
47 | String runHotswapAfterCompile = debuggerSettings.RUN_HOTSWAP_AFTER_COMPILE;
48 | if(runHotswapAfterCompile.equals(DebuggerSettings.RUN_HOTSWAP_NEVER)){
49 | //Notify user to open the reload classes.
50 | ApplicationManager.getApplication().invokeLater(new Runnable() {
51 | @Override
52 | public void run() {
53 | //create Notification to notify user to open the reload classes.
54 | MyUtils.notifyUserToOpenReloadClasses();
55 | }
56 | });
57 | }
58 | if (MyRunner.checkJdk(environment)) return;
59 | super.execute(environment);
60 | }
61 |
62 | @Override
63 | protected @Nullable RunContentDescriptor attachVirtualMachine(RunProfileState state, @NotNull ExecutionEnvironment env, RemoteConnection connection, boolean pollConnection) throws ExecutionException {
64 | int versionName = ApplicationInfo.getInstance().getBuild().getBaselineVersion();
65 | //check if version > 2024.3
66 | if(versionName>=243) {
67 | if (state instanceof JavaCommandLine) {
68 | JavaParameters javaParameters = ((JavaCommandLine) state).getJavaParameters();
69 | String jdkPath = javaParameters.getJdkPath();
70 | Project project1 = env.getProject();
71 | boolean dontCheckJdk = HotSwapHelperPluginSettingsProvider.Companion.getInstance(project1).getCurrentState().getDontCheckJdk();
72 | CheckResult checkResult = JdkManager.checkJdkHome(jdkPath,dontCheckJdk);
73 | //make sure if < 17
74 | if(checkResult.getJavaVersion()<17) {
75 | ParametersList vmParametersList = javaParameters.getVMParametersList();
76 | List parameters = vmParametersList.getParameters();
77 | for (String parameter : parameters) {
78 | if (parameter.contains("-javaagent:") && parameter.contains("debugger-agent.jar")) {
79 | File agentFile = MyUtils.getDebuggerAgentFile();
80 | vmParametersList.replaceOrAppend(parameter, "-javaagent:" + agentFile.getAbsolutePath());
81 | break;
82 | }
83 | }
84 | }
85 | }
86 | }
87 | return super.attachVirtualMachine(state, env, connection, pollConnection);
88 | }
89 |
90 | @Override
91 | protected @Nullable RunContentDescriptor attachVirtualMachine(RunProfileState state, @NotNull ExecutionEnvironment env, RemoteConnection connection, long pollTimeout) throws ExecutionException {
92 | return super.attachVirtualMachine(state, env, connection, pollTimeout);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/runner/HotSwapRunner.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.runner;
2 |
3 | import com.intellij.execution.ExecutionException;
4 | import com.intellij.execution.configurations.*;
5 | import com.intellij.execution.impl.DefaultJavaProgramRunner;
6 | import com.intellij.execution.runners.ExecutionEnvironment;
7 | import com.intellij.execution.runners.RunConfigurationWithSuppressedDefaultRunAction;
8 | import org.hotswap.hotswaphelper.HotSwapExecutor;
9 | import org.jetbrains.annotations.NotNull;
10 | import org.jetbrains.annotations.Nullable;
11 |
12 | /**
13 | * @author bruce ge 2024/8/19
14 | */
15 | public class HotSwapRunner extends DefaultJavaProgramRunner {
16 |
17 | @Override
18 | public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
19 | return executorId.equals(HotSwapExecutor.EXECUTOR_ID) &&
20 | profile instanceof ModuleRunProfile &&
21 | !(profile instanceof RunConfigurationWithSuppressedDefaultRunAction);
22 |
23 | }
24 |
25 | @Override
26 | public void execute(@NotNull ExecutionEnvironment environment) throws ExecutionException {
27 | if (MyRunner.checkJdk(environment)) return;
28 | super.execute(environment);
29 | }
30 |
31 | @Override
32 | public void patch(@NotNull JavaParameters javaParameters, @Nullable RunnerSettings settings, @NotNull RunProfile runProfile, boolean beforeExecution) {
33 | super.patch(javaParameters, settings, runProfile, beforeExecution);
34 | MyRunner.patchProfile(javaParameters, runProfile);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/runner/HotswapPatcher.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.runner;
2 |
3 | import com.intellij.execution.Executor;
4 | import com.intellij.execution.configurations.JavaParameters;
5 | import com.intellij.execution.configurations.RunProfile;
6 | import com.intellij.execution.runners.JavaProgramPatcher;
7 | import org.hotswap.hotswaphelper.HotSwapDebugExecutor;
8 |
9 | /**
10 | * @author bruce ge 2024/8/20
11 | */
12 | public class HotswapPatcher extends JavaProgramPatcher {
13 | @Override
14 | public void patchJavaParameters(Executor executor, RunProfile configuration, JavaParameters javaParameters) {
15 | if(executor instanceof HotSwapDebugExecutor){
16 | MyRunner.patchProfile(javaParameters, configuration);;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/runner/MyRunner.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.runner;
2 |
3 | import com.google.common.base.Joiner;
4 | import com.google.common.collect.Lists;
5 | import com.intellij.execution.CantRunException;
6 | import com.intellij.execution.ExecutionException;
7 | import com.intellij.execution.JavaTestConfigurationBase;
8 | import com.intellij.execution.configurations.*;
9 | import com.intellij.execution.runners.ExecutionEnvironment;
10 | import com.intellij.openapi.application.ApplicationManager;
11 | import com.intellij.openapi.project.Project;
12 | import com.intellij.openapi.projectRoots.Sdk;
13 | import com.intellij.openapi.ui.Messages;
14 | import lombok.val;
15 | import org.apache.commons.lang3.StringUtils;
16 | import org.hotswap.hotswaphelper.CheckResult;
17 | import org.hotswap.hotswaphelper.JdkManager;
18 | import org.hotswap.hotswaphelper.settings.HotSwapHelperPluginSettingsProvider;
19 | import org.hotswap.hotswaphelper.ui.JdkNotSupportedDialog;
20 | import org.hotswap.hotswaphelper.utils.MyUtils;
21 | import org.apache.commons.io.FileUtils;
22 | import org.jetbrains.annotations.NotNull;
23 | import org.jetbrains.jps.model.java.JdkVersionDetector;
24 |
25 | import java.io.File;
26 | import java.io.InputStream;
27 | import java.util.List;
28 | import java.util.Set;
29 |
30 | /**
31 | * @author bruce ge 2024/8/20
32 | */
33 | public interface MyRunner {
34 |
35 |
36 | static void patchProfile(@NotNull JavaParameters javaParameters, @NotNull RunProfile runProfile) {
37 | if (runProfile instanceof RunConfiguration) {
38 | //get current jdk to check.
39 | Sdk jdk1 = javaParameters.getJdk();
40 | String homePath1 = jdk1.getHomePath();
41 | Project project1 = ((RunConfiguration) runProfile).getProject();
42 | boolean dontCheckJdk = HotSwapHelperPluginSettingsProvider.Companion.getInstance(project1).getCurrentState().getDontCheckJdk();
43 | //todo maybe parse it from versionString?
44 | CheckResult result = JdkManager.checkJdkHome(homePath1,dontCheckJdk);
45 | Sdk jdk = javaParameters.getJdk();
46 | //get user jdk version. if this is from dcevm jdk, add the agent to it.
47 | // String jdkPath = javaParameters.getJdkPath();
48 | String homePath = jdk.getHomePath();
49 | String versionString = jdk.getVersionString();
50 | //check if the jdk is from jetbrain.
51 | //check the version and replace with the decvm jdk.
52 | //todo jvm 参数未来可以自定义
53 | if (!(runProfile instanceof JavaTestConfigurationBase)) {
54 | int javaVersion = result.getJavaVersion();
55 |
56 | Project project = ((RunConfiguration) runProfile).getProject();
57 | HotSwapHelperPluginSettingsProvider.State currentState = HotSwapHelperPluginSettingsProvider.Companion.getInstance(project).getCurrentState();
58 | boolean useExternalHotSwapAgentFile = currentState.getUseExternalHotSwapAgentFile();
59 | // if (!useExternalHotSwapAgentFile) {
60 | // if (javaVersion == 8) {
61 | // File agentFile = MyUtils.getHotSwapJarPath();
62 | // if (!agentFile.exists()) {
63 | // ApplicationManager.getApplication().invokeLater(new Runnable() {
64 | // @Override
65 | // public void run() {
66 | // Messages.showErrorDialog(((RunConfiguration) runProfile).getProject(),
67 | // "HotSwap agent jar not found," +
68 | // "please check your folder:" + agentFile.getAbsolutePath() + " is there exist " +
69 | // "hotswap-agent.jar" +
70 | // "https://github.com/gejun123456/HotSwapIntellij", "HotSwap Agent Jar Not Found");
71 | // }
72 | // });
73 | // return;
74 | // } else {
75 | // javaParameters.getVMParametersList().addParametersString("-XXaltjvm=dcevm");
76 | // javaParameters.getVMParametersList().addParametersString("-javaagent:\"" + agentFile.getAbsolutePath()+"\"");
77 | // }
78 | // } else if (javaVersion == 11) {
79 | // //check if jbr?
80 | // if (result.isJbr()) {
81 | // javaParameters.getVMParametersList().addParametersString("-XX:+AllowEnhancedClassRedefinition");
82 | // }
83 | // javaParameters.getVMParametersList().addParametersString("-XX:HotswapAgent=fatjar");
84 | // } else if (javaVersion >= 17) {
85 | // javaParameters.getVMParametersList().addParametersString("-XX:+AllowEnhancedClassRedefinition");
86 | // javaParameters.getVMParametersList().addParametersString("-XX:HotswapAgent=fatjar");
87 | // javaParameters.getVMParametersList().addParametersString("-XX:+ClassUnloading");
88 | // //add --add-opens
89 | // addOpens(javaParameters);
90 | // }
91 | // } else {
92 | String agentPath = MyUtils.getHotSwapJarPath().getAbsolutePath();
93 | // use external mode?
94 | if (useExternalHotSwapAgentFile) {
95 | agentPath = currentState.getAgentPath();
96 | }
97 | File agentFile = new File(agentPath);
98 | if (!agentFile.exists()) {
99 | ApplicationManager.getApplication().invokeLater(new Runnable() {
100 | @Override
101 | public void run() {
102 | Messages.showErrorDialog(((RunConfiguration) runProfile).getProject(),
103 | "HotSwap agent jar not found in path:" + agentFile.getAbsolutePath(), "Error");
104 | }
105 | });
106 | return;
107 | }
108 | javaParameters.getVMParametersList().addParametersString("-javaagent:\"" + agentFile.getAbsolutePath() + "\"");
109 | if (javaVersion == 8) {
110 | javaParameters.getVMParametersList().addParametersString("-XXaltjvm=dcevm");
111 | } else if (javaVersion >= 11) {
112 | if (result.isJbr() || javaVersion >= 17) {
113 | javaParameters.getVMParametersList().addParametersString("-XX:+AllowEnhancedClassRedefinition");
114 | }
115 | javaParameters.getVMParametersList().addParametersString("-XX:HotswapAgent=external");
116 | if (javaVersion >= 17) {
117 | //add --add-opens
118 | javaParameters.getVMParametersList().addParametersString("-XX:+ClassUnloading");
119 | addOpens(javaParameters);
120 | }
121 | }
122 | Set disabledPlugins = currentState.getDisabledPlugins();
123 | if (!disabledPlugins.isEmpty()) {
124 | String join = Joiner.on(",").join(disabledPlugins);
125 | if (StringUtils.isNotBlank(join)) {
126 | javaParameters.getVMParametersList().addParametersString("-Dhotswapagent.disablePlugin=" + join);
127 | }
128 | }
129 | }
130 | }
131 | }
132 |
133 |
134 | static void addOpens(@NotNull JavaParameters javaParameters) {
135 | for (String allOpen : MyUtils.allOpens) {
136 | javaParameters.getVMParametersList().addParametersString(allOpen);
137 | }
138 | }
139 |
140 | static boolean checkJdk(@NotNull ExecutionEnvironment environment) throws ExecutionException {
141 | RunProfileState currentState = environment.getState();
142 | if (currentState == null) {
143 | return true;
144 | }
145 | if (currentState instanceof JavaCommandLine) {
146 | JavaCommandLine theline = (JavaCommandLine) currentState;
147 | JavaParameters javaParameters = theline.getJavaParameters();
148 | Sdk jdk = javaParameters.getJdk();
149 | String homePath = jdk.getHomePath();
150 | if (homePath == null) {
151 | throw new CantRunException("please select jdk");
152 | }
153 |
154 | Project project1 = environment.getProject();
155 | boolean dontCheckJdk = HotSwapHelperPluginSettingsProvider.Companion.getInstance(project1).getCurrentState().getDontCheckJdk();
156 |
157 | //add setting to not check jdk.
158 | CheckResult result = JdkManager.checkJdkHome(homePath,dontCheckJdk);
159 | if (!result.isHasFound()) {
160 | ApplicationManager.getApplication().invokeLater(new Runnable() {
161 | @Override
162 | public void run() {
163 | JdkNotSupportedDialog dialog = new JdkNotSupportedDialog(environment.getProject(), true, result.getErrorText());
164 | dialog.show();
165 | }
166 | });
167 | return true;
168 | }
169 | Project project = environment.getProject();
170 | boolean useExternalHotSwapAgentFile = HotSwapHelperPluginSettingsProvider.Companion.getInstance(project).getCurrentState().getUseExternalHotSwapAgentFile();
171 | //copy the hotwap agent file to the folder if 11 or 17.
172 | // if (result.getJavaVersion() > 8 && !useExternalHotSwapAgentFile) {
173 | // try {
174 | // // make sure to use with the least version in plugin resource.
175 | // // user can config it later if needed.
176 | // //copy the hotswap agent to the place.
177 | // File agentFile = new File(homePath, "lib/hotswap/hotswap-agent.jar");
178 | // //copy the stream to the file.
179 | // if (agentFile.exists()) {
180 | // agentFile.delete();
181 | // }
182 | // InputStream resourceAsStream = JdkManager.class.getClassLoader().getResourceAsStream("hotswap-agent.jar");
183 | // //copy resource to the file
184 | // FileUtils.copyInputStreamToFile(resourceAsStream, agentFile);
185 | // resourceAsStream.close();
186 | // } catch (Exception e) {
187 | // //ignore this.
188 | // }
189 | // }
190 |
191 | //when use external, no need to delete the file.
192 | // if(useExternalHotSwapAgentFile){
193 | // //delete the file?
194 | // // make sure to use with the least version in plugin resource.
195 | // // user can config it later if needed.
196 | // //copy the hotswap agent to the place.
197 | // File agentFile = new File(homePath, "lib/hotswap/hotswap-agent.jar");
198 | // //copy the stream to the file.
199 | // if (agentFile.exists()) {
200 | // try {
201 | // agentFile.delete();
202 | // }catch (Exception e){
203 | // throw new RuntimeException("Cant delete hotSwap when use External hotSwap file in path:"+
204 | // agentFile.getAbsolutePath()+","+"please delete by yourself");
205 | // }
206 | // }
207 | //
208 | // }
209 | }
210 | return false;
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/settings/HotSwapHelperPluginSettingsConfigurable.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017 Dmitry Zhuravlev, Sergei Stepanov
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.hotswap.hotswaphelper.settings
17 |
18 | import com.google.common.base.Joiner
19 | import com.intellij.execution.RunManager
20 | import com.intellij.ide.BrowserUtil
21 | import com.intellij.openapi.fileChooser.FileChooserDescriptor
22 | import com.intellij.openapi.options.Configurable
23 | import com.intellij.openapi.project.Project
24 | import com.intellij.openapi.roots.ProjectRootManager
25 | import com.intellij.openapi.ui.Messages
26 | import com.intellij.ui.DocumentAdapter
27 | import org.apache.commons.lang.StringUtils
28 | import org.hotswap.hotswaphelper.JdkManager
29 | import org.hotswap.hotswaphelper.ui.CopyTextDialog
30 | import org.hotswap.hotswaphelper.ui.HotSwapAgentPluginSettingsForm
31 | import org.hotswap.hotswaphelper.utils.MyUtils
32 | import org.hotswap.hotswaphelper.utils.MyUtils.allOpens
33 | import java.awt.CardLayout
34 | import java.util.*
35 | import javax.swing.JComponent
36 | import javax.swing.event.DocumentEvent
37 |
38 | /**
39 | * @author Dmitry Zhuravlev
40 | * Date: 09.03.2017
41 | */
42 | class HotSwapHelperPluginSettingsConfigurable(var project: Project) : Configurable {
43 | companion object {
44 | val bundle = ResourceBundle.getBundle("HotSwapHelperIntellijPluginBundle")!!
45 | private const val DCEVM_NOT_DETERMINED = ""
46 | }
47 |
48 | private var stateChanged: Boolean = false
49 | private val form = HotSwapAgentPluginSettingsForm()
50 | private val projectRootManager = ProjectRootManager.getInstance(project)
51 | private val stateProvider = HotSwapHelperPluginSettingsProvider.getInstance(project)
52 | private val runManager = RunManager.getInstance(project)
53 |
54 | override fun isModified() = stateChanged
55 |
56 | override fun getDisplayName() = bundle.getString("settings.hotswap.plugin.name")
57 |
58 |
59 | override fun apply() {
60 |
61 | val agentPath = form.agentInstallPathField.text
62 | val jdkDirectory = form.jdkDirectoryField.text
63 | val useExternalAgentFile = form.useExternalAgentFileCheckBox.isSelected
64 | val currentDontCheckJdk = form.dontCheckJdkCheckBox.isSelected
65 | if (useExternalAgentFile && StringUtils.isBlank(agentPath)) {
66 | Messages.showErrorDialog("when use external, agent must not null empty", "agent file empty");
67 | return;
68 | }
69 | stateProvider.currentState.agentPath = agentPath
70 | stateProvider.currentState.jdkDirectory = jdkDirectory
71 | stateProvider.currentState.useExternalHotSwapAgentFile = useExternalAgentFile;
72 | stateProvider.currentState.dontCheckJdk = currentDontCheckJdk
73 | // stateProvider.currentState.enableAgentForAllConfiguration = form.applyAgentToAllConfigurationsBox.isSelected
74 | // stateProvider.currentState.selectedRunConfigurations = form.configurationTableProvider.getSelectedConfigurationNames()
75 | stateProvider.currentState.disabledPlugins = form.disabledPluginsField.text.parse()
76 | // showUpdateButton()
77 | stateChanged = false
78 | }
79 |
80 |
81 | override fun createComponent(): JComponent? {
82 | setupFormComponents()
83 | //support it in later release version.
84 | form.disablePluginPanel.isVisible = true;
85 | return form.rootPanel
86 | }
87 |
88 | override fun reset() {
89 | form.agentInstallPathField.text = stateProvider.currentState.agentPath
90 | form.jdkDirectoryField.text = stateProvider.currentState.jdkDirectory
91 | // form.applyAgentToAllConfigurationsBox.isSelected = stateProvider.currentState.enableAgentForAllConfiguration
92 | form.disabledPluginsField.text = stateProvider.currentState.disabledPlugins.joinString()
93 | form.useExternalAgentFileCheckBox.isSelected = stateProvider.currentState.useExternalHotSwapAgentFile
94 | form.dontCheckJdkCheckBox.isSelected = stateProvider.currentState.dontCheckJdk
95 | stateChanged = false
96 | }
97 |
98 | override fun getHelpTopic() = null
99 |
100 | private fun setupFormComponents() {
101 | // projectRootManager.projectSdk?.let { sdk ->
102 | // form.dcevmVersionLabel.text = DCEVMUtil.determineDCEVMVersion(sdk) ?: DCEVM_NOT_DETERMINED
103 | // }
104 | form.agentInstallPathField.addBrowseFolderListener(
105 | null,
106 | null,
107 | null,
108 | FileChooserDescriptor(false, false, true, true, false, false)
109 | )
110 | form.agentInstallPathField.textField.document.addDocumentListener(object : DocumentAdapter() {
111 | override fun textChanged(e: DocumentEvent) {
112 | stateChanged = form.agentInstallPathField.textField.text != stateProvider.currentState.agentPath
113 | }
114 | })
115 | form.jdkDirectoryField.addBrowseFolderListener(
116 | null,
117 | null,
118 | null,
119 | FileChooserDescriptor(false, true, true, false, false, false)
120 | )
121 | form.jdkDirectoryField.textField.document.addDocumentListener(object : DocumentAdapter() {
122 | override fun textChanged(e: DocumentEvent) {
123 | stateChanged = form.jdkDirectoryField.textField.text != stateProvider.currentState.jdkDirectory
124 | }
125 | })
126 | form.disabledPluginsField.document.addDocumentListener(object : DocumentAdapter() {
127 | override fun textChanged(e: DocumentEvent) {
128 | stateChanged = form.disabledPluginsField.text != stateProvider.currentState.disabledPlugins.joinString()
129 | }
130 | })
131 | form.useExternalAgentFileCheckBox.addItemListener {
132 | stateChanged =
133 | form.useExternalAgentFileCheckBox.isSelected != stateProvider.currentState.useExternalHotSwapAgentFile;
134 | }
135 |
136 | form.dontCheckJdkCheckBox.addItemListener {
137 | stateChanged = form.dontCheckJdkCheckBox.isSelected != stateProvider.currentState.dontCheckJdk
138 | }
139 | form.updateButton.addActionListener {
140 | BrowserUtil.browse("https://github.com/HotswapProjects/HotswapAgent/releases")
141 | }
142 |
143 | form.exampleButton.addActionListener({
144 | val allPluginName =
145 | "Hotswapper, JdkPlugin, AnonymousClassPatch, ClassInitPlugin, WatchResources, Hibernate, HibernateJakarta, Hibernate3JPA, Hibernate3, Spring, SpringBoot, Jersey1, Jersey2, Jetty, Tomcat, ZK, Logback, Log4j2, MyFaces, Mojarra, Omnifaces, ELResolver, WildFlyELResolver, OsgiEquinox, Owb, OwbJakarta, Proxy, WebObjects, Weld, WeldJakarta, JBossModules, ResteasyRegistry, Deltaspike, DeltaspikeJakarta, GlassFish, Weblogic, Vaadin, Wicket, CxfJAXRS, FreeMarker, Undertow, MyBatis, MyBatisPlus, IBatis, JacksonPlugin, Idea, Thymeleaf, Velocity, Sponge"
146 | var split = allPluginName.split(",");
147 | //trim all names.
148 | split = split.map { it.trim() }
149 | var join = Joiner.on(",").join(split)
150 | val text = "all plugin names:\n" + join + "\n\n";
151 | val disableSpringText = "disable spring plugins:\n" + "Spring,Springboot";
152 | CopyTextDialog(project, text + disableSpringText,"Plugin name to disable").show()
153 | })
154 |
155 | //set the linkListener
156 |
157 | form.whatWillHappen.setListener({ label, linkData ->
158 | // Your implementation here
159 | BrowserUtil.browse("https://github.com/HotswapProjects/HotswapAgent")
160 | }, null)
161 |
162 | form.showVmParametersForButton.addActionListener({
163 | val builder = StringBuilder()
164 | builder.append(buildVmCommandFor(8));
165 | builder.append(buildVmCommandFor(11));
166 | builder.append(buildVmCommandFor(17));
167 | CopyTextDialog(project, builder.toString(),"Vm Parameters").show()
168 | })
169 | // form.dcevmDownloadSuggestionLabel.apply {
170 | // setHtmlText("""
171 | // DCEVM installation not found for JDK specified for the current project.
172 | // You should download and""")
173 | // foreground = Color.red
174 | // setHyperlinkTarget(DCEVM_RELEASES_URL)
175 | // isVisible = form.dcevmVersionLabel.text == DCEVM_NOT_DETERMINED
176 | // }
177 | // form.dcevmHowToInstallLabel.apply {
178 | // setHtmlText("""install it.""")
179 | // foreground = Color.red
180 | // setHyperlinkTarget(DCEVM_HOW_TO_INSTALL_URL)
181 | // isVisible = form.dcevmVersionLabel.text == DCEVM_NOT_DETERMINED
182 | // }
183 | // form.configurationTableProvider.apply {
184 | // addModelChangeListener {
185 | // stateChanged = stateProvider.currentState.selectedRunConfigurations != form.configurationTableProvider.getSelectedConfigurationNames()
186 | // }
187 | // setItems(runManager.allConfigurationsList.toTableItems())
188 | // setSelected(stateProvider.currentState.selectedRunConfigurations)
189 | // }
190 | }
191 |
192 | private fun showUpdateButton() {
193 | (form.updateButtonPanel.layout as CardLayout).show(form.updateButtonPanel, "cardWithUpdateButton")
194 | // val currentVersion = HotSwapAgentPathUtil.determineAgentVersionFromPath(stateProvider.currentState.agentPath)
195 | // val show = currentVersion != null && File(stateProvider.currentState.agentPath).exists() && downloadManager.isLatestAgentVersionAvailable(currentVersion)
196 | // if (show) {
197 | // (form.updateButtonPanel.layout as CardLayout).show(form.updateButtonPanel, "cardWithUpdateButton")
198 | // } else {
199 | // (form.updateButtonPanel.layout as CardLayout).show(form.updateButtonPanel, "emptyCard")
200 | // }
201 | }
202 |
203 | fun buildVmCommandFor(jdkVersion: Int): String {
204 | val builder = StringBuilder()
205 | builder.append("java$jdkVersion:\n")
206 | val isUseExternal = form.useExternalAgentFileCheckBox.isSelected
207 | val agentPath = if (isUseExternal) {
208 | form.agentInstallPathField.text
209 | } else {
210 | MyUtils.getHotSwapJarPath().absolutePath
211 | }
212 | //get current project jdk
213 | val sdk = ProjectRootManager.getInstance(project).projectSdk
214 | var isJbr = false;
215 | if (sdk == null) {
216 | return "no project jdk found for current project"
217 | }
218 | val homePath = sdk.homePath
219 | if (homePath == null) {
220 | return "no project jdk found for current project"
221 | }
222 | val result = JdkManager.checkJdkHome(homePath, true)
223 | if (result.isJbr) {
224 | isJbr = true;
225 | }
226 | builder.append("-javaagent:\"" + agentPath + "\"")
227 | builder.append("\n")
228 | if (jdkVersion == 8) {
229 | builder.append("-XXaltjvm=dcevm\n")
230 | } else {
231 | builder.append("-XX:HotswapAgent=external\n")
232 | if (jdkVersion == 11 && isJbr) {
233 | builder.append("-XX:+AllowEnhancedClassRedefinition\n")
234 | }
235 | if (jdkVersion >= 17) {
236 | builder.append("-XX:+AllowEnhancedClassRedefinition\n")
237 | builder.append("-XX:+ClassUnloading\n");
238 | for (item in allOpens) {
239 | builder.append(item + "\n");
240 | }
241 | }
242 | }
243 | val texts = form.disabledPluginsField.text
244 | val disablePlugins = texts.parse()
245 | if (texts.isNotBlank() && disablePlugins.isNotEmpty()) {
246 | val join = Joiner.on(",").join(disablePlugins)
247 | builder.append("-Dhotswapagent.disablePlugin=" + join + "\n")
248 | }
249 |
250 | builder.append("\n\n");
251 | return builder.toString()
252 | }
253 |
254 | private fun String.parse() = this.split(",").map(String::trim).toMutableSet()
255 |
256 | private fun Set.joinString() = Joiner.on(",").join(this);
257 | }
258 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/settings/HotSwapHelperPluginSettingsProvider.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017 Dmitry Zhuravlev, Sergei Stepanov
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.hotswap.hotswaphelper.settings
17 |
18 | import com.intellij.openapi.components.PersistentStateComponent
19 | import com.intellij.openapi.components.ServiceManager
20 | import com.intellij.openapi.components.State
21 | import com.intellij.openapi.components.Storage
22 | import com.intellij.openapi.project.Project
23 |
24 | /**
25 | * @author Dmitry Zhuravlev
26 | * Date: 09.03.2017
27 | */
28 | @State(
29 | name = "HotSwapHelperPluginSettingsProvider",
30 | storages = [(Storage("HotSwapHelper.xml"))]
31 | )
32 | class HotSwapHelperPluginSettingsProvider : PersistentStateComponent {
33 | companion object{
34 | fun getInstance(project: Project): HotSwapHelperPluginSettingsProvider {
35 | return ServiceManager.getService(project, HotSwapHelperPluginSettingsProvider::class.java)
36 | }
37 | }
38 | class State {
39 | var useExternalHotSwapAgentFile = false;
40 | var agentPath = ""
41 | var jdkDirectory = ""
42 | // var enableAgentForAllConfiguration = false
43 | // var selectedRunConfigurations = mutableSetOf()
44 | var disabledPlugins = mutableSetOf()
45 |
46 | var dontCheckJdk = false;
47 | }
48 |
49 | var currentState = State()
50 |
51 | override fun getState() = currentState
52 |
53 | override fun loadState(state: State) {
54 | currentState.agentPath = state.agentPath
55 | currentState.jdkDirectory = state.jdkDirectory
56 | // currentState.enableAgentForAllConfiguration = state.enableAgentForAllConfiguration
57 | // currentState.selectedRunConfigurations = state.selectedRunConfigurations
58 | currentState.disabledPlugins = state.disabledPlugins
59 | currentState.useExternalHotSwapAgentFile = state.useExternalHotSwapAgentFile;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/ui/CopyTextDialog.form:
--------------------------------------------------------------------------------
1 |
2 |
29 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/ui/CopyTextDialog.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.ui;
2 |
3 | import com.intellij.openapi.ide.CopyPasteManager;
4 | import com.intellij.openapi.project.Project;
5 | import com.intellij.openapi.ui.DialogWrapper;
6 | import com.intellij.openapi.ui.popup.JBPopupFactory;
7 | import com.intellij.ui.components.JBScrollPane;
8 | import com.intellij.uiDesigner.core.GridConstraints;
9 | import com.intellij.uiDesigner.core.GridLayoutManager;
10 | import org.jetbrains.annotations.Nullable;
11 |
12 | import javax.swing.*;
13 | import java.awt.*;
14 | import java.awt.datatransfer.StringSelection;
15 |
16 | /**
17 | * pacakgeName com.ccnode.codegenerator.view.datasource
18 | *
19 | * @author bruce ge
20 | */
21 | public class CopyTextDialog extends DialogWrapper {
22 |
23 | private JPanel thePanel;
24 | private JTextArea theTextArea;
25 | private String text;
26 | @Nullable
27 | private Project project;
28 |
29 | public CopyTextDialog(@Nullable Project project, String text, String title) {
30 | super(project, true, IdeModalityType.MODELESS);
31 | this.project = project;
32 | $$$setupUI$$$();
33 | this.text = text;
34 | theTextArea.setText(text);
35 | this.setSize(800, 800);
36 | setOKButtonText("Ok");
37 | setTitle(title);
38 | init();
39 | }
40 |
41 | @Nullable
42 | @Override
43 | protected JComponent createCenterPanel() {
44 | return new JBScrollPane(thePanel);
45 | }
46 |
47 |
48 | @Override
49 | protected void doOKAction() {
50 | CopyPasteManager.getInstance().setContents(new StringSelection(text));
51 | JBPopupFactory.getInstance().createMessage("copy content success").showCenteredInCurrentWindow(project);
52 | super.doOKAction();
53 | }
54 |
55 | /**
56 | * Method generated by IntelliJ IDEA GUI Designer
57 | * >>> IMPORTANT!! <<<
58 | * DO NOT edit this method OR call it in your code!
59 | *
60 | * @noinspection ALL
61 | */
62 | private void $$$setupUI$$$() {
63 | thePanel = new JPanel();
64 | thePanel.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
65 | final JScrollPane scrollPane1 = new JScrollPane();
66 | thePanel.add(scrollPane1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
67 | theTextArea = new JTextArea();
68 | theTextArea.setRows(20);
69 | scrollPane1.setViewportView(theTextArea);
70 | }
71 |
72 | /**
73 | * @noinspection ALL
74 | */
75 | public JComponent $$$getRootComponent$$$() {
76 | return thePanel;
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/ui/HotSwapAgentPluginSettingsForm.form:
--------------------------------------------------------------------------------
1 |
2 |
164 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/ui/HotSwapAgentPluginSettingsForm.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017 Dmitry Zhuravlev, Sergei Stepanov
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.hotswap.hotswaphelper.ui;
17 |
18 | import com.intellij.openapi.ui.TextFieldWithBrowseButton;
19 | import com.intellij.ui.HyperlinkLabel;
20 | import com.intellij.ui.components.JBCheckBox;
21 | import com.intellij.ui.components.JBLabel;
22 | import com.intellij.ui.components.fields.ExpandableTextField;
23 | import com.intellij.ui.components.labels.LinkLabel;
24 | import com.intellij.ui.table.TableView;
25 | import com.intellij.uiDesigner.core.GridConstraints;
26 | import com.intellij.uiDesigner.core.GridLayoutManager;
27 | import com.intellij.uiDesigner.core.Spacer;
28 | import com.intellij.util.execution.ParametersListUtil;
29 |
30 | import javax.swing.*;
31 | import java.awt.*;
32 | import java.lang.reflect.Method;
33 | import java.util.ResourceBundle;
34 |
35 | /**
36 | * @author Dmitry Zhuravlev
37 | * Date: 09.03.2017
38 | */
39 | public class HotSwapAgentPluginSettingsForm {
40 | public JButton updateButton;
41 | public TextFieldWithBrowseButton agentInstallPathField;
42 | public JBCheckBox applyAgentToAllConfigurationsBox;
43 | public JPanel rootPanel;
44 | public JPanel updateButtonPanel;
45 | public JBLabel dcevmVersionLabel;
46 | public HyperlinkLabel dcevmDownloadSuggestionLabel;
47 | public HyperlinkLabel dcevmHowToInstallLabel;
48 |
49 | private TableView configurationsTableView;
50 | public JTextField disabledPluginsField;
51 | public JCheckBox useExternalAgentFileCheckBox;
52 | public JPanel disablePluginPanel;
53 | public JButton exampleButton;
54 | public JCheckBox dontCheckJdkCheckBox;
55 | public LinkLabel whatWillHappen;
56 | public JButton showVmParametersForButton;
57 | public TextFieldWithBrowseButton jdkDirectoryField;
58 |
59 | private void createUIComponents() {
60 | disabledPluginsField = new ExpandableTextField(ParametersListUtil.COLON_LINE_PARSER, ParametersListUtil.COLON_LINE_JOINER);
61 | }
62 |
63 | {
64 | // GUI initializer generated by IntelliJ IDEA GUI Designer
65 | // >>> IMPORTANT!! <<<
66 | // DO NOT EDIT OR ADD ANY CODE HERE!
67 | $$$setupUI$$$();
68 | }
69 |
70 | /**
71 | * Method generated by IntelliJ IDEA GUI Designer
72 | * >>> IMPORTANT!! <<<
73 | * DO NOT edit this method OR call it in your code!
74 | *
75 | * @noinspection ALL
76 | */
77 | private void $$$setupUI$$$() {
78 | createUIComponents();
79 | rootPanel = new JPanel();
80 | rootPanel.setLayout(new GridLayoutManager(3, 4, new Insets(0, 0, 0, 0), -1, -1));
81 | final JPanel panel1 = new JPanel();
82 | panel1.setLayout(new GridLayoutManager(5, 4, new Insets(0, 0, 0, 0), -1, -1));
83 | rootPanel.add(panel1, new GridConstraints(1, 0, 1, 4, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
84 | final JPanel panel2 = new JPanel();
85 | panel2.setLayout(new BorderLayout(0, 0));
86 | panel1.add(panel2, new GridConstraints(0, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
87 | final JBLabel jBLabel1 = new JBLabel();
88 | jBLabel1.setHorizontalAlignment(11);
89 | jBLabel1.setHorizontalTextPosition(11);
90 | jBLabel1.setText("HotSwapAgent Jar file");
91 | panel2.add(jBLabel1, BorderLayout.WEST);
92 | agentInstallPathField = new TextFieldWithBrowseButton();
93 | panel2.add(agentInstallPathField, BorderLayout.CENTER);
94 | final JPanel panel3 = new JPanel();
95 | panel3.setLayout(new BorderLayout(0, 0));
96 | panel1.add(panel3, new GridConstraints(4, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
97 | dcevmDownloadSuggestionLabel = new HyperlinkLabel();
98 | dcevmDownloadSuggestionLabel.setBackground(new Color(-12828863));
99 | dcevmDownloadSuggestionLabel.setVisible(false);
100 | panel3.add(dcevmDownloadSuggestionLabel, BorderLayout.WEST);
101 | disablePluginPanel = new JPanel();
102 | disablePluginPanel.setLayout(new BorderLayout(0, 0));
103 | panel1.add(disablePluginPanel, new GridConstraints(1, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
104 | final JBLabel jBLabel2 = new JBLabel();
105 | this.$$$loadLabelText$$$(jBLabel2, this.$$$getMessageFromBundle$$$("string", "disabled.plugins"));
106 | disablePluginPanel.add(jBLabel2, BorderLayout.WEST);
107 | disabledPluginsField.setToolTipText("Semicolon separated list of disabled plugins");
108 | disablePluginPanel.add(disabledPluginsField, BorderLayout.CENTER);
109 | updateButton = new JButton();
110 | updateButton.setText("download");
111 | updateButton.setVisible(true);
112 | panel1.add(updateButton, new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
113 | exampleButton = new JButton();
114 | this.$$$loadButtonText$$$(exampleButton, this.$$$getMessageFromBundle$$$("string", "example"));
115 | panel1.add(exampleButton, new GridConstraints(1, 3, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
116 | dontCheckJdkCheckBox = new JCheckBox();
117 | this.$$$loadButtonText$$$(dontCheckJdkCheckBox, this.$$$getMessageFromBundle$$$("string", "dont.check.jdk"));
118 | panel1.add(dontCheckJdkCheckBox, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
119 | whatWillHappen = new LinkLabel();
120 | whatWillHappen.setText("what will happen");
121 | panel1.add(whatWillHappen, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
122 | showVmParametersForButton = new JButton();
123 | showVmParametersForButton.setText("show vm parameters for tomcat or gradle ect");
124 | panel1.add(showVmParametersForButton, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
125 | final JPanel panel4 = new JPanel();
126 | panel4.setLayout(new BorderLayout(0, 0));
127 | rootPanel.add(panel4, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_VERTICAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
128 | useExternalAgentFileCheckBox = new JCheckBox();
129 | this.$$$loadButtonText$$$(useExternalAgentFileCheckBox, this.$$$getMessageFromBundle$$$("string", "useexternalagentfileforcurrentproject"));
130 | panel4.add(useExternalAgentFileCheckBox, BorderLayout.WEST);
131 | final Spacer spacer1 = new Spacer();
132 | rootPanel.add(spacer1, new GridConstraints(2, 0, 1, 4, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
133 | }
134 |
135 | private static Method $$$cachedGetBundleMethod$$$ = null;
136 |
137 | private String $$$getMessageFromBundle$$$(String path, String key) {
138 | ResourceBundle bundle;
139 | try {
140 | Class> thisClass = this.getClass();
141 | if ($$$cachedGetBundleMethod$$$ == null) {
142 | Class> dynamicBundleClass = thisClass.getClassLoader().loadClass("com.intellij.DynamicBundle");
143 | $$$cachedGetBundleMethod$$$ = dynamicBundleClass.getMethod("getBundle", String.class, Class.class);
144 | }
145 | bundle = (ResourceBundle) $$$cachedGetBundleMethod$$$.invoke(null, path, thisClass);
146 | } catch (Exception e) {
147 | bundle = ResourceBundle.getBundle(path);
148 | }
149 | return bundle.getString(key);
150 | }
151 |
152 | /**
153 | * @noinspection ALL
154 | */
155 | private void $$$loadLabelText$$$(JLabel component, String text) {
156 | StringBuffer result = new StringBuffer();
157 | boolean haveMnemonic = false;
158 | char mnemonic = '\0';
159 | int mnemonicIndex = -1;
160 | for (int i = 0; i < text.length(); i++) {
161 | if (text.charAt(i) == '&') {
162 | i++;
163 | if (i == text.length()) break;
164 | if (!haveMnemonic && text.charAt(i) != '&') {
165 | haveMnemonic = true;
166 | mnemonic = text.charAt(i);
167 | mnemonicIndex = result.length();
168 | }
169 | }
170 | result.append(text.charAt(i));
171 | }
172 | component.setText(result.toString());
173 | if (haveMnemonic) {
174 | component.setDisplayedMnemonic(mnemonic);
175 | component.setDisplayedMnemonicIndex(mnemonicIndex);
176 | }
177 | }
178 |
179 | /**
180 | * @noinspection ALL
181 | */
182 | private void $$$loadButtonText$$$(AbstractButton component, String text) {
183 | StringBuffer result = new StringBuffer();
184 | boolean haveMnemonic = false;
185 | char mnemonic = '\0';
186 | int mnemonicIndex = -1;
187 | for (int i = 0; i < text.length(); i++) {
188 | if (text.charAt(i) == '&') {
189 | i++;
190 | if (i == text.length()) break;
191 | if (!haveMnemonic && text.charAt(i) != '&') {
192 | haveMnemonic = true;
193 | mnemonic = text.charAt(i);
194 | mnemonicIndex = result.length();
195 | }
196 | }
197 | result.append(text.charAt(i));
198 | }
199 | component.setText(result.toString());
200 | if (haveMnemonic) {
201 | component.setMnemonic(mnemonic);
202 | component.setDisplayedMnemonicIndex(mnemonicIndex);
203 | }
204 | }
205 |
206 | /**
207 | * @noinspection ALL
208 | */
209 | public JComponent $$$getRootComponent$$$() {
210 | return rootPanel;
211 | }
212 |
213 | }
214 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/ui/JdkNotSupportedDialog.form:
--------------------------------------------------------------------------------
1 |
2 |
182 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/ui/JdkNotSupportedDialog.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.ui;
2 |
3 | import cn.hutool.core.collection.CollUtil;
4 | import cn.hutool.core.io.FileUtil;
5 | import cn.hutool.core.util.ObjUtil;
6 | import cn.hutool.core.util.StrUtil;
7 | import cn.hutool.http.HttpRequest;
8 | import cn.hutool.http.HttpUtil;
9 | import com.intellij.ide.BrowserUtil;
10 | import com.intellij.openapi.application.ApplicationManager;
11 | import com.intellij.openapi.fileChooser.FileChooserDescriptor;
12 | import com.intellij.openapi.progress.ProgressManager;
13 | import com.intellij.openapi.project.Project;
14 | import com.intellij.openapi.projectRoots.ProjectJdkTable;
15 | import com.intellij.openapi.projectRoots.Sdk;
16 | import com.intellij.openapi.roots.ProjectRootManager;
17 | import com.intellij.openapi.ui.DialogWrapper;
18 | import com.intellij.openapi.ui.Messages;
19 | import com.intellij.openapi.ui.TextBrowseFolderListener;
20 | import com.intellij.openapi.ui.TextFieldWithBrowseButton;
21 | import com.intellij.openapi.vfs.VirtualFile;
22 | import com.intellij.ui.DocumentAdapter;
23 | import com.intellij.ui.components.labels.LinkLabel;
24 | import com.intellij.ui.components.labels.LinkListener;
25 | import com.intellij.uiDesigner.core.GridConstraints;
26 | import com.intellij.uiDesigner.core.GridLayoutManager;
27 | import com.intellij.uiDesigner.core.Spacer;
28 | import com.intellij.util.ui.JBUI;
29 | import lombok.val;
30 | import org.hotswap.hotswaphelper.settings.HotSwapHelperPluginSettingsProvider;
31 | import org.hotswap.hotswaphelper.utils.*;
32 | import org.jetbrains.annotations.NotNull;
33 | import org.jetbrains.annotations.Nullable;
34 | import org.yaml.snakeyaml.Yaml;
35 |
36 | import javax.swing.*;
37 | import javax.swing.event.DocumentEvent;
38 | import java.awt.event.ActionEvent;
39 | import java.awt.event.ActionListener;
40 | import java.io.File;
41 | import java.lang.reflect.Method;
42 | import java.nio.file.Path;
43 | import java.nio.file.Paths;
44 | import java.util.Arrays;
45 | import java.util.List;
46 | import java.util.Map;
47 | import java.util.ResourceBundle;
48 | import java.util.concurrent.atomic.AtomicReference;
49 | import java.util.stream.Collectors;
50 |
51 | /**
52 | * @author bruce ge 2024/8/22
53 | */
54 | public class JdkNotSupportedDialog extends DialogWrapper {
55 | private static Method $$$cachedGetBundleMethod$$$ = null;
56 | public LinkLabel jbrLink;
57 | private TextFieldWithBrowseButton jdkDirectoryField = new TextFieldWithBrowseButton();
58 | private JPanel panel1;
59 | private JPanel thePanel;
60 | private LinkLabel qqGroupLink;
61 | private LinkLabel githubLink;
62 | private JLabel errorLabel;
63 | private LinkLabel dontCheckJdk;
64 | private LinkLabel documentationLink;
65 | private JButton downloadJdkButton;
66 | private JTextField currentJdkText;
67 | private JButton clearDownloadCacheButton;
68 |
69 | {
70 | // GUI initializer generated by IntelliJ IDEA GUI Designer
71 | // >>> IMPORTANT!! <<<
72 | // DO NOT EDIT OR ADD ANY CODE HERE!
73 | $$$setupUI$$$();
74 | }
75 |
76 | public JdkNotSupportedDialog(@Nullable Project project, boolean canBeParent, String errorText) {
77 | super(project, canBeParent);
78 | ApplicationManager.getApplication().runWriteAction(() -> {
79 | errorLabel.setText(errorText);
80 | ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project);
81 | val sdk = projectRootManager.getProjectSdk();
82 | String javaVersion = sdk.getVersionString();
83 | currentJdkText.setText("当前项目jdk: " + javaVersion);
84 | FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, false, false, false);
85 | jdkDirectoryField.getTextField().setText(HotSwapHelperPluginSettingsProvider.Companion.getInstance(project).getCurrentState().getJdkDirectory());
86 | jdkDirectoryField.addBrowseFolderListener(new TextBrowseFolderListener(descriptor) {
87 | @Override
88 | protected void onFileChosen(@NotNull VirtualFile chosenFile) {
89 | ApplicationManager.getApplication().runWriteAction(() -> {
90 | // 当用户选择文件夹后,更新 TextFieldWithBrowseButton 的文本
91 | String chosenFilePath = chosenFile.getPath();
92 | jdkDirectoryField.setText(chosenFilePath);
93 | System.out.println("Selected folder: " + chosenFilePath);
94 | // String jdkDirectory = HotSwapHelperPluginSettingsProvider.Companion.getInstance(project).getCurrentState().getJdkDirectory();
95 | HotSwapHelperPluginSettingsProvider.State state = HotSwapHelperPluginSettingsProvider.Companion.getInstance(project).getState();
96 | state.setJdkDirectory(chosenFilePath);
97 | // 可以在这里添加更多逻辑,例如更新 UI 或处理文件夹路径
98 | });
99 | }
100 | });
101 | jdkDirectoryField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
102 | @Override
103 | protected void textChanged(@NotNull DocumentEvent e) {
104 | ApplicationManager.getApplication().runWriteAction(() -> {
105 | HotSwapHelperPluginSettingsProvider.State state = HotSwapHelperPluginSettingsProvider.Companion.getInstance(project).getState();
106 | state.setJdkDirectory(jdkDirectoryField.getTextField().getText());
107 | });
108 | }
109 | });
110 |
111 | githubLink.setListener(new LinkListener() {
112 | @Override
113 | public void linkSelected(LinkLabel aSource, Object aLinkData) {
114 | BrowserUtil.browse("https://github.com/gejun123456/HotSwapHelper/releases/tag/1.0");
115 | }
116 | }, null);
117 | qqGroupLink.setListener(new LinkListener() {
118 | @Override
119 | public void linkSelected(LinkLabel aSource, Object aLinkData) {
120 | BrowserUtil.browse("https://qm.qq.com/q/pOUouYAUsU");
121 | }
122 | }, null);
123 |
124 | dontCheckJdk.setListener(new LinkListener() {
125 | @Override
126 | public void linkSelected(LinkLabel aSource, Object aLinkData) {
127 | //shows setting pages. show intellij settings page.
128 | Messages.showInfoMessage("You can disable jdk check in settings->hotswap helper", "Disable Jdk Check");
129 | }
130 | }, null);
131 |
132 | jbrLink.setListener(new LinkListener() {
133 | @Override
134 | public void linkSelected(LinkLabel aSource, Object aLinkData) {
135 | BrowserUtil.browse("https://github.com/JetBrains/JetBrainsRuntime");
136 | }
137 | }, null);
138 |
139 | documentationLink.setListener(new LinkListener() {
140 | @Override
141 | public void linkSelected(LinkLabel aSource, Object aLinkData) {
142 | BrowserUtil.browse("https://github.com/gejun123456/HotSwapHelper");
143 | }
144 | }, null);
145 | setTitle("Current Jdk Is Not Supported");
146 | JButton submit = new JButton("提交");
147 |
148 | downloadJdkButton.addActionListener(new ActionListener() {
149 | @Override
150 | public void actionPerformed(ActionEvent e) {
151 | ApplicationManager.getApplication().runWriteAction(() -> {
152 | System.out.println("点击了下载按钮");
153 | String jdkDirectory = HotSwapHelperPluginSettingsProvider.Companion.getInstance(project).getCurrentState().getJdkDirectory();
154 | if (ObjUtil.isEmpty(jdkDirectory)) {
155 | // Messages.showWarningDialog("请先设置jdk下载路径", "请先设置jdk下载路径");
156 | SwingUtilities.invokeLater(() -> Messages.showWarningDialog("请先设置jdk下载路径", "请先设置jdk下载路径"));
157 | return;
158 | }
159 | // Messages.showInfoMessage("正在下载和设置项目jdk 请稍等", "正在下载和设置项目jdk");
160 | SwingUtilities.invokeLater(() -> Messages.showInfoMessage("正在下载和设置项目jdk 请稍等", "正在下载和设置项目jdk"));
161 | setJdk(project, jdkDirectory);
162 | });
163 | }
164 | });
165 |
166 | clearDownloadCacheButton.addActionListener(new ActionListener() {
167 | @Override
168 | public void actionPerformed(ActionEvent e) {
169 |
170 | }
171 | });
172 | init();
173 | });
174 | }
175 |
176 | /**
177 | * 从完整的 Java 版本字符串中提取大版本号
178 | *
179 | * @param versionString 完整的版本字符串,例如 "1.8.0_231"
180 | * @return 大版本号,例如 "1.8" 或 "8"
181 | */
182 | private static String extractMajorVersion(String versionString) {
183 | if (versionString == null || versionString.isEmpty()) {
184 | return "Unknown";
185 | }
186 |
187 | // 去除版本字符串中的非数字和点号部分
188 | String cleanedVersion = versionString.replaceAll("[^\\d.]", "");
189 |
190 | // 按点号分割版本字符串
191 | String[] parts = cleanedVersion.split("\\.");
192 |
193 | if (parts.length == 0) {
194 | return "Unknown";
195 | }
196 |
197 | // 判断版本号格式
198 | if (parts[0].equals("1") && parts.length > 1) {
199 | // 旧版本格式:1.x
200 | return "1." + parts[1];
201 | } else {
202 | // 新版本格式:x
203 | return parts[0];
204 | }
205 | }
206 |
207 | private void setJdk(@Nullable Project project, String jdksPath) {
208 | AtomicReference majorJavaVersionAtomic = new AtomicReference<>();
209 | AtomicReference hotswapJdkNameAtomic = new AtomicReference<>();
210 | ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project);
211 | val sdk = projectRootManager.getProjectSdk();
212 | String javaVersion = sdk.getVersionString();
213 | // 获取当前操作系统架构
214 | String osName = System.getProperty("os.name").toLowerCase();
215 | String osArch = System.getProperty("os.arch").toLowerCase();
216 | String osKey;
217 | if (osName.contains("mac")) {
218 | osKey = "macos";
219 | } else if (osName.contains("win")) {
220 | osKey = "windows";
221 | } else if (osName.contains("linux")) {
222 | osKey = "linux";
223 | } else {
224 | SwingUtilities.invokeLater(() -> Messages.showWarningDialog(StrUtil.format("找不到本系统架构{}的可用jdk", osArch), "设置项目jdk失败"));
225 | return;
226 | }
227 | // 构建完整的操作系统键
228 | String fullOsKey = osKey + "-" + osArch;
229 | // 提取大版本号
230 | majorJavaVersionAtomic.set(extractMajorVersion(javaVersion));
231 | hotswapJdkNameAtomic.set("hotswap-jdk-" + majorJavaVersionAtomic.get());
232 | ProjectJdkTable sdkTable = ProjectJdkTable.getInstance();
233 | Sdk[] allJdks = sdkTable.getAllJdks();
234 | Sdk findJdk = null;
235 | // dcevmJdk
236 | List dcevmJdks = Arrays.stream(allJdks).filter(jdk -> {
237 | String jdkVersionString = jdk.getVersionString();
238 | String jdkHomePath = jdk.getHomePath();
239 | boolean jdkDcevm = FileUtils.findJdkDcevm(Paths.get(jdkHomePath));
240 | //说明已经有可用jdk 则需要设置为本项目jdk
241 | return jdkDcevm;
242 | }).collect(Collectors.toList());
243 | // this version
244 | try {
245 | findJdk = dcevmJdks.stream().filter(jdk -> {
246 | String jdkVersionString = jdk.getVersionString();
247 | String jdkHomePath = jdk.getHomePath();
248 | // boolean jdkDcevm = FileUtils.findJdkDcevm(Paths.get(jdkHomePath));
249 | // if (jdkDcevm) {
250 | // 提取大版本号
251 | majorJavaVersionAtomic.set(extractMajorVersion(javaVersion));
252 | if (jdkVersionString.equals(majorJavaVersionAtomic.get())) {
253 | //说明已经有可用jdk 则需要设置为本项目jdk
254 | return true;
255 | }
256 | throw new RuntimeException();
257 | // }
258 | // return false;
259 | }).findFirst().orElse(null);
260 | } catch (Exception ignored) {
261 | }
262 | SwingUtilities.invokeLater(() -> Messages.showInfoMessage(StrUtil.format("检测到你已经设置了{}个jdk {}个Dcevm的jdk", allJdks.length, dcevmJdks.size()), "chech Jdk"));
263 | if (!ObjUtil.isEmpty(findJdk)) {
264 | String jdkHome = findJdk.getHomePath();
265 | // 切换到该jdk
266 | changeJdk(project, majorJavaVersionAtomic.get(), hotswapJdkNameAtomic.get(), jdkHome);
267 | return;
268 | }
269 | String githubProxy = "https://github.proxy.class3.fun/";
270 | // 读取 JSON 文件
271 | // String jdkMappingPath = "/home/ntfs/Common/Project/IdeaProjects/hotswap/src/main/resources/jdk-mapping.yml";
272 | String body = null;
273 | try {
274 | HttpRequest httpRequest = HttpUtil.createGet(githubProxy + "https://raw.githubusercontent.com/lidiaoo/hotswap-jdk/refs/heads/main/jdk-mapping.yml");
275 | body = httpRequest.execute().body();
276 | } catch (Exception e) {
277 | System.out.println(e.getMessage());
278 | e.printStackTrace();
279 | Messages.showWarningDialog("github无法访问 无法继续", "github无法访问");
280 | return;
281 | }
282 | try {
283 | String jdkFile;
284 | String jdkZipPath;
285 | try {
286 | // 加载 YAML 文件
287 | Yaml yaml = new Yaml();
288 | // InputStream inputStream = new FileInputStream(jdkMappingPath);
289 | Map>>> mapping = yaml.load(body);
290 | // 获取文件路径
291 | Map>> jdkMap = mapping.get("jdk");
292 | Map> map = jdkMap.get(fullOsKey);
293 | List jdkFiles = map.get(majorJavaVersionAtomic.get());
294 | jdkFile = CollUtil.getFirst(jdkFiles);
295 | String jdkFileName = FileUtil.file(jdkFile).getName();
296 | jdkZipPath = Paths.get(jdksPath, jdkFileName).toString();
297 | System.out.println("JDK file for " + fullOsKey + " and JDK " + majorJavaVersionAtomic.get() + ": " + jdkFiles);
298 | } catch (Exception e) {
299 | System.out.println(StrUtil.format("找不到本系统架构{}的可用jdk", fullOsKey));
300 | // Messages.showWarningDialog(StrUtil.format("找不到本系统架构{}的可用jdk", fullOsKey), "设置项目jdk失败");
301 | SwingUtilities.invokeLater(() -> Messages.showWarningDialog(StrUtil.format("找不到本系统架构{}的可用jdk", fullOsKey), "设置项目jdk失败"));
302 | return;
303 | }
304 | // 定义下载完成后的回调
305 | String finalJdkZipPath = jdkZipPath;
306 | Runnable onDownloadComplete = () -> {
307 | // Messages.showInfoMessage("Download completed successfully!", "Download Success");
308 | SwingUtilities.invokeLater(() -> Messages.showInfoMessage("Download completed successfully!", "Download Success"));
309 | System.out.println("下载成功");
310 | ApplicationManager.getApplication().runWriteAction(() -> {
311 | // 在这里执行写操作
312 | // 解压文件
313 | String jdkPathStr;
314 | Path jdkDirName;
315 | Path jdkHomePath;
316 | String jdkHome;
317 | try {
318 | jdkPathStr = ArchiveUtils.findShortestBinPath(Paths.get(finalJdkZipPath));
319 | jdkDirName = Paths.get(jdkPathStr);
320 | if (jdkDirName.toString().endsWith("bin") || jdkDirName.toString().endsWith("bin" + File.separator)) {
321 | jdkDirName = jdkDirName.getParent();
322 | }
323 | jdkHomePath = Paths.get(jdksPath, jdkDirName.toString());
324 | jdkHome = jdkHomePath.toString();
325 | FileUtil.del(jdkHomePath);
326 | ArchiveUtils.extract(Paths.get(finalJdkZipPath), Paths.get(jdksPath));
327 | } catch (Exception e) {
328 | System.out.println(e.getMessage());
329 | e.printStackTrace();
330 | // Messages.showWarningDialog("设置项目jdk失败 文件损坏", "设置项目jdk失败");
331 | SwingUtilities.invokeLater(() -> Messages.showWarningDialog("设置项目jdk失败 文件损坏", "设置项目jdk失败"));
332 | return;
333 | }
334 | changeJdk(project, majorJavaVersionAtomic.get(), hotswapJdkNameAtomic.get(), jdkHome);
335 | });
336 | };
337 | // 定义下载失败后的回调
338 | Runnable onDownloadFailed = () -> {
339 | // Messages.showWarningDialog("Download failed!", "Download Failed");
340 | SwingUtilities.invokeLater(() -> Messages.showWarningDialog("Download failed!", "Download Failed"));
341 | System.out.println("下载失败");
342 | ApplicationManager.getApplication().runWriteAction(() -> {
343 | // 在这里执行写操作
344 | });
345 | };
346 | DownloadTask downloadTask = new DownloadTask(project, jdkFile, jdkZipPath, onDownloadComplete, onDownloadFailed, false);
347 | ProgressManager.getInstance().run(downloadTask);
348 | } catch (Exception e) {
349 | System.err.println(e.getMessage());
350 | }
351 | }
352 |
353 |
354 | private void changeJdk(@NotNull Project project, String majorJavaVersion, String hotswapJdkName, String jdkHome) {
355 | try {
356 | JdkConfigurationUtils.setupProjectJdk(project, majorJavaVersion, hotswapJdkName,jdkHome);
357 | } catch (Exception e) {
358 | System.out.println(e.getMessage());
359 | e.printStackTrace();
360 | // Messages.showWarningDialog("设置项目jdk成功 设置jdk执行权限失败 请手动赋予jdk目录执行权限", "设置项目jdk成功");
361 | SwingUtilities.invokeLater(() -> Messages.showWarningDialog("设置项目jdk失败", "设置项目jdk失败"));
362 | return;
363 | }
364 | currentJdkText.setText("当前项目jdk: " + majorJavaVersion);
365 | try {
366 | FilePermissionUtils.setPermissionsRecursive(Paths.get(jdkHome));
367 | // Messages.showInfoMessage("设置项目jdk成功 设置jdk执行权限成功", "设置项目jdk成功");
368 | SwingUtilities.invokeLater(() -> Messages.showInfoMessage("设置项目jdk成功 设置jdk执行权限成功", "设置项目jdk成功"));
369 | } catch (Exception e) {
370 | System.out.println(e.getMessage());
371 | e.printStackTrace();
372 | // Messages.showWarningDialog("设置项目jdk成功 设置jdk执行权限失败 请手动赋予jdk目录执行权限", "设置项目jdk成功");
373 | SwingUtilities.invokeLater(() -> Messages.showWarningDialog("设置项目jdk成功 设置jdk执行权限失败 请手动赋予jdk目录执行权限", "设置项目jdk成功"));
374 | }
375 | }
376 |
377 | /**
378 | * Method generated by IntelliJ IDEA GUI Designer
379 | * >>> IMPORTANT!! <<<
380 | * DO NOT edit this method OR call it in your code!
381 | *
382 | * @noinspection ALL
383 | */
384 | private void $$$setupUI$$$() {
385 | panel1 = new JPanel();
386 | panel1.setLayout(new GridLayoutManager(1, 1, JBUI.emptyInsets(), -1, -1));
387 | thePanel = new JPanel();
388 | thePanel.setLayout(new GridLayoutManager(9, 4, JBUI.emptyInsets(), -1, -1));
389 | panel1.add(thePanel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
390 | final JLabel label1 = new JLabel();
391 | this.$$$loadLabelText$$$(label1, this.$$$getMessageFromBundle$$$("string", "jdkNotSupported"));
392 | thePanel.add(label1, new GridConstraints(1, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
393 | final Spacer spacer1 = new Spacer();
394 | thePanel.add(spacer1, new GridConstraints(1, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
395 | final Spacer spacer2 = new Spacer();
396 | thePanel.add(spacer2, new GridConstraints(8, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
397 | final JLabel label2 = new JLabel();
398 | label2.setText("Error is:");
399 | thePanel.add(label2, new GridConstraints(3, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
400 | errorLabel = new JLabel();
401 | errorLabel.setText("Label");
402 | thePanel.add(errorLabel, new GridConstraints(3, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
403 | final JLabel label3 = new JLabel();
404 | label3.setText("Download jdk from:(testd jdk)");
405 | thePanel.add(label3, new GridConstraints(4, 0, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
406 | githubLink = new LinkLabel();
407 | githubLink.setText("github");
408 | thePanel.add(githubLink, new GridConstraints(4, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
409 | final JLabel label4 = new JLabel();
410 | this.$$$loadLabelText$$$(label4, this.$$$getMessageFromBundle$$$("HotSwapHelperIntellijPluginBundle", "无法下载可以加入qq群"));
411 | thePanel.add(label4, new GridConstraints(6, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
412 | qqGroupLink = new LinkLabel();
413 | qqGroupLink.setText("qq群");
414 | thePanel.add(qqGroupLink, new GridConstraints(6, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
415 | dontCheckJdk = new LinkLabel();
416 | dontCheckJdk.setText("don't check jdk");
417 | thePanel.add(dontCheckJdk, new GridConstraints(7, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
418 | final JLabel label5 = new JLabel();
419 | label5.setText("Documentation:");
420 | thePanel.add(label5, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
421 | documentationLink = new LinkLabel();
422 | this.$$$loadLabelText$$$(documentationLink, this.$$$getMessageFromBundle$$$("string", "documentation"));
423 | thePanel.add(documentationLink, new GridConstraints(0, 1, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
424 | final JLabel label6 = new JLabel();
425 | this.$$$loadLabelText$$$(label6, this.$$$getMessageFromBundle$$$("string", "afterdownloading"));
426 | thePanel.add(label6, new GridConstraints(2, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
427 | final JLabel label7 = new JLabel();
428 | label7.setText("After java11 can downloading jbr");
429 | thePanel.add(label7, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
430 | jbrLink = new LinkLabel();
431 | jbrLink.setText("downloadJbr");
432 | thePanel.add(jbrLink, new GridConstraints(5, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
433 | final JLabel label8 = new JLabel();
434 | label8.setText("download \tJBRSDK with JCEF or DCEVM");
435 | thePanel.add(label8, new GridConstraints(5, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
436 | }
437 |
438 | private String $$$getMessageFromBundle$$$(String path, String key) {
439 | ResourceBundle bundle;
440 | try {
441 | Class> thisClass = this.getClass();
442 | if ($$$cachedGetBundleMethod$$$ == null) {
443 | Class> dynamicBundleClass = thisClass.getClassLoader().loadClass("com.intellij.DynamicBundle");
444 | $$$cachedGetBundleMethod$$$ = dynamicBundleClass.getMethod("getBundle", String.class, Class.class);
445 | }
446 | bundle = (ResourceBundle) $$$cachedGetBundleMethod$$$.invoke(null, path, thisClass);
447 | } catch (Exception e) {
448 | bundle = ResourceBundle.getBundle(path);
449 | }
450 | return bundle.getString(key);
451 | }
452 |
453 | /**
454 | * @noinspection ALL
455 | */
456 | private void $$$loadLabelText$$$(JLabel component, String text) {
457 | StringBuffer result = new StringBuffer();
458 | boolean haveMnemonic = false;
459 | char mnemonic = '\0';
460 | int mnemonicIndex = -1;
461 | for (int i = 0; i < text.length(); i++) {
462 | if (text.charAt(i) == '&') {
463 | i++;
464 | if (i == text.length()) break;
465 | if (!haveMnemonic && text.charAt(i) != '&') {
466 | haveMnemonic = true;
467 | mnemonic = text.charAt(i);
468 | mnemonicIndex = result.length();
469 | }
470 | }
471 | result.append(text.charAt(i));
472 | }
473 | component.setText(result.toString());
474 | if (haveMnemonic) {
475 | component.setDisplayedMnemonic(mnemonic);
476 | component.setDisplayedMnemonicIndex(mnemonicIndex);
477 | }
478 | }
479 |
480 | /**
481 | * @noinspection ALL
482 | */
483 | public JComponent $$$getRootComponent$$$() {
484 | return panel1;
485 | }
486 |
487 | @Override
488 | protected @Nullable JComponent createCenterPanel() {
489 | return thePanel;
490 | }
491 | }
492 |
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/utils/ArchiveUtils.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.utils;
2 |
3 | import org.apache.commons.compress.archivers.ArchiveEntry;
4 | import org.apache.commons.compress.archivers.ArchiveException;
5 | import org.apache.commons.compress.archivers.ArchiveInputStream;
6 | import org.apache.commons.compress.archivers.ArchiveStreamFactory;
7 | import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
8 | import org.apache.commons.compress.archivers.sevenz.SevenZFile;
9 | import org.apache.commons.compress.compressors.CompressorException;
10 | import org.apache.commons.compress.compressors.CompressorStreamFactory;
11 | import org.apache.commons.compress.utils.FileNameUtils;
12 | import org.apache.commons.compress.utils.IOUtils;
13 |
14 | import java.io.*;
15 | import java.nio.file.Files;
16 | import java.nio.file.Path;
17 | import java.nio.file.Paths;
18 |
19 | public class ArchiveUtils {
20 |
21 | /**
22 | * 查找压缩包中最短的 bin 目录路径(不解压)
23 | *
24 | * @param archivePath 压缩包路径(支持 .zip, .tar, .tar.gz, .tar.bz2, .tar.xz 等)
25 | * @return 最短的 bin 目录路径(如 "jdk-17.0.1/bin"),找不到返回 null
26 | */
27 | public static String findShortestBinPath(Path archivePath) throws IOException, ArchiveException, CompressorException {
28 | if (!isSupportedArchive(archivePath)) {
29 | throw new ArchiveException("Archive is not supported");
30 | }
31 | String shortestBinPath = null;
32 | int shortestLength = Integer.MAX_VALUE;
33 | try {
34 | try (InputStream inputStream = Files.newInputStream(archivePath); InputStream bufferedIn = new BufferedInputStream(inputStream); InputStream compressedIn = tryDecompress(bufferedIn); ArchiveInputStream archiveIn = new ArchiveStreamFactory().createArchiveInputStream(compressedIn)) {
35 | ArchiveEntry entry;
36 | while ((entry = archiveIn.getNextEntry()) != null) {
37 | if (!archiveIn.canReadEntryData(entry)) {
38 | continue; // 跳过不可读的条目(如加密文件)
39 | }
40 | if (entry.isDirectory() && isBinDirectory(entry.getName())) {
41 | int pathLength = entry.getName().length();
42 | if (pathLength < shortestLength) {
43 | shortestLength = pathLength;
44 | shortestBinPath = entry.getName();
45 | }
46 | }
47 | }
48 | }
49 | } catch (Exception e) {
50 | e.printStackTrace();
51 | System.err.println("压缩包格式不支持或损坏: " + e.getMessage());
52 | }
53 | return shortestBinPath;
54 | }
55 |
56 | /**
57 | * 检查是否为 bin 目录
58 | */
59 | private static boolean isBinDirectory(String entryName) {
60 | Path path = Paths.get(entryName);
61 | return path.getFileName() != null && path.getFileName().toString().equals("bin");
62 | }
63 |
64 | /**
65 | * 尝试解压流(支持 .gz, .bz2, .xz 等压缩格式)
66 | */
67 | private static InputStream tryDecompress(InputStream inputStream) throws CompressorException, IOException {
68 | if (!inputStream.markSupported()) {
69 | inputStream = new BufferedInputStream(inputStream);
70 | }
71 | inputStream.mark(100); // 标记以便回退
72 | try {
73 | return new CompressorStreamFactory().createCompressorInputStream(inputStream);
74 | } catch (CompressorException e) {
75 | inputStream.reset(); // 不是压缩文件,返回原始流
76 | return inputStream;
77 | }
78 | }
79 |
80 | public static boolean isSupportedArchive(Path path) {
81 | String ext = FileNameUtils.getExtension(path.toString().toLowerCase());
82 | return ext.matches("zip|tar|gz|bz2|xz");
83 | }
84 |
85 | /**
86 | * 解压任意格式的压缩文件(自动检测格式)
87 | *
88 | * @param archiveFile 压缩文件路径(支持 .zip, .tar, .tar.gz, .tar.bz2, .7z 等)
89 | * @param outputDir 解压目标目录
90 | */
91 | public static void extract(Path archiveFile, Path outputDir) throws IOException {
92 | if (!Files.exists(archiveFile)) {
93 | throw new FileNotFoundException("压缩文件不存在: " + archiveFile);
94 | }
95 |
96 | // 创建目标目录
97 | Files.createDirectories(outputDir);
98 |
99 | // 根据文件扩展名选择解压方式
100 | String fileName = archiveFile.getFileName().toString().toLowerCase();
101 | if (fileName.endsWith(".7z")) {
102 | extract7z(archiveFile, outputDir);
103 | } else if (fileName.endsWith(".zip")) {
104 | extractArchive(archiveFile, outputDir, ArchiveStreamFactory.ZIP);
105 | } else if (fileName.endsWith(".tar")) {
106 | extractArchive(archiveFile, outputDir, ArchiveStreamFactory.TAR);
107 | } else if (fileName.endsWith(".tar.gz") || fileName.endsWith(".tgz")) {
108 | extractCompressedTar(archiveFile, outputDir, CompressorStreamFactory.GZIP);
109 | } else if (fileName.endsWith(".tar.bz2") || fileName.endsWith(".tbz2")) {
110 | extractCompressedTar(archiveFile, outputDir, CompressorStreamFactory.BZIP2);
111 | } else if (fileName.endsWith(".tar.xz")) {
112 | extractCompressedTar(archiveFile, outputDir, CompressorStreamFactory.XZ);
113 | } else {
114 | throw new IllegalArgumentException("不支持的压缩格式: " + fileName);
115 | }
116 | }
117 |
118 | // 解压 ZIP/TAR 文件
119 | private static void extractArchive(Path archiveFile, Path outputDir, String archiveType) throws IOException {
120 | try (InputStream is = Files.newInputStream(archiveFile);
121 | ArchiveInputStream ais = new ArchiveStreamFactory().createArchiveInputStream(archiveType, is)) {
122 | extractEntries(ais, outputDir);
123 | } catch (Exception e) {
124 | throw new IOException("解压失败: " + e.getMessage(), e);
125 | }
126 | }
127 |
128 | // 解压 TAR.GZ / TAR.BZ2 / TAR.XZ
129 | private static void extractCompressedTar(Path archiveFile, Path outputDir, String compressorType) throws IOException {
130 | try (InputStream is = Files.newInputStream(archiveFile);
131 | InputStream cis = new CompressorStreamFactory().createCompressorInputStream(compressorType, is);
132 | ArchiveInputStream ais = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.TAR, cis)) {
133 | extractEntries(ais, outputDir);
134 | } catch (Exception e) {
135 | throw new IOException("解压失败: " + e.getMessage(), e);
136 | }
137 | }
138 |
139 | // 解压 7Z 文件(特殊处理)
140 | private static void extract7z(Path archiveFile, Path outputDir) throws IOException {
141 | try (SevenZFile sevenZFile = new SevenZFile(archiveFile.toFile())) {
142 | SevenZArchiveEntry entry;
143 | while ((entry = sevenZFile.getNextEntry()) != null) {
144 | Path entryPath = outputDir.resolve(entry.getName());
145 | if (entry.isDirectory()) {
146 | Files.createDirectories(entryPath);
147 | } else {
148 | Files.createDirectories(entryPath.getParent());
149 | try (OutputStream os = Files.newOutputStream(entryPath)) {
150 | byte[] buffer = new byte[8192];
151 | int len;
152 | while ((len = sevenZFile.read(buffer)) > 0) {
153 | os.write(buffer, 0, len);
154 | }
155 | }
156 | }
157 | }
158 | }
159 | }
160 |
161 | // 通用条目解压逻辑
162 | private static void extractEntries(ArchiveInputStream ais, Path outputDir) throws IOException {
163 | ArchiveEntry entry;
164 | while ((entry = ais.getNextEntry()) != null) {
165 | Path entryPath = outputDir.resolve(entry.getName()).normalize();
166 | if (!entryPath.startsWith(outputDir)) {
167 | throw new IOException("非法路径: " + entry.getName()); // 防止路径穿越攻击
168 | }
169 |
170 | if (entry.isDirectory()) {
171 | Files.createDirectories(entryPath);
172 | } else {
173 | Files.createDirectories(entryPath.getParent());
174 | try (OutputStream os = Files.newOutputStream(entryPath)) {
175 | IOUtils.copy(ais, os);
176 | }
177 | }
178 | }
179 | }
180 |
181 | public static void main(String[] args) {
182 | try {
183 | Path archivePath = Paths.get("jdk-17.0.1.tar.gz");
184 | String binPath = findShortestBinPath(archivePath);
185 | System.out.println("Found bin directory: " + binPath);
186 | } catch (Exception e) {
187 | e.printStackTrace();
188 | }
189 | }
190 | }
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/utils/DownloadTask.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.utils;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import cn.hutool.core.io.IoUtil;
5 | import com.intellij.openapi.application.ApplicationManager;
6 | import com.intellij.openapi.progress.ProgressIndicator;
7 | import com.intellij.openapi.progress.Task;
8 | import com.intellij.openapi.project.Project;
9 | import org.apache.commons.io.FileUtils;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.net.HttpURLConnection;
16 | import java.net.URL;
17 | import java.nio.file.Files;
18 | import java.nio.file.Paths;
19 | import java.nio.file.StandardCopyOption;
20 |
21 | public class DownloadTask extends Task.Backgroundable {
22 | private final String downloadUrl;
23 | private final String destinationPath;
24 | private final Runnable onDownloadComplete;
25 | private final Runnable onDownloadFailed;
26 | private final boolean overwriteExisting;
27 |
28 | public DownloadTask(Project project, String downloadUrl, String destinationPath,
29 | Runnable onDownloadComplete, Runnable onDownloadFailed, boolean overwriteExisting) {
30 | super(project, "Downloading File", true);
31 | this.downloadUrl = downloadUrl;
32 | this.destinationPath = destinationPath;
33 | this.onDownloadComplete = onDownloadComplete;
34 | this.onDownloadFailed = onDownloadFailed;
35 | this.overwriteExisting = overwriteExisting;
36 | }
37 |
38 | @Override
39 | public void run(@NotNull ProgressIndicator progressIndicator) {
40 | HttpURLConnection connection = null;
41 | InputStream inputStream = null;
42 |
43 | try {
44 | // Check if we should skip download
45 | if (!overwriteExisting && FileUtil.exist(destinationPath) && !FileUtil.isEmpty(new File(destinationPath))) {
46 | if (onDownloadComplete != null) {
47 | ApplicationManager.getApplication().invokeLater(onDownloadComplete::run);
48 | }
49 | return;
50 | }
51 |
52 | // Create parent directories if they don't exist
53 | FileUtil.mkParentDirs(destinationPath);
54 | FileUtils.touch(new File(destinationPath));
55 |
56 | URL url = new URL(downloadUrl);
57 | connection = (HttpURLConnection) url.openConnection();
58 |
59 | // Set Chrome-like headers
60 | connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
61 | connection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
62 | connection.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
63 | connection.setRequestProperty("Connection", "keep-alive");
64 | connection.setRequestProperty("Upgrade-Insecure-Requests", "1");
65 |
66 | connection.connect();
67 |
68 | // Check HTTP response code
69 | int responseCode = connection.getResponseCode();
70 | if (responseCode != HttpURLConnection.HTTP_OK) {
71 | throw new RuntimeException("Server returned HTTP " + responseCode + ": " + connection.getResponseMessage());
72 | }
73 |
74 | // Get content length for progress tracking
75 | int contentLength = connection.getContentLength();
76 | progressIndicator.setIndeterminate(contentLength <= 0);
77 |
78 | inputStream = connection.getInputStream();
79 |
80 | // Download with progress tracking
81 | try (InputStream progressStream = new ProgressInputStream(inputStream, contentLength, progressIndicator)) {
82 | Files.copy(progressStream, Paths.get(destinationPath), StandardCopyOption.REPLACE_EXISTING);
83 | }
84 |
85 | // Completion callback
86 | if (onDownloadComplete != null) {
87 | ApplicationManager.getApplication().invokeLater(onDownloadComplete::run);
88 | }
89 | } catch (Exception e) {
90 | System.out.println(e);
91 | e.printStackTrace();
92 | // Clean up partially downloaded file
93 | if (FileUtil.exist(destinationPath)) {
94 | FileUtil.del(destinationPath);
95 | }
96 |
97 | // Failure callback
98 | if (onDownloadFailed != null) {
99 | ApplicationManager.getApplication().invokeLater(onDownloadFailed::run);
100 | }
101 | } finally {
102 | IoUtil.close(inputStream);
103 | if (connection != null) {
104 | connection.disconnect();
105 | }
106 | }
107 | }
108 |
109 | /**
110 | * InputStream wrapper that updates progress indicator
111 | */
112 | private static class ProgressInputStream extends InputStream {
113 | private final InputStream wrapped;
114 | private final int totalSize;
115 | private final ProgressIndicator progressIndicator;
116 | private int bytesRead;
117 |
118 | ProgressInputStream(InputStream wrapped, int totalSize, ProgressIndicator progressIndicator) {
119 | this.wrapped = wrapped;
120 | this.totalSize = totalSize;
121 | this.progressIndicator = progressIndicator;
122 | this.bytesRead = 0;
123 | }
124 |
125 | @Override
126 | public int read() throws IOException {
127 | int data = wrapped.read();
128 | if (data != -1) {
129 | bytesRead++;
130 | updateProgress();
131 | }
132 | return data;
133 | }
134 |
135 | @Override
136 | public int read(byte[] b, int off, int len) throws IOException {
137 | int count = wrapped.read(b, off, len);
138 | if (count != -1) {
139 | bytesRead += count;
140 | updateProgress();
141 | }
142 | return count;
143 | }
144 |
145 | private void updateProgress() {
146 | if (totalSize > 0) {
147 | double fraction = (double) bytesRead / totalSize;
148 | progressIndicator.setFraction(fraction);
149 | progressIndicator.setText2(String.format("Downloaded %s/%s",
150 | formatSize(bytesRead), formatSize(totalSize)));
151 | }
152 | }
153 |
154 | private String formatSize(int bytes) {
155 | if (bytes < 1024) return bytes + " B";
156 | if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
157 | return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
158 | }
159 |
160 | @Override
161 | public void close() throws IOException {
162 | wrapped.close();
163 | }
164 | }
165 | }
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/utils/FilePermissionUtils.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.utils;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.*;
5 | import java.nio.file.attribute.BasicFileAttributes;
6 | import java.nio.file.attribute.DosFileAttributeView;
7 | import java.nio.file.attribute.PosixFileAttributeView;
8 | import java.nio.file.attribute.PosixFilePermission;
9 | import java.util.EnumSet;
10 | import java.util.Set;
11 |
12 | public class FilePermissionUtils {
13 |
14 | /**
15 | * 递归设置目录和文件的权限(跨平台)
16 | *
17 | * @param path 目录或文件路径
18 | * @throws IOException 如果发生 I/O 错误
19 | */
20 | public static void setPermissionsRecursive(Path path) throws IOException {
21 | // 设置当前路径的权限
22 | setPermissions(path);
23 |
24 | // 如果是目录,递归设置子文件和子目录的权限
25 | if (Files.isDirectory(path)) {
26 | Files.walkFileTree(path, new SimpleFileVisitor() {
27 | @Override
28 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
29 | setPermissions(file);
30 | return FileVisitResult.CONTINUE;
31 | }
32 |
33 | @Override
34 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
35 | if (exc == null) {
36 | setPermissions(dir);
37 | return FileVisitResult.CONTINUE;
38 | } else {
39 | throw exc; // 抛出异常而不是静默忽略
40 | }
41 | }
42 | });
43 | }
44 | }
45 |
46 | /**
47 | * 跨平台设置权限
48 | *
49 | * @param path 文件或目录路径
50 | * @throws IOException 如果发生 I/O 错误
51 | */
52 | private static void setPermissions(Path path) throws IOException {
53 | // 对于POSIX系统(Linux/Mac)
54 | if (isPosix(path)) {
55 | // 设置所有者、组和其他用户的读、写和执行权限
56 | Set perms = EnumSet.of(
57 | PosixFilePermission.OWNER_READ,
58 | PosixFilePermission.OWNER_WRITE,
59 | PosixFilePermission.OWNER_EXECUTE,
60 | PosixFilePermission.GROUP_READ,
61 | PosixFilePermission.GROUP_WRITE,
62 | PosixFilePermission.GROUP_EXECUTE,
63 | PosixFilePermission.OTHERS_READ,
64 | PosixFilePermission.OTHERS_WRITE,
65 | PosixFilePermission.OTHERS_EXECUTE
66 | );
67 | Files.setPosixFilePermissions(path, perms);
68 | }
69 | // 对于Windows系统
70 | else {
71 | // Windows不需要显式设置"执行"权限,主要通过读取权限控制
72 | // 但我们需要确保至少有以下权限:
73 | DosFileAttributeView attrs = Files.getFileAttributeView(path, DosFileAttributeView.class);
74 | if (attrs != null) {
75 | // 移除只读属性(如果有)
76 | attrs.setReadOnly(false);
77 | }
78 | }
79 | }
80 |
81 | /**
82 | * 检查是否支持POSIX权限
83 | *
84 | * @param path 文件或目录路径
85 | * @return 是否支持POSIX权限
86 | * @throws IOException 如果发生 I/O 错误
87 | */
88 | private static boolean isPosix(Path path) throws IOException {
89 | return Files.getFileStore(path).supportsFileAttributeView(PosixFileAttributeView.class);
90 | }
91 |
92 | /**
93 | * 测试方法
94 | *
95 | * @param args 命令行参数
96 | */
97 | public static void main(String[] args) {
98 | if (args.length == 0) {
99 | System.err.println("Usage: java FilePermissionUtils ");
100 | return;
101 | }
102 |
103 | try {
104 | Path path = Paths.get(args[0]);
105 | setPermissionsRecursive(path);
106 | System.out.println("Permissions set successfully for: " + path);
107 | } catch (IOException e) {
108 | System.err.println("Failed to set permissions: " + e.getMessage());
109 | e.printStackTrace();
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.utils;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.Files;
5 | import java.nio.file.Path;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.stream.Stream;
9 |
10 | public class FileUtils {
11 | /**
12 | * 查找父目录下最短的 bin 目录路径
13 | *
14 | * @param parentDirectory 父目录路径
15 | * @return 最短的 bin 目录路径(如 "jdk-17.0.1/bin"),找不到返回 null
16 | */
17 | public static String findShortestBinPath(Path parentDirectory) {
18 | if (!Files.isDirectory(parentDirectory)) {
19 | System.err.println("Provided path is not a directory");
20 | return null;
21 | }
22 |
23 | String shortestBinPath = null;
24 | int shortestLength = Integer.MAX_VALUE;
25 |
26 | try {
27 | List binPaths = new ArrayList<>();
28 | Files.walk(parentDirectory)
29 | .filter(Files::isDirectory)
30 | .filter(path -> path.getFileName().toString().equals("bin"))
31 | .forEach(binPaths::add);
32 |
33 | for (Path binPath : binPaths) {
34 | String relativePath = parentDirectory.relativize(binPath).toString();
35 | int pathLength = relativePath.length();
36 | if (pathLength < shortestLength) {
37 | shortestLength = pathLength;
38 | shortestBinPath = relativePath;
39 | }
40 | }
41 | } catch (IOException e) {
42 | System.err.println("文件读取失败: " + e.getMessage());
43 | }
44 |
45 | return shortestBinPath;
46 | }
47 |
48 | /**
49 | * 检查父目录下是否存在名为 dcevm 的文件或目录
50 | *
51 | * @param parentDirectory 父目录路径
52 | * @return 如果存在名为 dcevm 的文件或目录,返回 true;否则返回 false
53 | */
54 | public static boolean findJdkDcevm(Path parentDirectory) {
55 | if (!Files.isDirectory(parentDirectory)) {
56 | System.err.println("Provided path is not a directory");
57 | return false;
58 | }
59 |
60 | try (Stream stream = Files.walk(parentDirectory)) {
61 | // 使用 Files.walk 遍历目录,并检查是否存在名为 dcevm 的文件或目录
62 | return stream.anyMatch(path -> path.getFileName().toString().equalsIgnoreCase("dcevm"));
63 | } catch (Exception e) {
64 | System.err.println("文件读取失败: " + e.getMessage());
65 | return false;
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/utils/HotswapPropertiesGenerator.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.utils;
2 |
3 | import com.intellij.openapi.vfs.VirtualFile;
4 | import org.apache.commons.io.IOUtils;
5 |
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.nio.charset.StandardCharsets;
9 |
10 | public class HotswapPropertiesGenerator {
11 |
12 | public static VirtualFile generateHotswapProperties(VirtualFile resourceDir) {
13 | try {
14 | InputStream templateStream = HotswapPropertiesGenerator.class
15 | .getResourceAsStream("/template/hotswap-agent.properties");
16 | if (templateStream == null) {
17 | return null;
18 | }
19 |
20 | String content = IOUtils.toString(templateStream, StandardCharsets.UTF_8);
21 |
22 | VirtualFile propertiesFile = resourceDir.findChild("hotswap-agent.properties");
23 | if (propertiesFile == null) {
24 | propertiesFile = resourceDir.createChildData(null, "hotswap-agent.properties");
25 | }
26 |
27 | propertiesFile.setBinaryContent(content.getBytes(StandardCharsets.UTF_8));
28 |
29 | return propertiesFile;
30 | } catch (IOException ex) {
31 | ex.printStackTrace();
32 | return null;
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/utils/JdkConfigurationUtils.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.utils;
2 |
3 | import com.intellij.openapi.diagnostic.Logger;
4 | import com.intellij.openapi.project.Project;
5 | import com.intellij.openapi.projectRoots.ProjectJdkTable;
6 | import com.intellij.openapi.projectRoots.Sdk;
7 | import com.intellij.openapi.projectRoots.SdkModificator;
8 | import com.intellij.openapi.projectRoots.impl.JavaSdkImpl;
9 | import com.intellij.openapi.roots.ProjectRootManager;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import java.lang.reflect.Method;
14 |
15 | /**
16 | * JDK 配置工具类(兼容 IntelliJ IDEA 2016.3+)
17 | * 功能:
18 | * 1. 自动检测 IDE 版本选择最优配置方式
19 | * 2. 支持新增/更新 JDK 配置
20 | * 3. 自动配置类路径、源码路径、文档路径
21 | * 4. 完善的错误处理和日志记录
22 | */
23 | public class JdkConfigurationUtils {
24 | private static final Logger LOG = Logger.getInstance(JdkConfigurationUtils.class);
25 |
26 | // 标记是否检测到新版 API(2019.3+ 的 JdkUtil)
27 | private static final boolean HAS_MODERN_API = detectModernApi();
28 |
29 | /**
30 | * 配置项目 JDK(自动新增或更新)
31 | *
32 | * @param project 当前项目
33 | * @param jdkName JDK 名称(如 "JDK 17")
34 | * @param jdkHome JDK 安装路径
35 | * @param version Java 版本(如 "1.8"、"11"、"17")
36 | * @return 是否配置成功
37 | */
38 | public static boolean setupProjectJdk(@NotNull Project project, @NotNull String version, @NotNull String jdkName, @NotNull String jdkHome) {
39 | try {
40 | // 获取或创建 JDK 实例
41 | Sdk jdk = getOrCreateJdk(jdkName, jdkHome);
42 | if (jdk == null) {
43 | LOG.error("Failed to create JDK instance");
44 | return false;
45 | }
46 | ProjectJdkTable sdkTable = ProjectJdkTable.getInstance();
47 | sdkTable.addJdk(jdk);
48 |
49 | // 设置版本信息
50 | if (!setJdkVersion(jdk, version)) {
51 | LOG.warn("Failed to set JDK version, but continuing...");
52 | }
53 |
54 | // 应用到项目
55 | ProjectRootManager.getInstance(project).setProjectSdk(jdk);
56 | LOG.info("JDK configured successfully");
57 | return true;
58 | } catch (Exception e) {
59 | LOG.error("JDK configuration failed", e);
60 | return false;
61 | }
62 | }
63 |
64 | /**
65 | * 获取或创建 JDK 实例(自动处理新增/更新)
66 | */
67 | @Nullable
68 | private static Sdk getOrCreateJdk(@NotNull String name, @NotNull String home) {
69 | ProjectJdkTable sdkTable = ProjectJdkTable.getInstance();
70 | Sdk existingJdk = sdkTable.findJdk(name);
71 |
72 | // 移除现有 JDK(确保配置完全更新)
73 | if (existingJdk != null) {
74 | LOG.info("Removing existing JDK to refresh configuration");
75 | sdkTable.removeJdk(existingJdk);
76 | }
77 |
78 | // 创建新的 JDK 实例
79 | return createJdk(name, home);
80 | }
81 |
82 | /**
83 | * 创建 JDK 实例(自动选择最优方式)
84 | */
85 | @Nullable
86 | private static Sdk createJdk(@NotNull String name, @NotNull String home) {
87 | try {
88 | if (HAS_MODERN_API) {
89 | // 使用新版 API(2019.3+)
90 | return createJdkWithJdkUtil(name, home);
91 | }
92 | // 使用兼容模式(2016.3+)
93 | return createJdkWithJavaSdkImpl(name, home);
94 | } catch (Exception e) {
95 | LOG.error("JDK creation failed", e);
96 | return null;
97 | }
98 | }
99 |
100 | /**
101 | * 使用 JdkUtil(2019.3+ API)
102 | */
103 | private static Sdk createJdkWithJdkUtil(@NotNull String name, @NotNull String home) {
104 | try {
105 | Class> jdkUtil = Class.forName("com.intellij.openapi.projectRoots.impl.JdkUtil");
106 | Method createJdk = jdkUtil.getMethod("createJdk", String.class, String.class, boolean.class);
107 | return (Sdk) createJdk.invoke(null, name, home, false);
108 | } catch (Exception e) {
109 | LOG.warn("JdkUtil failed, falling back to JavaSdkImpl", e);
110 | return createJdkWithJavaSdkImpl(name, home);
111 | }
112 | }
113 |
114 | /**
115 | * 使用 JavaSdkImpl(兼容旧版)
116 | */
117 | private static Sdk createJdkWithJavaSdkImpl(@NotNull String name, @NotNull String home) {
118 | try {
119 | return JavaSdkImpl.getInstance().createJdk(name, home,false);
120 | } catch (Exception e) {
121 | LOG.error("JavaSdkImpl creation failed", e);
122 | return null;
123 | }
124 | }
125 |
126 | /**
127 | * 设置 JDK 版本信息
128 | */
129 | private static boolean setJdkVersion(@NotNull Sdk jdk, @NotNull String version) {
130 | try {
131 | SdkModificator modificator = jdk.getSdkModificator();
132 | modificator.setVersionString(version);
133 | modificator.commitChanges();
134 | return true;
135 | } catch (Exception e) {
136 | LOG.error("Failed to set JDK version", e);
137 | return false;
138 | }
139 | }
140 |
141 | /**
142 | * 检测是否支持新版 API
143 | */
144 | private static boolean detectModernApi() {
145 | try {
146 | Class.forName("com.intellij.openapi.projectRoots.impl.JdkUtil");
147 | LOG.warn("support JdkUtil 检测到新版 API(2019.3+ 的 JdkUtil)");
148 | return true;
149 | } catch (ClassNotFoundException e) {
150 | LOG.warn("support JavaSdkImpl 使用兼容模式(2016.3+)");
151 | LOG.warn("", e);
152 | return false;
153 | }
154 | }
155 | }
--------------------------------------------------------------------------------
/src/main/java/org/hotswap/hotswaphelper/utils/MyUtils.java:
--------------------------------------------------------------------------------
1 | package org.hotswap.hotswaphelper.utils;
2 |
3 | import com.google.common.collect.Lists;
4 | import com.intellij.notification.Notification;
5 | import com.intellij.notification.NotificationAction;
6 | import com.intellij.notification.NotificationType;
7 | import com.intellij.openapi.actionSystem.AnActionEvent;
8 | import com.intellij.openapi.options.ShowSettingsUtil;
9 | import com.intellij.openapi.project.Project;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.io.File;
13 | import java.util.List;
14 |
15 | /**
16 | * @author bruce ge 2024/8/19
17 | */
18 | public class MyUtils {
19 |
20 | public static List allOpens = Lists.newArrayList("--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"
21 | ,"--add-opens=java.base/java.lang=ALL-UNNAMED","--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
22 | ,"--add-opens=java.base/java.io=ALL-UNNAMED","--add-opens=java.base/sun.security.action=ALL-UNNAMED",
23 | "--add-opens=java.base/jdk.internal.reflect=ALL-UNNAMED","--add-opens=java.base/java.net=ALL-UNNAMED");
24 |
25 | public static String WINDOWSHOTSWAPFOLDER = "C:/tmp/HotSwapAgent/";
26 | public static String OTHERHOTSWAPFOLDER = "/tmp/HotSwapAgent/";
27 |
28 |
29 | public static boolean isWindows() {
30 | return System.getProperty("os.name").startsWith("Windows");
31 | }
32 |
33 |
34 | public static File getHotSwapFolder() {
35 | //todo use user path instead of tmp file
36 | String property = System.getProperty("user.home");
37 | File file = new File(property, ".hotswap");
38 | return file;
39 | }
40 |
41 | public static File getHotSwapJarPath(){
42 | return new File(getHotSwapFolder(),"hotswap-agent.jar");
43 | }
44 |
45 | public static File getDebuggerAgentFile(){
46 | return new File(getHotSwapFolder(),"debugger-agent.jar");
47 | }
48 |
49 | public static void notifyUserToOpenReloadClasses() {
50 | Notification notification = new Notification("HotSwapAgent", "HotSwapAgent issues", "Current hotswap reload after compilation is never,Please change it to always", NotificationType.ERROR);
51 | notification.addAction(new NotificationAction("Open Settings") {
52 | @Override
53 | public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
54 | Project project = e.getProject();
55 | if (project != null) {
56 | ShowSettingsUtil.getInstance().showSettingsDialog(project, "reference.idesettings.debugger.hotswap");
57 | }
58 | }
59 | });
60 | notification.notify(null);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/resources/HotSwapHelperIntellijPluginBundle.properties:
--------------------------------------------------------------------------------
1 | afterdownloading=afterDownloading
2 | settings.hotswap.plugin.name=HotSwapHelper
3 | \u65E0\u6CD5\u4E0B\u8F7D\u53EF\u4EE5\u52A0\u5165qq\u7FA4=\u65E0\u6CD5\u4E0B\u8F7D\u53EF\u4EE5\u52A0\u5165qq\u7FA4 \u7FA4\u6587\u4EF6\u6709jdk:
4 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/conf_gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.bruce.hotswapHelper
3 | HotSwapHelper
4 | bruce-ge
5 |
7 | Based on HotSwapAgent, reload code without restarting the application.
8 | Support many framework like spring,hibernate,mybatis ect
9 |
10 | 中文:
11 | BiliBili
12 | 基于HotSwapAgent开发的idea插件,实现热加载功能,支持spring,hibernate,mybatis,mybatisplus等框架
13 | jrebel的免费替代
14 | 修改代码后无需重启服务器即可热加载
15 | 测试已支持若依jeecg,mall,RuoYi-Vue-Plus等项目,支持xml和java文件热加载
16 | 支持java方法字段增减,修改java方法体,支持spring controller加方法,改参数等
17 | 兼容mybatis和mybatisplus,支持修改xml,增删xml
18 | 相比Jrebel支持mybatis添加删除xml,Mybatis实体类加减字段后也能映射好,还支持spring aop比如方法注解等生效
19 | ]]>
20 |
21 | 1.1.6
23 |
24 | 1.Support generate hotswap-agent.properties file
25 | 2.Check if reload after compilation is used and notify user
26 |
27 |
28 | 1.支持生成hotswap-agent.properties文件
29 | 2.检查是否在编译后热加载是否开启,并通知用户
30 |
31 | 1.1.5
32 |
33 | 1.extract vm options from setting for gradle project ect
34 | 2.add support for dont check jdk
35 |
36 |
37 | 1.从设置中提取vm options,适配gradle项目
38 | 2.增加不检测jdk的选项
39 | 3.兼容mybatisplus 3.5.7以上版本,比如ruoyi vue plus项目
40 |
41 | 1.1.4
42 |
43 | 1.exception fix when use default agent file
44 |
45 |
46 | 1.修复非spring web项目Filter报错
47 | 2.兼容ruoyi flowable项目
48 |
49 | 1.1.3
50 |
51 | 1.When using jbr, wont check hotswapAgent.jar so user can just download from jbr github
52 | 2.update the agent to latest
53 |
54 |
55 | 1.当使用jbr的时候,不再去检测是否使用了hotSwapAgent文件,可以直接从jbr官方下载jdk
56 | 2.修复mybatisplus使用config文件热加载不生效的问题
57 | 3.兼容mybatisplus老版本使用service batch方法运行报错的问题
58 | 4.兼容springBlade项目
59 |
60 | 1.1.2
61 |
62 | 1.Support 2024.3 NoClassFoundException issue
63 |
64 |
65 | 1.兼容2024.3 解决了报错问题
66 |
67 | 1.1.0
68 |
69 | 1.Fix mybatis xml reload not work issue
70 | 2.Support disable plugins
71 |
72 |
73 | 1.修复mybatis xml没有热加载的问题
74 | 2.支持禁用插件
75 |
76 | 1.0.9
77 |
78 | 1.Support mybatisPlus version from 3.0 by homejim
79 |
80 |
81 |
82 | 1.兼容mybatisplus的版本从3.0开始homejim
83 |
84 | 1.0.8
85 |
86 | 1.Support linux jdk 1.8
87 | 2.Update HotSwapAgent jar file
88 | 3.Support dynamic datasource start from 3.3.2
89 |
90 | 1.0.7
91 |
92 | 1.Fix HotSwapAgent jar file not found issues
93 |
94 | 1.0.6
95 |
96 | 1.Mac using jbr11 AllowEnhancedClassRedefinition not added issue
97 |
98 | 1.0.5
99 |
100 | 1.Spring controller change parameters support
101 | 2.Mybatis add or delete xml support
102 |
103 | 1.0.4
104 |
105 | 1.Better spring aop support
106 | 2.Spring @Valid annotation support
107 | 3.Better Mybatis interceptor support
108 |
109 | 1.0.3
110 |
111 | 1.Support pageHelper ect mybatis interceptor
112 |
113 | 1.0.2
114 |
115 | 1.Support config hotSwap jar file
116 |
117 | 1.0.1
118 |
119 | 1.Fix java17 module java.base does not "opens sun.nio.ch" to unnamed module
120 |
121 | 1.0.0
122 |
123 | 1.Support run with hotswap agent
124 | 2.Support many framework include spring,mybatis ect
125 |
126 |
127 | ]]>
128 |
129 |
130 |
131 |
132 |
134 |
137 |
138 | com.intellij.modules.lang
139 |
140 | com.intellij.java
141 | com.intellij.gradle
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ionicons-v5-b
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/resources/debugger-agent.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/src/main/resources/debugger-agent.jar
--------------------------------------------------------------------------------
/src/main/resources/hotswap-agent.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/src/main/resources/hotswap-agent.jar
--------------------------------------------------------------------------------
/src/main/resources/icons/PluginIcons.java:
--------------------------------------------------------------------------------
1 | package icons;
2 |
3 | import com.intellij.openapi.util.IconLoader;
4 | import javax.swing.Icon;
5 |
6 | public interface PluginIcons {
7 | Icon ICON = IconLoader.getIcon("/icons/icon.png", PluginIcons.class);
8 | Icon ICON_16 = IconLoader.getIcon("/icons/icon_16.png", PluginIcons.class);
9 | Icon ICON_32 = IconLoader.getIcon("/icons/icon@2x.png", PluginIcons.class);
10 | Icon ICON_64 = IconLoader.getIcon("/icons/icon_64.png", PluginIcons.class);
11 | Icon ICONDBUG_16 = IconLoader.getIcon("/icons/rotate.png", PluginIcons.class);
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/resources/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/src/main/resources/icons/icon.png
--------------------------------------------------------------------------------
/src/main/resources/icons/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/src/main/resources/icons/icon@2x.png
--------------------------------------------------------------------------------
/src/main/resources/icons/icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/src/main/resources/icons/icon_16.png
--------------------------------------------------------------------------------
/src/main/resources/icons/icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/src/main/resources/icons/icon_64.png
--------------------------------------------------------------------------------
/src/main/resources/icons/rotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/src/main/resources/icons/rotate.png
--------------------------------------------------------------------------------
/src/main/resources/icons/rotate@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/src/main/resources/icons/rotate@2x.png
--------------------------------------------------------------------------------
/src/main/resources/icons/rotate_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gejun123456/HotSwapHelper/61d2ce9e822993fd37249f615a6ed4f09c5825f4/src/main/resources/icons/rotate_64.png
--------------------------------------------------------------------------------
/src/main/resources/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
ionicons-v5-b
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/resources/string.properties:
--------------------------------------------------------------------------------
1 | button.reset=reset default
2 | label.all=&All
3 | label.version=¤t version\uFF1A
4 | label.author.name=&author name\uFF1A
5 | label.cancel=&Cancel
6 | label.choose=&Choose
7 | label.group=&Group
8 | label.module=&Module:
9 | label.ok=&OK
10 | label.package=&Package:
11 | label.path=&Path:
12 | label.removePre=&RemovePre:
13 | label.template=Template:
14 | label.unified.config=configure unified
15 | migratelable=when migrate from MybatisCodeHelperPro plugin, please use global config MybatisCodeHelperPro.
16 | clipboardToScratch=clipboard json to scratch file
17 | exportToNetWork=export to network
18 | exportFromNetwork=import from network
19 | exportToLocalFile=export to local file
20 | importFromLocalFile=export from local file
21 | exportToClipBoard=export to clipboard
22 | importFromClipBoard=import from clipboard
23 | localFileJsonToScratchFile=local file json to scratch file
24 | resetDefaultsetting=reset default settings
25 | authorName=author name\uFF1A
26 | version=current version\uFF1A
27 | how.scratch.file.work=how scratch file generate code work
28 | unifiedConfig=unified config
29 | allClickYes=all yes
30 | allClickNo=all no
31 | reformat\ code=reformat code
32 | \u5F39\u6846\u9009\u662F=All yes(will cover file if exist)
33 | \u5F39\u6846\u9009\u5426=All No
34 | \u683C\u5F0F\u5316\u4EE3\u7801=Reformat code
35 | oldConfiguration=old configuration(not recommend, when configure will set value for tableInfo.savePath and tableInfo.savePackageName)
36 | jdkNotSupported=Current jdk is not supported, hotSwapAgent require jdk with dcevm
37 | documentation=documentation
38 | afterdownloading=after downloading, Edit configurations to use with the downloaded jdk as sdk
39 | useexternalagentfileforcurrentproject=useExternalAgentFileForCurrentProject
40 | example=example
41 | disabled.plugins=Disabled plugins:
42 | dont.check.jdk=dont check jdk(not recommended)
43 |
--------------------------------------------------------------------------------
/src/main/resources/string_zh_CN.properties:
--------------------------------------------------------------------------------
1 | button.reset=\u91CD\u7F6E\u9ED8\u8BA4\u8BBE\u7F6E
2 | label.all=&All
3 | label.version=&\u5F53\u524D\u7248\u672C\uFF1A
4 | label.author.name=&\u4F5C\u8005\u540D\u79F0\uFF1A
5 | label.cancel=&Cancel
6 | label.choose=&Choose
7 | label.group=&Group
8 | label.module=&Module:
9 | label.ok=&OK
10 | label.package=&Package:
11 | label.path=&Path:
12 | label.removePre=&RemovePre:
13 | label.template=Template:
14 | label.unified.config=\u7EDF\u4E00\u914D\u7F6E
15 | migratelable=\u5F53\u4ECEMybatisCodeHelperPro\u63D2\u4EF6\u8FC1\u79FB\u5230\u6A21\u7248\u751F\u6210,GlobalConfig\u9700\u8981\u914D\u7F6E\u4E3AMybatisCodeHelper,template\u9009\u62E9\u4E00\u4E2Amigrate\u6A21\u7248
16 | clipboardToScratch=\u526A\u5207\u677F\u5BFC\u5165\u5230scratch\u6587\u4EF6
17 | exportToNetWork=\u5BFC\u51FA\u81F3\u4E91\u7AEF
18 | exportFromNetwork=\u4ECE\u4E91\u7AEF\u5BFC\u5165
19 | exportToLocalFile=\u5BFC\u51FA\u81F3\u672C\u5730
20 | importFromLocalFile=\u4ECE\u672C\u5730\u5BFC\u5165
21 | exportToClipBoard=\u5BFC\u51FA\u81F3\u526A\u5207\u677F
22 | importFromClipBoard=\u4ECE\u526A\u5207\u677F\u5BFC\u5165
23 | localFileJsonToScratchFile=\u4ECEjson\u6587\u4EF6\u5BFC\u5165\u5230scratch\u6587\u4EF6
24 | resetDefaultsetting=\u91CD\u7F6E\u9ED8\u8BA4\u8BBE\u7F6E
25 | authorName=\u4F5C\u8005\u540D\u79F0\uFF1A
26 | version=\u5F53\u524D\u7248\u672C\uFF1A
27 | how.scratch.file.work=\u5982\u4F55\u4F7F\u7528scratch\u6587\u4EF6\u751F\u6210\u4EE3\u7801
28 | unifiedConfig=\u5728\u591A\u8868\u751F\u6210\u65F6\uFF0C\u6BCF\u5F20\u8868\u4F1A\u6709\u81EA\u5DF1\u4FDD\u5B58\u7684\u914D\u7F6E\u4FE1\u606F\u3002 \u52FE\u9009\u7EDF\u4E00\u914D\u7F6E\u8868\u793A\u6240\u6709\u8868\u90FD\u4F7F\u7528\u5F53\u524D\u7684\u914D\u7F6E\u4FE1\u606F\u8FDB\u884C\u751F\u6210\u3002 \u5426\u5219\u4F7F\u7528\u81EA\u8EAB\u4FDD\u5B58\u7684\u914D\u7F6E\u4FE1\u606F\u751F\u6210\u4EE3\u7801\u3002
29 | allClickYes=\u5F39\u6846\u9009\u662F
30 | allClickNo=\u5F39\u6846\u9009\u5426
31 | reformat\ code=\u683C\u5F0F\u5316\u4EE3\u7801
32 | \u5F39\u6846\u9009\u662F=\u5F39\u6846\u9009\u662F(\u5982\u679C\u6587\u4EF6\u5DF2\u7ECF\u5B58\u5728\u4F1A\u88AB\u8986\u76D6)
33 | \u5F39\u6846\u9009\u5426=\u5F39\u6846\u9009\u5426
34 | \u683C\u5F0F\u5316\u4EE3\u7801=\u683C\u5F0F\u5316\u4EE3\u7801
35 | oldConfiguration=\u8001\u914D\u7F6E(\u4E0D\u63A8\u8350, \u914D\u7F6E\u7684\u65F6\u5019\u4F1A\u7ED9 tableInfo.savePath\u548CtableInfo.savePackageName\u8D4B\u503C\uFF0C\u6A21\u7248\u4E2D\u4F7F\u7528\u8FD9\u4E9B\u53D8\u91CF\u9700\u8981\u914D\u7F6E\u8FD9\u4E2A)
36 | jdkNotSupported=\u5F53\u524DJDK\u4E0D\u652F\u6301,\u9700\u8981\u4E0B\u8F7D\u7279\u6B8A\u7684jdk\u624D\u80FD\u652F\u6301\u7C7B\u52A0\u51CF\u5B57\u6BB5\u52A0\u51CF\u65B9\u6CD5\u70ED\u52A0\u8F7D,\u4E0B\u9762\u53EF\u4EE5\u4E0B\u8F7D
37 | documentation=\u6587\u6863
38 | afterdownloading=\u4E0B\u8F7D\u5B8C\u6210\u540E\uFF0CEdit Configurations\u7F16\u8F91\u914D\u7F6E\u4E2D\u7684sdk\u914D\u7F6E\u4E3A\u4E0B\u8F7D\u7684jdk
39 | useexternalagentfileforcurrentproject=\u5F53\u524D\u9879\u76EE\u4F7F\u7528\u5916\u90E8agent\u6587\u4EF6(\u4E00\u822C\u8FD9\u91CC\u4E0D\u9700\u8981\u914D\u7F6E,\u63D2\u4EF6\u5185\u90E8\u81EA\u5E26\u4E86\u4E00\u4E2A)
40 | example=\u4F8B\u5B50
41 | disabled.plugins=\u7981\u7528\u7684\u63D2\u4EF6(\u7981\u7528\u90E8\u5206\u63D2\u4EF6\u53EF\u4EE5\u63D0\u5347\u6027\u80FD\u6216\u8005\u907F\u514D\u9519\u8BEF):
42 | dont.check.jdk=\u4E0D\u68C0\u67E5jdk(\u4E0D\u5EFA\u8BAE)
43 |
--------------------------------------------------------------------------------
/src/main/resources/template/hotswap-agent.properties:
--------------------------------------------------------------------------------
1 | # Default agent properties
2 | # You can override them in your application by creating hotswap-agent.properties file in class root
3 | # and specifying new property values.
4 |
5 | # Add a directory prior to application classpath (load classes and resources).
6 | #
7 | # This may be useful for example in multi module maven project to load class changes from upstream project
8 | # classes. Set extraClasspath to upstream project compiler output and .class file will have precedence to
9 | # classes from built JAR file.
10 | extraClasspath=
11 |
12 | # Watch for changes in a directory (resources only). If not set, changes of resources won't be observed.
13 | #
14 | # Similar to extraClasspath this property adds classpath when searching for resources (not classes).
15 | # While extra classpath just modifies the classloader, this setting does nothing until the resource
16 | # is really changed.
17 | #
18 | # Sometimes it is not possible to point extraClasspath to your i.e. src/main/resources, because there are multiple
19 | # replacements of resources in a building step (maven filtering resource option).
20 | # This setting will leave i.e. src/target/classes as default source for resources, but after the resource is modified
21 | # in src/main/resources, the new changed resource is served instead.
22 | watchResources=
23 |
24 | # Load static web resources from different directory.
25 | #
26 | # This setting is dependent on application server plugin(Jetty, Tomcat, ...).
27 | # Jboss and Glassfish are not yet supported.
28 | # Use this setting to set to serve resources from source directory directly (e.g. src/main/webapp).
29 | webappDir=
30 |
31 |
32 | # Comma separated list of disabled plugins
33 | # Use plugin name - e.g. Hibernate, Spring, ZK, Hotswapper, AnonymousClassPatch, Tomcat, Logback ....
34 | disabledPlugins=
35 |
36 | # Watch for changed class files on watchResources path and reload class definition in the running application.
37 | #
38 | # Usually you will launch debugging session from your IDE and use standard hotswap feature.
39 | # This property is useful if you do not want to use debugging session for some reason or
40 | # if you want to enable hotswap at runtime environment.
41 | #
42 | # Internally this uses java Instrumentation API to reload class bytecode. If you need to use JPDA API instead,
43 | # specify autoHotswap.port with JPDA port.
44 | autoHotswap=false
45 |
46 | # The base package prefix of your spring application (e.g. org.hotswap.).
47 | # Needed when component scan is turned off, so we can still know which classes is your beans
48 | # Can also be set to filter beans we handle to improve performance (So that we won't create proxy for thirty party lib's beans).
49 | # Comma separated.
50 | #spring.basePackagePrefix=
51 |
52 | # Create Java Platform Debugger Architecture (JPDA) connection on autoHotswap.port, watch for changed class files
53 | # and do the hotswap (reload) in background.
54 | #
55 | # You need to specify JPDA port at startup
56 | # java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
57 | # autoHotswap.port=8000
58 |
59 | # Enables debugging in OsgiEquinox
60 | # osgiEquinox.debugMode=true
61 |
62 | # Setup reloading strategy of bean INSTANCE(s) in Weld CONTEXT(s). While bean class is redefined by DCEVM, reloading of bean instances
63 | # can be customized by this parameter. Available values:
64 | # - CLASS_CHANGE - reload bean instance on any class modification, plus reaload on changes specified in
65 | # METHOD_FIELD_SIGNATURE_CHANGE and FIELD_SIGNATURE_CHANGE strategies
66 | # - METHOD_FIELD_SIGNATURE_CHANGE - reload bean instance on any method/field change. Includes changes specified in
67 | # strategy FIELD_SIGNATURE_CHANGE
68 | # - FIELD_SIGNATURE_CHANGE - reload bean instance on any field signature change. Includes also field annotation changes
69 | # - NEVER - never reload bean (default)
70 | # weld.beanReloadStrategy=NEVER
71 |
72 | # Logger setup - use entries in the format of
73 | # format: LOGGER.my.package=LEVEL
74 | # e.g. LOGGER.org.hotswap.agent.plugin.myPlugin=trace
75 | # root level
76 | LOGGER=info
77 | # DateTime format using format of SimpleDateFormat, default value HH:mm:ss.SSS
78 | # LOGGER_DATETIME_FORMAT=HH:mm:ss.SSS
79 |
80 | # Print output into logfile (with choice to append - false by default)
81 | # LOGFILE=agent.log
82 | # LOGFILE.append=true
83 |
84 | # Comma separated list of class loaders to exclude from initialization, in the form of RegEx patterns.
85 | #excludedClassLoaderPatterns=jdk.nashorn.*
86 |
--------------------------------------------------------------------------------