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