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 if (!file.exists()) { 318 boolean isNewFile = file.createNewFile(); 319 if (!isNewFile) { 320 LOG.info("writeTmpFile createNewFile error"); 321 } 322 } 323 FileOutputStream fw = new FileOutputStream(file); 324 fw.write(bs, 0, bs.length); 325 fw.close(); 326 } 327 328 /** 329 * 获取生成成功结果文件。 330 * 331 * @param process 进程ID 332 */ genResultLog(Process process)333 private void genResultLog(Process process) { 334 BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); 335 BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream())); 336 String sErr; 337 String sOut; 338 sErr = getErrorResult(stdError); 339 if (TextUtils.isEmpty(sErr)) { 340 sOut = genInputLog(stdInput); 341 if (!generateIsSuccess(sOut)) { 342 sErrorMessage = sOut; 343 } 344 return; 345 } 346 generateSuccess = false; 347 sErrorMessage = sErr; 348 } 349 350 /** 351 * 获取生成失败结果文件。 352 * 353 * @param stdError error buff 354 * @return ErrorResult 355 */ getErrorResult(BufferedReader stdError)356 private String getErrorResult(BufferedReader stdError) { 357 StringBuilder sErr = new StringBuilder(); 358 while (true) { 359 String sTmp; 360 try { 361 if ((sTmp = stdError.readLine()) == null) { 362 break; 363 } 364 sErr.append(sTmp).append(FileUtil.getNewline()); 365 } catch (IOException ioException) { 366 LOG.error(" genResultLog stdInput error" + ioException); 367 } 368 } 369 return sErr.toString(); 370 } 371 generateIsSuccess(String sOut)372 private boolean generateIsSuccess(String sOut) { 373 generateSuccess = sOut.contains("success") || TextUtils.isEmpty(sOut); 374 return generateSuccess; 375 } 376 377 /** 378 * 获取生成文本内容。 379 * 380 * @param stdInput input buff 381 * @return 返回当前输入框内容 382 */ genInputLog(BufferedReader stdInput)383 private String genInputLog(BufferedReader stdInput) { 384 StringBuilder sOut = new StringBuilder(); 385 while (true) { 386 String sTmp; 387 try { 388 if ((sTmp = stdInput.readLine()) == null) { 389 break; 390 } 391 sOut.append(sTmp).append(FileUtil.getNewline()); 392 } catch (IOException ioException) { 393 LOG.error(" genResultLog stdInput error" + ioException); 394 } 395 } 396 return sOut.toString(); 397 } 398 399 static class StreamConsumer extends Thread { 400 InputStream is; 401 StreamConsumer(InputStream is)402 StreamConsumer(InputStream is) { 403 super.setName("StreamConsumer"); 404 this.is = is; 405 } 406 407 @Override run()408 public void run() { 409 try { 410 InputStreamReader inputStreamReader = new InputStreamReader(is); 411 BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 412 String readLine; 413 while ((readLine = bufferedReader.readLine()) != null) { 414 LOG.error("StreamConsumer" + readLine); 415 } 416 } catch (IOException ioException) { 417 LOG.error("StreamConsumer io error" + ioException); 418 } 419 } 420 } 421 422 /** 423 * 获取指定输出目录下的文件列表 424 * 425 * @param outPath 输出目录 426 * @return 文件信息列表 427 */ getFileInfoList(File outPath)428 public List<FileInfo> getFileInfoList(File outPath) { 429 List<FileInfo> fileInfoList = new ArrayList<>(); 430 File[] files = outPath.listFiles(); 431 for (File file : files) { 432 fileInfoList.add(new FileInfo(file)); 433 } 434 return fileInfoList; 435 } 436 437 /** 438 * 执行主程序入口 439 * 440 * @return 执行状态 441 */ runFunH2ts()442 public boolean runFunH2ts() { 443 GenNotification.notifyMessage(this.project, "", "Generating Ts", NotificationType.INFORMATION); 444 copyFileToLocalPath("header_parser"); 445 String command; 446 command = genCommandH2ts(); 447 448 File outPath = new File(textFieldSelectOutPath.getText()); 449 List<FileInfo> oldFileList = getFileInfoList(outPath); 450 try { 451 if (!TextUtils.isEmpty(command) && callExtProcess(command)) { 452 List<FileInfo> newFileList = getFileInfoList(outPath); 453 newFileList.removeAll(oldFileList); // 对比命令执行前后的文件列表差异,得到新生成的文件列表 454 GenNotification.notifyGenResult(project, newFileList, "Generate Ts Successfully", 455 NotificationType.INFORMATION); 456 return true; 457 } 458 } catch (IOException | InterruptedException ex) { 459 GenNotification.notifyMessage(project, textFieldSelectOutPath.getText(), "Command exec error", 460 NotificationType.ERROR); 461 LOG.error(ex); 462 } 463 return false; 464 } 465 466 /** 467 * 生成命令行指令 468 * 469 * @return 返回命令行执行内容 470 */ genCommandH2ts()471 private String genCommandH2ts() { 472 String sysName = System.getProperties().getProperty("os.name").toUpperCase(); 473 String tmpDirFile = System.getProperty("java.io.tmpdir"); 474 if (sysName.contains("WIN")) { 475 copyFileToLocalPath("napi_generator-win"); 476 tmpDirFile += "napi_generator-win.exe"; 477 } else if (sysName.contains("LINUX")) { 478 copyFileToLocalPath("napi_generator-linux"); 479 tmpDirFile += "napi_generator-linux"; 480 } else { 481 copyFileToLocalPath("napi_generator-macos"); 482 tmpDirFile += "napi_generator-macos"; 483 } 484 485 File file = new File(tmpDirFile); 486 String command = file.toString(); 487 String inArgs = genInArgs(textFieldSelectH.getText()); 488 command += inArgs + " -o " + textFieldSelectOutPath.getText() + " -t " + true; 489 return command; 490 } 491 getContentPanel()492 JPanel getContentPanel() { 493 return contentPane; 494 } 495 496 /** 497 * 获取生成的.d.ts文件名 498 * 499 * @return 生成的.d.ts文件名 500 */ getTsFileName()501 public String getTsFileName() { 502 hGenFileName = textFieldSelectH.getText(); 503 tsGenFileName = hGenFileName.substring(0, hGenFileName.lastIndexOf(".")) + ".d.ts"; 504 return tsGenFileName; 505 } 506 507 /** 508 * 获取生成的.d.ts文件输出路径 509 * 510 * @return .d.ts文件输出路径 511 */ getTsOutPath()512 public String getTsOutPath() { 513 tsOutPath = textFieldSelectOutPath.getText(); 514 return tsOutPath; 515 } 516 517 /** 518 * 获取工程 519 * 520 * @return 当前工程 521 */ getProject()522 public Project getProject() { 523 return this.project; 524 } 525 526 } 527