1 /* 2 * Copyright (c) 2023 Huawei Device 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 16 package com.update.check.action.view; 17 18 import com.update.check.dto.UpdateCheckReportDto; 19 import com.update.check.action.DataUpdateNotifier; 20 import com.update.check.dto.ApiDiffResultDto; 21 import com.update.check.log.Logger; 22 import com.update.check.utils.FileUtils; 23 import com.intellij.openapi.Disposable; 24 import com.intellij.openapi.editor.Editor; 25 import com.intellij.openapi.editor.CaretModel; 26 import com.intellij.openapi.editor.LogicalPosition; 27 import com.intellij.openapi.editor.ScrollType; 28 import com.intellij.openapi.editor.ScrollingModel; 29 import com.intellij.openapi.fileEditor.FileEditorManager; 30 import com.intellij.openapi.project.Project; 31 import com.intellij.openapi.util.Disposer; 32 import com.intellij.openapi.vfs.LocalFileSystem; 33 import com.intellij.openapi.vfs.VirtualFile; 34 import com.intellij.openapi.wm.ToolWindow; 35 import com.intellij.ui.content.Content; 36 import com.intellij.ui.content.ContentManager; 37 import org.apache.commons.lang.StringUtils; 38 import org.jetbrains.annotations.NotNull; 39 40 import javax.swing.JPanel; 41 import javax.swing.JButton; 42 import javax.swing.JTable; 43 import javax.swing.JComponent; 44 import javax.swing.JLabel; 45 import javax.swing.table.TableColumn; 46 import javax.swing.table.AbstractTableModel; 47 import java.awt.Desktop; 48 import java.awt.event.ActionListener; 49 import java.awt.event.ActionEvent; 50 import java.awt.event.MouseAdapter; 51 import java.awt.event.MouseEvent; 52 import java.io.File; 53 import java.io.IOException; 54 import java.net.URI; 55 import java.net.URISyntaxException; 56 import java.util.HashMap; 57 import java.util.Map; 58 import java.util.List; 59 import java.util.LinkedHashMap; 60 import java.util.ArrayList; 61 import java.util.concurrent.ConcurrentHashMap; 62 import java.util.function.Function; 63 import java.util.function.Predicate; 64 import java.util.regex.Matcher; 65 import java.util.regex.Pattern; 66 import java.util.stream.Collectors; 67 68 /** 69 * UpdateReportPanel 70 * 71 * @since 23-04-07 72 */ 73 public class UpdateReportPanel implements Disposable { 74 private static final Logger LOGGER = Logger.createLogger(); 75 76 private static final String LOG_TAG = UpdateReportPanel.class.getName(); 77 78 private JPanel rootPanel; 79 80 private JTable updateReport; 81 82 private JLabel sumLabel; 83 84 private JButton chooseTypeButton; 85 86 private Project project; 87 88 private Map<String, String> changeLogs = new HashMap<>(); 89 90 private List<UpdateCheckReportDto> reportDtos = new ArrayList<>(); 91 92 private LinkedHashMap<String, Boolean> arrayTypes = new LinkedHashMap<>(); 93 94 /** 95 * UpdateReportPanel 96 * 97 * @param project project 98 */ UpdateReportPanel(Project project)99 public UpdateReportPanel(Project project) { 100 this.project = project; 101 Report report = new Report(); 102 this.updateReport.setModel(report); 103 this.setTableStyle(); 104 } 105 106 @Override dispose()107 public void dispose() { 108 109 } 110 111 /** 112 * loadPanel 113 * 114 * @param project project 115 * @param toolWindow toolWindow 116 */ loadPanel(@otNull Project project, @NotNull ToolWindow toolWindow)117 public static void loadPanel(@NotNull Project project, @NotNull ToolWindow toolWindow) { 118 UpdateReportPanel updateReportPanel = new UpdateReportPanel(project); 119 ContentManager contentManager = toolWindow.getContentManager(); 120 Content content = contentManager.getFactory().createContent( 121 updateReportPanel.getPanel(), "Report", false); 122 contentManager.addContent(content); 123 Disposer.register(project, updateReportPanel); 124 } 125 126 /** 127 * getPanel 128 * 129 * @return rootPanel 130 */ getPanel()131 public JComponent getPanel() { 132 return this.rootPanel; 133 } 134 filterType()135 private void filterType() { 136 this.updateChooseType(); 137 this.setUpdateReportStyle(); 138 } 139 updateChooseType()140 private void updateChooseType() { 141 List<UpdateCheckReportDto> typeDto = reportDtos.stream().distinct() 142 .filter(distinctByKey(UpdateCheckReportDto::getChangeType)) 143 .collect(Collectors.toList()); 144 arrayTypes.clear(); 145 arrayTypes.put(ConstString.get("check.choose.all"), true); 146 if (typeDto.size() == 0) { 147 return; 148 } 149 for (UpdateCheckReportDto dto : typeDto) { 150 if (dto == null) { 151 break; 152 } 153 arrayTypes.put(dto.getChangeType(), true); 154 } 155 } 156 setTableStyle()157 private void setTableStyle() { 158 LOGGER.info(LOG_TAG, "Start rendering JTable"); 159 this.filterType(); 160 this.chooseTypeButton.setText(ConstString.get("check.choose.type")); 161 this.chooseTypeButton.addActionListener(new ActionListener() { 162 @Override 163 public void actionPerformed(ActionEvent e) { 164 ShowTypeDialog.showDialog(project, arrayTypes); 165 } 166 }); 167 LOGGER.info(LOG_TAG, "Rendering JTable end"); 168 } 169 setUpdateReportStyle()170 private void setUpdateReportStyle() { 171 172 // Set Table Row Height 173 this.updateReport.setRowHeight(30); 174 175 // add addMouseListener 176 this.updateReport.setRowSelectionAllowed(true); 177 this.updateReport.setColumnSelectionAllowed(true); 178 this.setUpdateStyle(); 179 this.setListenerToReportPanel(); 180 } 181 setUpdateStyle()182 private void setUpdateStyle() { 183 TableColumn tc = this.updateReport.getColumnModel().getColumn(0); 184 tc.setCellEditor(this.updateReport.getDefaultEditor(Boolean.class)); 185 tc.setCellRenderer(this.updateReport.getDefaultRenderer(Boolean.class)); 186 tc.setPreferredWidth(100); 187 tc.setMaxWidth(100); 188 tc.setMinWidth(100); 189 TableColumn tableColumn = this.updateReport.getColumnModel().getColumn(5); 190 tableColumn.setPreferredWidth(200); 191 tableColumn.setMaxWidth(200); 192 tableColumn.setMinWidth(200); 193 } 194 setListenerToReportPanel()195 private void setListenerToReportPanel() { 196 this.updateReport.addMouseListener(new MouseAdapter() { 197 @Override 198 public void mouseClicked(MouseEvent e) { 199 int col = updateReport.columnAtPoint(e.getPoint()); 200 int row = updateReport.getSelectedRow(); 201 202 // api in simple address 203 if (col == 4 && e.getClickCount() == 2) { 204 Object valueAt1 = updateReport.getValueAt(row, 4); 205 String valueAt = null; 206 if (valueAt1 instanceof String) { 207 valueAt = (String) valueAt1; 208 } 209 if (StringUtils.isBlank(valueAt)) { 210 return; 211 } 212 if (getApiInApplicationPath(valueAt) == null) { 213 return; 214 } 215 try { 216 VirtualFile fileByIoFile = LocalFileSystem 217 .getInstance() 218 .findFileByIoFile(new File(project.getBasePath(), 219 getApiInApplicationPath(valueAt))); 220 FileEditorManager.getInstance(project).openFile(fileByIoFile, true); 221 gotoLine(Integer.parseInt(valueAt.replaceAll(ConstString.get("check.replace"), "$1"))); 222 } catch (IllegalArgumentException exception) { 223 MessageBox.showDialog(project, "", ConstString.get("can.not.find.file")); 224 } 225 } else if (col == 5) { 226 Object childValue = updateReport.getValueAt(row, 5); 227 String valueAt = null; 228 if (childValue instanceof String) { 229 valueAt = (String) childValue; 230 } 231 if (StringUtils.isBlank(valueAt)) { 232 return; 233 } 234 if (valueAt.split(",").length == 1) { 235 clickUrl(changeLogs.get(valueAt)); 236 return; 237 } 238 ShowChangeLogsDialog.showDialog(project, changeLogs, valueAt); 239 } 240 } 241 }); 242 } 243 getApiInApplicationPath(String apiAddress)244 private String getApiInApplicationPath(String apiAddress) { 245 String pattern = ConstString.get("check.report.pattern"); 246 Pattern r = Pattern.compile(pattern); 247 Matcher m = r.matcher(apiAddress); 248 if (m.find()) { 249 return m.group(0); 250 } 251 return null; 252 } 253 clickUrl(String url)254 private void clickUrl(String url) { 255 try { 256 if (StringUtils.isNotBlank(url)) { 257 Desktop desktop = Desktop.getDesktop(); 258 URI uri = new URI(url); 259 desktop.browse(uri); 260 } 261 } catch (URISyntaxException | IOException exception) { 262 exception.printStackTrace(); 263 LOGGER.error(LOG_TAG, "Click " + url + " error! " + exception.getMessage()); 264 } 265 } 266 gotoLine(int lineNumber)267 private void gotoLine(int lineNumber) { 268 Editor editor = FileEditorManager.getInstance(this.project).getSelectedTextEditor(); 269 if (editor == null) { 270 return; 271 } 272 CaretModel caretModel = editor.getCaretModel(); 273 int totalLineCount = editor.getDocument().getLineCount(); 274 if (lineNumber > totalLineCount) { 275 return; 276 } 277 278 // Moving caret to line number 279 caretModel.moveToLogicalPosition(new LogicalPosition(lineNumber - 1, 0)); 280 281 // Scroll to the caret 282 ScrollingModel scrollingModel = editor.getScrollingModel(); 283 scrollingModel.scrollToCaret(ScrollType.CENTER); 284 } 285 286 class Report extends AbstractTableModel implements DataUpdateNotifier.UpdateListener { 287 List<Object[]> dataList = new ArrayList<>(); 288 String[] titles = new String[7]; 289 int sum = 0; 290 291 /** 292 * Report 293 */ Report()294 public Report() { 295 LOGGER.info(LOG_TAG, "Start loading report window"); 296 DataUpdateNotifier.getInstance().addUpdateListener(this); 297 this.getReportResult(); 298 this.initReport(new LinkedHashMap<>()); 299 LOGGER.info(LOG_TAG, "Loading report window end"); 300 } 301 getReportResult()302 private void getReportResult() { 303 try { 304 File resultJsonFile = new File(project.getBasePath(), 305 ConstString.get("check.report.json")); 306 List<UpdateCheckReportDto> updateCheckReportDtos = 307 FileUtils.readJsonFileToJavaList(resultJsonFile.toString(), 308 UpdateCheckReportDto.class); 309 reportDtos = updateCheckReportDtos; 310 LOGGER.info(LOG_TAG, "Report size:" + updateCheckReportDtos.size()); 311 } catch (IOException e) { 312 LOGGER.error(LOG_TAG, e.getMessage()); 313 } 314 } 315 screenResult(LinkedHashMap<String, Boolean> changeTypes)316 private List<UpdateCheckReportDto> screenResult(LinkedHashMap<String, Boolean> changeTypes) { 317 if (changeTypes.size() == 0) { 318 return reportDtos; 319 } 320 List<String> chooseTypes = new ArrayList<>(); 321 for (Map.Entry<String, Boolean> entry : changeTypes.entrySet()) { 322 if (entry.getValue()) { 323 chooseTypes.add(entry.getKey()); 324 } 325 } 326 return this.multipleChoice(chooseTypes); 327 } 328 multipleChoice(List<String> chooseTypes)329 private List<UpdateCheckReportDto> multipleChoice(List<String> chooseTypes) { 330 List<UpdateCheckReportDto> multipleChoice = new ArrayList<>(); 331 for (String changeType : chooseTypes) { 332 for (UpdateCheckReportDto dto : reportDtos) { 333 if (dto != null && dto.getChangeType().equals(changeType)) { 334 multipleChoice.add(dto); 335 } 336 } 337 } 338 return multipleChoice; 339 } 340 update(LinkedHashMap<String, Boolean> chooseType, String type)341 private void update(LinkedHashMap<String, Boolean> chooseType, String type) { 342 LOGGER.info(LOG_TAG, "Start reload report window"); 343 dataList.clear(); 344 if ("executeAgain".equals(type)) { 345 this.getReportResult(); 346 updateChooseType(); 347 } else { 348 arrayTypes = chooseType; 349 } 350 this.initReport(chooseType); 351 fireTableDataChanged(); 352 LOGGER.info(LOG_TAG, "Reload report window end"); 353 } 354 initReport(LinkedHashMap<String, Boolean> changeTypes)355 private void initReport(LinkedHashMap<String, Boolean> changeTypes) { 356 List<UpdateCheckReportDto> reportResult = this.screenResult(changeTypes); 357 if (reportResult == null) { 358 return; 359 } 360 sum = reportResult.size(); 361 sumLabel.setText(ConstString.get("sum.report") + sum); 362 for (int i = 0; i < reportResult.size(); i++) { 363 UpdateCheckReportDto report = reportResult.get(i); 364 String filePath = report 365 .getSourceFileName() 366 .replace(project.getBasePath() + "/", ""); 367 dataList.add(new Object[]{false 368 , ConstString.get("not.involved") 369 .equals(report.getSourceFileName()) 370 ? report.getSourceFileName() 371 : getFileName(report.getSourceFileName()) 372 , report.getApiDefinition(), report.getReminderInformation() 373 , ConstString.get("not.involved").equals(report.getPos()) 374 ? report.getPos() : filePath + "(" + report.getPos() + ")" 375 , this.setChangeLogUrl(report.getChangelogs())}); 376 } 377 titles[0] = ConstString.get("check.report.order.number"); 378 titles[1] = ConstString.get("check.report.ets.name"); 379 titles[2] = ConstString.get("report.old.method.text"); 380 titles[3] = ConstString.get("report.new.method.text"); 381 titles[4] = ConstString.get("report.function.position"); 382 titles[5] = ConstString.get("report.change.log"); 383 } 384 setChangeLogUrl(List<ApiDiffResultDto.Changelog> changelogs)385 private String setChangeLogUrl(List<ApiDiffResultDto.Changelog> changelogs) { 386 if (changelogs == null) { 387 return ""; 388 } 389 StringBuilder sb = new StringBuilder(); 390 for (ApiDiffResultDto.Changelog changelog : changelogs) { 391 if (changelog == null) { 392 continue; 393 } 394 changeLogs.put(changelog.getVersion(), changelog.getUrl()); 395 sb.append(changelog.getVersion() + ","); 396 } 397 return sb.toString().replaceFirst(",$", ""); 398 } 399 getFileName(String filePath)400 private String getFileName(String filePath) { 401 File file = new File(filePath); 402 if (file.isFile()) { 403 return file.getName(); 404 } 405 return ""; 406 } 407 408 @Override isCellEditable(int row, int col)409 public boolean isCellEditable(int row, int col) { 410 return col == 0; 411 } 412 413 @Override getColumnName(int column)414 public String getColumnName(int column) { 415 return titles[column]; 416 } 417 418 @Override getRowCount()419 public int getRowCount() { 420 return sum; 421 } 422 423 @Override getColumnCount()424 public int getColumnCount() { 425 return 6; 426 } 427 428 @Override getValueAt(int rowIndex, int columnIndex)429 public Object getValueAt(int rowIndex, int columnIndex) { 430 return this.dataList.get(rowIndex)[columnIndex]; 431 } 432 433 @Override setValueAt(Object aValue, int rowIndex, int columnIndex)434 public void setValueAt(Object aValue, int rowIndex, int columnIndex) { 435 this.dataList.get(rowIndex)[columnIndex] = aValue; 436 } 437 438 @Override onUpdate(LinkedHashMap<String, Boolean> chooseType, String type)439 public void onUpdate(LinkedHashMap<String, Boolean> chooseType, String type) { 440 update(chooseType, type); 441 } 442 } 443 distinctByKey(Function<? super T, Object> keyExtractor)444 private <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) { 445 Map<Object, Boolean> seen = new ConcurrentHashMap<>(); 446 return object -> seen.putIfAbsent(keyExtractor.apply(object), Boolean.TRUE) == null; 447 } 448 449 } 450