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 Process process = Runtime.getRuntime().exec(command); 281 genResultLog(process); 282 StreamConsumer errConsumer = new StreamConsumer(process.getErrorStream()); 283 StreamConsumer outputConsumer = new StreamConsumer(process.getInputStream()); 284 errConsumer.start(); 285 outputConsumer.start(); 286 287 if (!generateSuccess) { 288 GenNotification.notifyMessage(project, sErrorMessage, "提示", NotificationType.ERROR); 289 return false; 290 } 291 292 errConsumer.join(); 293 outputConsumer.join(); 294 return true; 295 } 296 297 /** 298 * 赋值可执行文件权限。 299 * 300 * @param execFn 可执行命令 301 * @throws IOException 打开文件异常 302 * @throws InterruptedException 中断异常 303 */ executable(String execFn)304 private void executable(String execFn) throws IOException, InterruptedException { 305 callExtProcess("chmod a+x " + execFn); 306 } 307 308 /** 309 * 拷贝可执行文件到临时文件夹 310 * 311 * @param path 目标文件路径 312 * @param bs 字节内容 313 * @throws IOException exception 314 */ writeTmpFile(String path, byte[] bs)315 private void writeTmpFile(String path, byte[] bs) throws IOException { 316 File file = new File(path); 317 FileOutputStream fw = null; 318 if (!file.exists()) { 319 boolean isNewFile = file.createNewFile(); 320 if (!isNewFile) { 321 LOG.info("writeTmpFile createNewFile error"); 322 } 323 } 324 try { 325 fw = new FileOutputStream(file); 326 fw.write(bs, 0, bs.length); 327 } catch (IOException e) { 328 // 处理可能发生的IOException 329 LOG.error("Error reading from process streams", e); 330 } finally { 331 try { 332 fw.close(); 333 } catch (IOException e) { 334 LOG.error("Error closing stdInput", e); 335 } 336 } 337 } 338 339 /** 340 * 获取生成成功结果文件。 341 * 342 * @param process 进程ID 343 */ genResultLog(Process process)344 private void genResultLog(Process process) { 345 BufferedReader stdInput = null; 346 BufferedReader stdError = null; 347 try { 348 stdInput = new BufferedReader(new InputStreamReader(process.getInputStream(), 349 StandardCharsets.UTF_8)); 350 stdError = new BufferedReader(new InputStreamReader(process.getErrorStream(), 351 StandardCharsets.UTF_8)); 352 String sErr; 353 String sOut; 354 sErr = getErrorResult(stdError); 355 if (TextUtils.isEmpty(sErr)) { 356 sOut = genInputLog(stdInput); 357 if (!generateIsSuccess(sOut)) { 358 sErrorMessage = sOut; 359 } 360 } else { 361 generateSuccess = false; 362 sErrorMessage = sErr; 363 } 364 } catch (IOException e) { 365 // 处理可能发生的IOException 366 LOG.error("Error in genResultLog", e); 367 } finally { 368 // 确保BufferedReader对象被关闭 369 try { 370 stdInput.close(); 371 } catch (IOException e) { 372 LOG.error("Error closing stdInput", e); // 记录关闭stdInput时的错误 373 } 374 try { 375 stdError.close(); 376 } catch (IOException e) { 377 LOG.error("Error closing stdError", e); // 记录关闭stdError时的错误 378 } 379 } 380 } 381 382 /** 383 * 获取生成失败结果文件。 384 * 385 * @param stdError error buff 386 * @return ErrorResult 387 */ getErrorResult(BufferedReader stdError)388 private String getErrorResult(BufferedReader stdError) { 389 StringBuilder sErr = new StringBuilder(); 390 while (true) { 391 String sTmp; 392 try { 393 if ((sTmp = stdError.readLine()) == null) { 394 break; 395 } 396 sErr.append(sTmp).append(FileUtil.getNewline()); 397 } catch (IOException ioException) { 398 LOG.error(" genResultLog stdInput error" + ioException); 399 } 400 } 401 return sErr.toString(); 402 } 403 generateIsSuccess(String sOut)404 private boolean generateIsSuccess(String sOut) { 405 generateSuccess = sOut.contains("success") || TextUtils.isEmpty(sOut); 406 return generateSuccess; 407 } 408 409 /** 410 * 获取生成文本内容。 411 * 412 * @param stdInput input buff 413 * @return 返回当前输入框内容 414 */ genInputLog(BufferedReader stdInput)415 private String genInputLog(BufferedReader stdInput) { 416 StringBuilder sOut = new StringBuilder(); 417 while (true) { 418 String sTmp; 419 try { 420 if ((sTmp = stdInput.readLine()) == null) { 421 break; 422 } 423 sOut.append(sTmp).append(FileUtil.getNewline()); 424 } catch (IOException ioException) { 425 LOG.error(" genResultLog stdInput error" + ioException); 426 } 427 } 428 return sOut.toString(); 429 } 430 431 static class StreamConsumer extends Thread { 432 InputStream is; 433 StreamConsumer(InputStream is)434 StreamConsumer(InputStream is) { 435 super.setName("StreamConsumer"); 436 this.is = is; 437 } 438 439 @Override run()440 public void run() { 441 InputStreamReader inputStreamReader = null; 442 BufferedReader bufferedReader = null; 443 try { 444 inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8); 445 bufferedReader = new BufferedReader(inputStreamReader); 446 String readLine; 447 while ((readLine = bufferedReader.readLine()) != null) { 448 LOG.error("StreamConsumer" + readLine); 449 } 450 } catch (IOException ioException) { 451 LOG.error("StreamConsumer io error" + ioException); 452 } finally { 453 // 确保BufferedReader和InputStreamReader被关闭 454 try { 455 bufferedReader.close(); 456 } catch (IOException e) { 457 LOG.error("Error closing BufferedReader", e); 458 } 459 try { 460 inputStreamReader.close(); 461 } catch (IOException e) { 462 LOG.error("Error closing inputStreamReader", e); 463 } 464 } 465 } 466 } 467 468 /** 469 * 获取指定输出目录下的文件列表 470 * 471 * @param outPath 输出目录 472 * @return 文件信息列表 473 */ getFileInfoList(File outPath)474 public List<FileInfo> getFileInfoList(File outPath) { 475 List<FileInfo> fileInfoList = new ArrayList<>(); 476 File[] files = outPath.listFiles(); 477 for (File file : files) { 478 fileInfoList.add(new FileInfo(file)); 479 } 480 return fileInfoList; 481 } 482 483 /** 484 * 执行主程序入口 485 * 486 * @return 执行状态 487 */ runFunH2ts()488 public boolean runFunH2ts() { 489 GenNotification.notifyMessage(this.project, "", "Generating Ts", NotificationType.INFORMATION); 490 copyFileToLocalPath("header_parser"); 491 String command; 492 command = genCommandH2ts(); 493 494 File outPath = new File(textFieldSelectOutPath.getText()); 495 List<FileInfo> oldFileList = getFileInfoList(outPath); 496 try { 497 if (!TextUtils.isEmpty(command) && callExtProcess(command)) { 498 List<FileInfo> newFileList = getFileInfoList(outPath); 499 newFileList.removeAll(oldFileList); // 对比命令执行前后的文件列表差异,得到新生成的文件列表 500 GenNotification.notifyGenResult(project, newFileList, "Generate Ts Successfully", 501 NotificationType.INFORMATION); 502 return true; 503 } 504 } catch (IOException | InterruptedException ex) { 505 GenNotification.notifyMessage(project, textFieldSelectOutPath.getText(), "Command exec error", 506 NotificationType.ERROR); 507 LOG.error(ex); 508 } 509 return false; 510 } 511 512 /** 513 * 生成命令行指令 514 * 515 * @return 返回命令行执行内容 516 */ genCommandH2ts()517 private String genCommandH2ts() { 518 String sysName = System.getProperties().getProperty("os.name").toUpperCase(); 519 String tmpDirFile = System.getProperty("java.io.tmpdir"); 520 if (sysName.contains("WIN")) { 521 copyFileToLocalPath("napi_generator-win"); 522 tmpDirFile += "napi_generator-win.exe"; 523 } else if (sysName.contains("LINUX")) { 524 copyFileToLocalPath("napi_generator-linux"); 525 tmpDirFile += "napi_generator-linux"; 526 } else { 527 copyFileToLocalPath("napi_generator-macos"); 528 tmpDirFile += "napi_generator-macos"; 529 } 530 531 File file = new File(tmpDirFile); 532 String command = file.toString(); 533 String inArgs = genInArgs(textFieldSelectH.getText()); 534 command += inArgs + " -o " + textFieldSelectOutPath.getText() + " -t " + true; 535 return command; 536 } 537 getContentPanel()538 JPanel getContentPanel() { 539 return contentPane; 540 } 541 542 /** 543 * 获取生成的.d.ts文件名 544 * 545 * @return 生成的.d.ts文件名 546 */ getTsFileName()547 public String getTsFileName() { 548 hGenFileName = textFieldSelectH.getText(); 549 tsGenFileName = hGenFileName.substring(0, hGenFileName.lastIndexOf(".")) + ".d.ts"; 550 return tsGenFileName; 551 } 552 553 /** 554 * 获取生成的.d.ts文件输出路径 555 * 556 * @return .d.ts文件输出路径 557 */ getTsOutPath()558 public String getTsOutPath() { 559 tsOutPath = textFieldSelectOutPath.getText(); 560 return tsOutPath; 561 } 562 563 /** 564 * 获取工程 565 * 566 * @return 当前工程 567 */ getProject()568 public Project getProject() { 569 return this.project; 570 } 571 572 } 573