View Javadoc
1   package edu.jiangxin.apktoolbox.word.stat;
2   
3   import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
4   import edu.jiangxin.apktoolbox.swing.extend.FileListPanel;
5   import edu.jiangxin.apktoolbox.utils.Constants;
6   import edu.jiangxin.apktoolbox.utils.DateUtils;
7   import edu.jiangxin.apktoolbox.utils.ExcelExporter;
8   import edu.jiangxin.apktoolbox.utils.FileUtils;
9   import edu.jiangxin.apktoolbox.word.WordUtils;
10  
11  import javax.swing.*;
12  import javax.swing.table.DefaultTableModel;
13  import java.awt.event.ActionEvent;
14  import java.awt.event.ActionListener;
15  import java.awt.event.MouseAdapter;
16  import java.awt.event.MouseEvent;
17  import java.io.File;
18  import java.io.Serial;
19  import java.util.*;
20  import java.util.List;
21  import java.util.concurrent.CopyOnWriteArrayList;
22  import java.util.concurrent.ExecutorService;
23  import java.util.concurrent.Executors;
24  import java.util.concurrent.Future;
25  import java.util.concurrent.atomic.AtomicInteger;
26  import java.util.concurrent.atomic.AtomicLong;
27  
28  public class WordStatPanel extends EasyPanel {
29  
30      @Serial
31      private static final long serialVersionUID = 1L;
32  
33      private JTabbedPane tabbedPane;
34  
35      private JPanel mainPanel;
36  
37      private FileListPanel fileListPanel;
38  
39      private JCheckBox isRecursiveSearched;
40  
41      private JPanel resultPanel;
42  
43      private JTable resultTable;
44  
45      private DefaultTableModel resultTableModel;
46  
47      private JLabel statInfoLabel;
48  
49      private JButton statButton;
50      private JButton cancelButton;
51  
52      private JProgressBar progressBar;
53  
54      private transient SearchThread searchThread;
55  
56      private final AtomicLong totalFileSize = new AtomicLong(0);
57  
58      private final AtomicInteger totalPageCount = new AtomicInteger(0);
59  
60      private transient final List<Vector<Object>> resultFileList = new CopyOnWriteArrayList<>();
61  
62      @Override
63      public void initUI() {
64          tabbedPane = new JTabbedPane();
65          add(tabbedPane);
66  
67          createMainPanel();
68          tabbedPane.addTab("Option", null, mainPanel, "Show Stat Options");
69  
70          createResultPanel();
71          tabbedPane.addTab("Result", null, resultPanel, "Show Stat Result");
72      }
73  
74      private void createMainPanel() {
75          mainPanel = new JPanel();
76          mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
77  
78          fileListPanel = new FileListPanel();
79          fileListPanel.initialize();
80  
81          JPanel searchOptionPanel = new JPanel();
82          searchOptionPanel.setLayout(new BoxLayout(searchOptionPanel, BoxLayout.X_AXIS));
83          searchOptionPanel.setBorder(BorderFactory.createTitledBorder("Stat Options"));
84  
85          isRecursiveSearched = new JCheckBox("Recursive");
86          isRecursiveSearched.setSelected(true);
87          searchOptionPanel.add(isRecursiveSearched);
88          searchOptionPanel.add(Box.createHorizontalGlue());
89  
90          JPanel operationPanel = new JPanel();
91          operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
92          operationPanel.setBorder(BorderFactory.createTitledBorder("Operations"));
93  
94          statButton = new JButton("Stat");
95          cancelButton = new JButton("Cancel");
96          cancelButton.setEnabled(false);
97          statButton.addActionListener(new OperationButtonActionListener());
98          cancelButton.addActionListener(new OperationButtonActionListener());
99          operationPanel.add(statButton);
100         operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
101         operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
102         operationPanel.add(cancelButton);
103         operationPanel.add(Box.createHorizontalGlue());
104 
105         progressBar = new JProgressBar();
106         progressBar.setStringPainted(true);
107         progressBar.setString("Ready");
108 
109         mainPanel.add(fileListPanel);
110         mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
111         mainPanel.add(searchOptionPanel);
112         mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
113         mainPanel.add(operationPanel);
114         mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
115         mainPanel.add(progressBar);
116     }
117 
118     private void createResultPanel() {
119         resultPanel = new JPanel();
120         resultPanel.setLayout(new BoxLayout(resultPanel, BoxLayout.Y_AXIS));
121 
122         resultTableModel = new WordFilesTableModel(new Vector<>(), WordFilesConstants.COLUMN_NAMES);
123         resultTable = new JTable(resultTableModel);
124         resultTable.setDefaultRenderer(Vector.class, new WordFilesTableCellRenderer());
125         for (int i = 0; i < resultTable.getColumnCount(); i++) {
126             resultTable.getColumn(resultTable.getColumnName(i)).setCellRenderer(new WordFilesTableCellRenderer());
127         }
128         resultTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
129         resultTable.addMouseListener(new MouseAdapter() {
130             @Override
131             public void mouseReleased(MouseEvent e) {
132                 if (e.isPopupTrigger() && e.getComponent() instanceof JTable) {
133                     JPopupMenu popupmenu = new JPopupMenu();
134                     JMenuItem exportMenuItem = new JMenuItem("导出到 Excel");
135                     exportMenuItem.addActionListener(ev ->
136                         ExcelExporter.export(resultTableModel, "word_stat_export.xlsx", WordStatPanel.this));
137                     popupmenu.add(exportMenuItem);
138                     popupmenu.show(e.getComponent(), e.getX(), e.getY());
139                 }
140             }
141         });
142         JScrollPane scrollPane = new JScrollPane(resultTable);
143 
144         JPanel statInfoPanel = new JPanel();
145         statInfoPanel.setLayout(new BoxLayout(statInfoPanel, BoxLayout.X_AXIS));
146         statInfoLabel = new JLabel("");
147         statInfoPanel.add(statInfoLabel);
148         statInfoPanel.add(Box.createHorizontalGlue());
149 
150         resultPanel.add(scrollPane);
151         resultPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
152         resultPanel.add(statInfoPanel);
153     }
154 
155     private void processFile(File file) {
156         Vector<Object> fileVector = getRowVector(file);
157         resultFileList.add(fileVector);
158     }
159 
160     class OperationButtonActionListener implements ActionListener {
161         @Override
162         public void actionPerformed(ActionEvent e) {
163             Object source = e.getSource();
164             if (source.equals(statButton)) {
165                 if (fileListPanel.getFileList().isEmpty()) {
166                     return;
167                 }
168                 statButton.setEnabled(false);
169                 cancelButton.setEnabled(true);
170                 searchThread = new SearchThread(isRecursiveSearched.isSelected());
171                 searchThread.start();
172             } else if (source.equals(cancelButton)) {
173                 statButton.setEnabled(true);
174                 cancelButton.setEnabled(false);
175                 if (searchThread.isAlive()) {
176                     searchThread.interrupt();
177                     searchThread.executorService.shutdownNow();
178                 }
179             }
180         }
181     }
182 
183     private void showResult() {
184         SwingUtilities.invokeLater(() -> {
185             int index = 0;
186             for (Vector<Object> file : resultFileList) {
187                 file.add(0, ++index);
188                 resultTableModel.addRow(file);
189             }
190             tabbedPane.setSelectedIndex(1);
191             statInfoLabel.setText("Page Count: " + totalPageCount.get() + ", Total Size: " + FileUtils.sizeOfInHumanFormat(totalFileSize.get()));
192         });
193     }
194 
195     private Vector<Object> getRowVector(File file) {
196         Vector<Object> rowData = new Vector<>();
197         rowData.add(file.getParent());
198         rowData.add(file.getName());
199         totalFileSize.addAndGet(file.length());
200         rowData.add(FileUtils.sizeOfInHumanFormat(file));
201         rowData.add(DateUtils.millisecondToHumanFormat(file.lastModified()));
202         int pageCount = WordUtils.getPageCount(file);
203         totalPageCount.addAndGet(pageCount);
204         rowData.add(pageCount);
205         return rowData;
206     }
207 
208     class SearchThread extends Thread {
209         public final ExecutorService executorService;
210         private final AtomicInteger processedFiles = new AtomicInteger(0);
211         private int totalFiles = 0;
212         private final boolean isRecursiveSearched;
213 
214         public SearchThread(boolean isRecursiveSearched) {
215             super();
216             this.isRecursiveSearched = isRecursiveSearched;
217             this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
218 
219             resultFileList.clear();
220             totalFileSize.set(0);
221             totalPageCount.set(0);
222 
223             SwingUtilities.invokeLater(() -> {
224                 progressBar.setValue(0);
225                 progressBar.setString("Starting search...");
226                 resultTableModel.setRowCount(0);
227                 statInfoLabel.setText("");
228             });
229         }
230 
231         @Override
232         public void run() {
233             try {
234                 List<File> fileList = fileListPanel.getFileList();
235                 Set<File> fileSet = new TreeSet<>();
236                 String[] extensions = new String[]{"doc", "DOC", "docx", "DOCX"};
237                 for (File file : fileList) {
238                     fileSet.addAll(FileUtils.listFiles(file, extensions, isRecursiveSearched));
239                 }
240 
241                 List<Future<?>> futures = new ArrayList<>();
242                 totalFiles = fileSet.size();
243                 updateProgress();
244 
245                 for (File file : fileSet) {
246                     if (currentThread().isInterrupted()) {
247                         return;
248                     }
249                     futures.add(executorService.submit(() -> {
250                         if (currentThread().isInterrupted()) {
251                             return null;
252                         }
253                         processFile(file);
254                         incrementProcessedFiles();
255                         return null;
256                     }));
257                 }
258 
259                 // Wait for all tasks to complete
260                 for (Future<?> future : futures) {
261                     try {
262                         future.get();
263                     } catch (InterruptedException e) {
264                         logger.error("Search interrupted", e);
265                         currentThread().interrupt();
266                         return;
267                     }
268                 }
269 
270                 showResult();
271             } catch (Exception e) {
272                 logger.error("Search failed", e);
273                 SwingUtilities.invokeLater(() -> progressBar.setString("Search failed"));
274             } finally {
275                 executorService.shutdown();
276                 SwingUtilities.invokeLater(() -> {
277                     statButton.setEnabled(true);
278                     cancelButton.setEnabled(false);
279                 });
280             }
281         }
282 
283         private void incrementProcessedFiles() {
284             processedFiles.incrementAndGet();
285             updateProgress();
286         }
287 
288         private void updateProgress() {
289             if (totalFiles > 0) {
290                 SwingUtilities.invokeLater(() -> {
291                     int processed = processedFiles.get();
292                     int percentage = (int) (processed * 100.0 / totalFiles);
293                     progressBar.setValue(percentage);
294                     progressBar.setString(String.format("Processing: %d/%d files (%d%%)", processed, totalFiles, percentage));
295                 });
296             }
297         }
298     }
299 }