1 /* 2 * Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 package com.sk.ts.dialog; 16 17 import com.intellij.notification.NotificationType; 18 import com.intellij.openapi.diagnostic.Logger; 19 import com.intellij.openapi.project.Project; 20 import com.intellij.openapi.ui.ValidationInfo; 21 22 import com.sk.ts.action.SelectHAction; 23 import com.sk.ts.action.SelectOutPathAction; 24 import com.sk.ts.utils.FileInfo; 25 import com.sk.ts.utils.FileUtil; 26 import com.sk.ts.utils.GenNotification; 27 import org.apache.http.util.TextUtils; 28 import org.jetbrains.annotations.Nullable; 29 import javax.swing.JDialog; 30 import javax.swing.JPanel; 31 import javax.swing.JTextField; 32 import javax.swing.JButton; 33 import javax.swing.JComponent; 34 import javax.swing.KeyStroke; 35 import java.awt.event.KeyEvent; 36 import java.awt.event.WindowAdapter; 37 import java.awt.event.WindowEvent; 38 import java.awt.event.WindowListener; 39 import java.io.BufferedReader; 40 import java.io.File; 41 import java.io.FileOutputStream; 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.io.InputStreamReader; 45 import java.util.ArrayList; 46 import java.util.List; 47 48 /** 49 * 配置对话框 50 * 51 * @author: xudong 52 * @see: generator dialog 53 * @version: v1.0.0 54 * @since 2022-02-21 55 */ 56 public class GenerateDialogPane extends JDialog { 57 private static final Logger LOG = Logger.getInstance(GenerateDialogPane.class); 58 59 private final Project project; 60 private List<String> tsFileList = new ArrayList<>(); 61 private JPanel contentPane; 62 private JTextField textFieldSelectH; 63 private JTextField textFieldSelectOutPath; 64 65 private JButton buttonSelectH; 66 private JButton buttonSelectOutPath; 67 68 private boolean generateSuccess = true; 69 private String sErrorMessage = ""; 70 private String interFileOrDir; 71 private String genOutDir; 72 private String scriptOutDir; 73 private String hGenFileName; 74 private String tsGenFileName; 75 private String tsOutPath; 76 77 78 /** 79 * 构造函数 80 * 81 * @param project projectid 82 * @param interFilePath 接口文件路径 83 * @param genDir 生成框架文件路径 84 * @param scriptDir 脚本目录 85 */ GenerateDialogPane(Project project, String interFilePath, String genDir, String scriptDir)86 public GenerateDialogPane(Project project, String interFilePath, String genDir, String scriptDir) { 87 setContentPane(contentPane); 88 setModal(true); 89 this.project = project; 90 this.interFileOrDir = interFilePath; 91 this.genOutDir = genDir; 92 this.scriptOutDir = scriptDir; 93 if (FileUtil.patternFileNameH(scriptDir)) { 94 textFieldSelectH.setText(interFileOrDir); 95 textFieldSelectOutPath.setText(genOutDir); 96 } 97 contentPane.registerKeyboardAction(actionEvent -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), 98 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 99 100 buttonSelectH.addActionListener(new SelectHAction(buttonSelectH, textFieldSelectH, 101 textFieldSelectOutPath, project)); 102 buttonSelectOutPath.addActionListener(new SelectOutPathAction(buttonSelectOutPath, textFieldSelectOutPath)); 103 104 } 105 106 @Override addWindowListener(WindowListener windowListener)107 public synchronized void addWindowListener(WindowListener windowListener) { 108 super.addWindowListener(windowListener); 109 new WindowAdapter() { 110 /** 111 * close dialog 112 * 113 * @param windowEvent WindowEvent 114 */ 115 @Override 116 public void windowClosing(WindowEvent windowEvent) { 117 onCancel(); 118 } 119 }; 120 } 121 122 /** 123 * 检查输入的.h文件转换后的同名.d.ts文件是否在输出目录中已存在 124 * 125 * @param fileObj .h文件对象 126 * @param outPutDir 输出目录 127 * @return 如果ts文件已存在,返回文件名;否则返回空字符串 128 */ getExistFileName(File fileObj, String outPutDir)129 private String getExistFileName(File fileObj, String outPutDir) { 130 if (fileObj.isDirectory()) { 131 // 遇到文件夹直接跳过不检查,只检查普通.h文件是否存在对应的.ts 132 return ""; 133 } 134 String hFileName = fileObj.getName(); 135 String tsFileName = hFileName.substring(0, hFileName.lastIndexOf(".")) + ".d.ts"; 136 File tsFile = new File(outPutDir + "/" + tsFileName); 137 return tsFile.exists() ? tsFile.toString() : ""; 138 } 139 140 /** 141 * 检查待生成的ts文件在输出目录中是否已存在 142 * 143 * @param hFilePath 待转换的.h文件/目录路径 144 * @param outPutDir 输出目录路径 145 * @return 如果ts文件已存在,返回文件名;否则返回空字符串 146 */ checkTsFileExist(String hFilePath, String outPutDir)147 private String checkTsFileExist(String hFilePath, String outPutDir) { 148 File hFileObj = new File(hFilePath); 149 if (!hFileObj.isDirectory()) { 150 return getExistFileName(hFileObj, outPutDir); 151 } else { 152 File[] fileList = hFileObj.listFiles(); 153 for (File fileObj : fileList) { 154 String existFileName = getExistFileName(fileObj, outPutDir); 155 if (!existFileName.equals("")) { 156 return existFileName; 157 } 158 } 159 } 160 return ""; 161 } 162 163 /** 164 * 验证文本选择框是否空。是否替换已存在的内容 165 * 166 * @return ValidationInfo 返回不符要求的信息。 167 */ 168 @Nullable validationInfo()169 public ValidationInfo validationInfo() { 170 ValidationInfo validationInfo = null; 171 172 String hFile = textFieldSelectH.getText(); 173 String outPutDir = textFieldSelectOutPath.getText(); 174 boolean isEmptyFile = TextUtils.isEmpty(hFile) || TextUtils.isEmpty(outPutDir); 175 if (isEmptyFile) { 176 String warnMsg = "文件路径、输出路径不能为空"; 177 warningMessage(warnMsg); 178 validationInfo = new ValidationInfo(warnMsg); 179 return validationInfo; 180 } 181 182 String existFileName = checkTsFileExist(hFile, outPutDir); 183 if (!existFileName.equals("")) { 184 ConfirmDialog confirmDialog = new ConfirmDialog( 185 String.format("是否替换已存在的文件:%s ?", existFileName)); 186 if (!confirmDialog.showAndGet()) { 187 validationInfo = new ValidationInfo(String.format("不替换现有文件:%s", existFileName)); 188 return validationInfo; 189 } 190 } 191 192 return validationInfo; 193 } 194 onCancel()195 private void onCancel() { 196 dispose(); 197 } 198 warningMessage(String title)199 private void warningMessage(String title) { 200 String notifyContent = "请选择接口文件或文件夹,生成框架路径,编译脚本路径"; 201 GenNotification.notifyMessage(this.project, notifyContent, title, NotificationType.WARNING); 202 } 203 204 /** 205 * 拷贝文件到本地临时目录 206 * 207 * @param fileName 文件名 208 */ copyFileToLocalPath(String fileName)209 private void copyFileToLocalPath(String fileName) { 210 String sysName = System.getProperties().getProperty("os.name").toUpperCase(); 211 String tmpDirFile = System.getProperty("java.io.tmpdir"); 212 String execFn; 213 if (sysName.contains("WIN")) { 214 execFn = "cmds/win/" + fileName + ".exe"; 215 tmpDirFile += fileName + ".exe"; 216 } else if (sysName.contains("LINUX")) { 217 execFn = "cmds/linux/" + fileName; 218 tmpDirFile += fileName; 219 } else { 220 execFn = "cmds/mac/" + fileName; 221 tmpDirFile += fileName; 222 } 223 try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(execFn)) { 224 if (inputStream == null) { 225 throw new IOException("exec File InputStream is Null"); 226 } 227 byte[] bs = inputStream.readAllBytes(); 228 writeTmpFile(tmpDirFile, bs); 229 if (sysName.contains("LINUX") || sysName.contains("MAC OS")) { 230 executable(tmpDirFile); 231 } 232 } catch (IOException | InterruptedException e) { 233 GenNotification.notifyMessage(this.project, e.getMessage(), "Can not Find File:" + execFn, 234 NotificationType.ERROR); 235 LOG.error(e); 236 } 237 } 238 239 /** 240 * 生成 -f -d 输入参数。 241 * 242 * @param fileOrDir 选中的文件或文件夹路径 243 * @return 生成后的 -f -d的值 244 */ genInArgs(String fileOrDir)245 private String genInArgs(String fileOrDir) { 246 tsFileList.clear(); 247 String[] interArr = fileOrDir.split(","); 248 StringBuilder tsParam = new StringBuilder(" -f "); 249 StringBuilder dirParam = new StringBuilder(" -d "); 250 String inputCommand = ""; 251 if (interArr.length > 0) { 252 for (String interStr : interArr) { 253 File interFile = new File(interStr); 254 if (interFile.isDirectory()) { 255 dirParam.append(interStr).append(" "); 256 for (File tsFile : interFile.listFiles()) { 257 tsFileList.add(tsFile.getPath()); 258 } 259 } else { 260 tsParam.append(interStr).append(","); 261 tsFileList.add(interStr); 262 } 263 } 264 if (!TextUtils.isBlank(tsParam.toString().replaceAll("-f", ""))) { 265 inputCommand += tsParam.substring(0, tsParam.length() - 1); 266 } 267 if (!TextUtils.isBlank(dirParam.toString().replace("-d", ""))) { 268 inputCommand += dirParam.substring(0, dirParam.length() - 1); 269 } 270 } 271 return inputCommand; 272 } 273 callExtProcess(String command)274 private boolean callExtProcess(String command) throws IOException, InterruptedException { 275 276 if (TextUtils.isEmpty(command)) { 277 GenNotification.notifyMessage(this.project, "执行命令文件为空", "空命令行提示", NotificationType.ERROR); 278 return false; 279 } 280 errConsumer.start(); 281 outputConsumer.start(); 282 283 if (!generateSuccess) { 284 GenNotification.notifyMessage(project, sErrorMessage, "提示", NotificationType.ERROR); 285 return false; 286 } 287 288 errConsumer.join(); 289 outputConsumer.join(); 290 return true; 291 } 292 293 /** 294 * 赋值可执行文件权限。 295 * 296 * @param execFn 可执行命令 297 * @throws IOException 打开文件异常 298 * @throws InterruptedException 中断异常 299 */ executable(String execFn)300 private void executable(String execFn) throws IOException, InterruptedException { 301 callExtProcess("chmod a+x " + execFn); 302 } 303 304 /** 305 * 拷贝可执行文件到临时文件夹 306 * 307 * @param path 目标文件路径 308 * @param bs 字节内容 309 * @throws IOException exception 310 */ writeTmpFile(String path, byte[] bs)311 private void writeTmpFile(String path, byte[] bs) throws IOException { 312 File file = new File(path); 313 FileOutputStream fw = null; 314 if (!file.exists()) { 315 boolean isNewFile = file.createNewFile(); 316 if (!isNewFile) { 317 LOG.info("writeTmpFile createNewFile error"); 318 } 319 } 320 try { 321 fw = new FileOutputStream(file); 322 fw.write(bs, 0, bs.length); 323 } catch (IOException e) { 324 // 处理可能发生的IOException 325 LOG.error("Error reading from process streams", e); 326 } finally { 327 try { 328 fw.close(); 329 } catch (IOException e) { 330 LOG.error("Error closing stdInput", e); 331 } 332 } 333 } 334 335 /** 336 * 获取生成成功结果文件。 337 * 338 * @param process 进程ID 339 */ genResultLog(Process process)340 private void genResultLog(Process process) { 341 BufferedReader stdInput = null; 342 BufferedReader stdError = null; 343 try { 344 String sErr; 345 String sOut; 346 sErr = getErrorResult(stdError); 347 if (TextUtils.isEmpty(sErr)) { 348 sOut = genInputLog(stdInput); 349 if (!generateIsSuccess(sOut)) { 350 sErrorMessage = sOut; 351 } 352 } else { 353 generateSuccess = false; 354 sErrorMessage = sErr; 355 } 356 } catch (IOException e) { 357 // 处理可能发生的IOException 358 LOG.error("Error in genResultLog", e); 359 } finally { 360 // 确保BufferedReader对象被关闭 361 try { 362 stdInput.close(); 363 } catch (IOException e) { 364 LOG.error("Error closing stdInput", e); // 记录关闭stdInput时的错误 365 } 366 try { 367 stdError.close(); 368 } catch (IOException e) { 369 LOG.error("Error closing stdError", e); // 记录关闭stdError时的错误 370 } 371 } 372 } 373 374 /** 375 * 获取生成失败结果文件。 376 * 377 * @param stdError error buff 378 * @return ErrorResult 379 */ getErrorResult(BufferedReader stdError)380 private String getErrorResult(BufferedReader stdError) { 381 StringBuilder sErr = new StringBuilder(); 382 while (true) { 383 String sTmp; 384 try { 385 if ((sTmp = stdError.readLine()) == null) { 386 break; 387 } 388 sErr.append(sTmp).append(FileUtil.getNewline()); 389 } catch (IOException ioException) { 390 LOG.error(" genResultLog stdInput error" + ioException); 391 } 392 } 393 return sErr.toString(); 394 } 395 generateIsSuccess(String sOut)396 private boolean generateIsSuccess(String sOut) { 397 generateSuccess = sOut.contains("success") || TextUtils.isEmpty(sOut); 398 return generateSuccess; 399 } 400 401 /** 402 * 获取生成文本内容。 403 * 404 * @param stdInput input buff 405 * @return 返回当前输入框内容 406 */ genInputLog(BufferedReader stdInput)407 private String genInputLog(BufferedReader stdInput) { 408 StringBuilder sOut = new StringBuilder(); 409 while (true) { 410 String sTmp; 411 try { 412 if ((sTmp = stdInput.readLine()) == null) { 413 break; 414 } 415 sOut.append(sTmp).append(FileUtil.getNewline()); 416 } catch (IOException ioException) { 417 LOG.error(" genResultLog stdInput error" + ioException); 418 } 419 } 420 return sOut.toString(); 421 } 422 423 static class StreamConsumer extends Thread { 424 InputStream is; 425 StreamConsumer(InputStream is)426 StreamConsumer(InputStream is) { 427 super.setName("StreamConsumer"); 428 this.is = is; 429 } 430 431 @Override run()432 public void run() { 433 InputStreamReader inputStreamReader = null; 434 BufferedReader bufferedReader = null; 435 try { 436 inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8); 437 bufferedReader = new BufferedReader(inputStreamReader); 438 String readLine; 439 while ((readLine = bufferedReader.readLine()) != null) { 440 LOG.error("StreamConsumer" + readLine); 441 } 442 } catch (IOException ioException) { 443 LOG.error("StreamConsumer io error" + ioException); 444 } finally { 445 // 确保BufferedReader和InputStreamReader被关闭 446 try { 447 bufferedReader.close(); 448 } catch (IOException e) { 449 LOG.error("Error closing BufferedReader", e); 450 } 451 try { 452 inputStreamReader.close(); 453 } catch (IOException e) { 454 LOG.error("Error closing inputStreamReader", e); 455 } 456 } 457 } 458 } 459 460 /** 461 * 获取指定输出目录下的文件列表 462 * 463 * @param outPath 输出目录 464 * @return 文件信息列表 465 */ getFileInfoList(File outPath)466 public List<FileInfo> getFileInfoList(File outPath) { 467 List<FileInfo> fileInfoList = new ArrayList<>(); 468 File[] files = outPath.listFiles(); 469 for (File file : files) { 470 fileInfoList.add(new FileInfo(file)); 471 } 472 return fileInfoList; 473 } 474 475 /** 476 * 执行主程序入口 477 * 478 * @return 执行状态 479 */ runFunH2ts()480 public boolean runFunH2ts() { 481 GenNotification.notifyMessage(this.project, "", "Generating Ts", NotificationType.INFORMATION); 482 copyFileToLocalPath("header_parser"); 483 String command; 484 command = genCommandH2ts(); 485 486 File outPath = new File(textFieldSelectOutPath.getText()); 487 List<FileInfo> oldFileList = getFileInfoList(outPath); 488 try { 489 if (!TextUtils.isEmpty(command) && callExtProcess(command)) { 490 List<FileInfo> newFileList = getFileInfoList(outPath); 491 newFileList.removeAll(oldFileList); // 对比命令执行前后的文件列表差异,得到新生成的文件列表 492 GenNotification.notifyGenResult(project, newFileList, "Generate Ts Successfully", 493 NotificationType.INFORMATION); 494 return true; 495 } 496 } catch (IOException | InterruptedException ex) { 497 GenNotification.notifyMessage(project, textFieldSelectOutPath.getText(), "Command exec error", 498 NotificationType.ERROR); 499 LOG.error(ex); 500 } 501 return false; 502 } 503 504 /** 505 * 生成命令行指令 506 * 507 * @return 返回命令行执行内容 508 */ genCommandH2ts()509 private String genCommandH2ts() { 510 String sysName = System.getProperties().getProperty("os.name").toUpperCase(); 511 String tmpDirFile = System.getProperty("java.io.tmpdir"); 512 if (sysName.contains("WIN")) { 513 copyFileToLocalPath("napi_generator-win"); 514 tmpDirFile += "napi_generator-win.exe"; 515 } else if (sysName.contains("LINUX")) { 516 copyFileToLocalPath("napi_generator-linux"); 517 tmpDirFile += "napi_generator-linux"; 518 } else { 519 copyFileToLocalPath("napi_generator-macos"); 520 tmpDirFile += "napi_generator-macos"; 521 } 522 523 String command = file.toString(); 524 String inArgs = genInArgs(textFieldSelectH.getText()); 525 command += inArgs + " -o " + textFieldSelectOutPath.getText() + " -t " + true; 526 return command; 527 } 528 getContentPanel()529 JPanel getContentPanel() { 530 return contentPane; 531 } 532 533 /** 534 * 获取生成的.d.ts文件名 535 * 536 * @return 生成的.d.ts文件名 537 */ getTsFileName()538 public String getTsFileName() { 539 hGenFileName = textFieldSelectH.getText(); 540 tsGenFileName = hGenFileName.substring(0, hGenFileName.lastIndexOf(".")) + ".d.ts"; 541 return tsGenFileName; 542 } 543 544 /** 545 * 获取生成的.d.ts文件输出路径 546 * 547 * @return .d.ts文件输出路径 548 */ getTsOutPath()549 public String getTsOutPath() { 550 tsOutPath = textFieldSelectOutPath.getText(); 551 return tsOutPath; 552 } 553 554 /** 555 * 获取工程 556 * 557 * @return 当前工程 558 */ getProject()559 public Project getProject() { 560 return this.project; 561 } 562 563 } 564