1. 程式人生 > >從靜態代碼掃描引擎PMD源碼學習-多線程任務模型和File過濾設計

從靜態代碼掃描引擎PMD源碼學習-多線程任務模型和File過濾設計

reg 進行 som pri lock erro void throw ren

不知不覺在工作中研究PMD並定制規則已經4個月左右了。其實PMD有許多值得我學習的源碼,不過出於時間並不曾動筆。今天簡單記錄總結一下PMD的多線程和File過濾設計的源碼。

 1 public class MultiThreadProcessor extends AbstractPMDProcessor {
 2 
 3     private ExecutorService executor;
 4     private CompletionService<Report> completionService;
 5     private List<Future<Report>> tasks = new
ArrayList<>(); 6 7 public MultiThreadProcessor(final PMDConfiguration configuration) { 8 super(configuration); 9 10 executor = Executors.newFixedThreadPool(configuration.getThreads(), new PmdThreadFactory()); 11 completionService = new ExecutorCompletionService<>(executor);
12 } 13 14 @Override 15 protected void runAnalysis(PmdRunnable runnable) { 16 // multi-threaded execution, dispatch analysis to worker threads 17 tasks.add(completionService.submit(runnable)); 18 } 19 20 @Override 21 protected void collectReports(List<Renderer> renderers) {
22 // Collect result analysis, waiting for termination if needed 23 try { 24 for (int i = 0; i < tasks.size(); i++) { 25 final Report report = completionService.take().get(); 26 super.renderReports(renderers, report); 27 } 28 } catch (InterruptedException ie) { 29 Thread.currentThread().interrupt(); 30 } catch (ExecutionException ee) { 31 Throwable t = ee.getCause(); 32 if (t instanceof RuntimeException) { 33 throw (RuntimeException) t; 34 } else if (t instanceof Error) { 35 throw (Error) t; 36 } else { 37 throw new IllegalStateException("PmdRunnable exception", t); 38 } 39 } finally { 40 executor.shutdownNow(); 41 } 42 } 43 }

這是MultiThreadProcessor,多線程執行類。

關鍵類是CompletionService,PMD使用實現了Callable接口的PMDRunnable類來封裝線程的具體運行,在這個類的call方法裏進行重寫。今天的核心不是call裏的代碼,核心是這種任務式提交的多線程操作的常見寫法。也就是結合CompletionService。

我們知道,如果實現了Callable接口,那麽使用者是想獲得結果的,而結果是Future對象,PMD代碼中的Report就是call的返回,使用Future來泛型封裝。

CompletionService內部維護一個BlockQuene隊列,通過ExecutorCompletionService<>(executor)來傳入一個executor,也就是一個線程池。

這裏使用jdk自帶的Executors.newFixedThreadPool來創建固定大小的線程池,也就是上限啦。

如果自己使用callable接口來向池子submit,返回future需要自己管理,而CompletionService則可以完美管理,應當每次都使用該類。

通過completionService.submit(runnable),提交callable實例,獲得Future對象,我們通過一個集合List存儲,但實際上我們後續並不真正要遍歷使用每一個Future。

註意,submit是讓線程開始啟動。

線程運行後,如何拿結果?

使用completionService.take().get();獲得call方法最終返回的結果類對象。
completionService.take就是獲得Future對象,但這裏已經是異步操作.
通過submit啟動了一個線程,take操作會獲得先運行完的線程的結果Future實例,這是這個服務類內部代碼所實現的.
通過get獲得Future封裝的對象report
至此,一連串標準操作,非常舒服.
---------------------------------------------------------------
接下來是,文件的Filter篇:
最近在給PMD增加exclude的功能.發現他的FileUtil類裏寫了以後要做exclude功能的註釋...結果6.x版本也還沒實現啊..然後我做了半天左右實現好了.(參數傳入)

實際上PMD的文件過濾是基於Filter實現,也和FilenameFilter有關.
核心來自於jdk的File.list(FilenameFilter filter)
 1 public static List<DataSource> collectFiles(String fileLocations, FilenameFilter filenameFilter) {
 2         List<DataSource> dataSources = new ArrayList<>();
 3         for (String fileLocation : fileLocations.split(",")) {
 4             collect(dataSources, fileLocation, filenameFilter);
 5         }
 6         return dataSources;
 7     }
 8 
 9     private static List<DataSource> collect(List<DataSource> dataSources, String fileLocation,
10             FilenameFilter filenameFilter) {
11         File file = new File(fileLocation);
12         if (!file.exists()) {
13             throw new RuntimeException("File " + file.getName() + " doesn‘t exist");
14         }
15         if (!file.isDirectory()) {
16             if (fileLocation.endsWith(".zip") || fileLocation.endsWith(".jar")) {
17                 ZipFile zipFile;
18                 try {
19                     zipFile = new ZipFile(fileLocation);
20                     Enumeration<? extends ZipEntry> e = zipFile.entries();
21                     while (e.hasMoreElements()) {
22                         ZipEntry zipEntry = e.nextElement();
23                         if (filenameFilter.accept(null, zipEntry.getName())) {
24                             dataSources.add(new ZipDataSource(zipFile, zipEntry));
25                         }
26                     }
27                 } catch (IOException ze) {
28                     throw new RuntimeException("Archive file " + file.getName() + " can‘t be opened");
29                 }
30             } else {
31                 dataSources.add(new FileDataSource(file));
32             }
33         } else {
34             // Match files, or directories which are not excluded.
35             // FUTURE Make the excluded directories be some configurable option
36             Filter<File> filter = new OrFilter<>(Filters.toFileFilter(filenameFilter),
37                     new AndFilter<>(Filters.getDirectoryFilter(), Filters.toNormalizedFileFilter(
38                             Filters.buildRegexFilterExcludeOverInclude(null, Collections.singletonList("SCCS")))));
39             FileFinder finder = new FileFinder();
40             List<File> files = finder.findFilesFrom(file, Filters.toFilenameFilter(filter), true);
41             for (File f : files) {
42                 dataSources.add(new FileDataSource(f));
43             }
44         }
45         return dataSources;
46     }

這是PMD來collect的核心代碼.dataSource其實就是一個File再次封裝的類.

collectFiles這個方法就是,傳入source路徑,一個目錄,一個文件路徑皆可.
再傳入一個過濾類FilenameFilter,在PMD當前實現裏是直接傳入基於Language代碼語言來做的後綴擴展名extension過濾FilenameFilter,
最後是傳入一個集合,就是做封裝的盒子.
因為路徑參數是逗號分隔的,所以先做遍歷,然後對每個路徑進行調用collect方法.
接下來是核心,collect傳入一個FilenameFilter,然後過濾這個路徑下的File.
其實也是簡單的實現.
然後再回到,如果我想增加一個過濾目錄的功能怎麽辦?看似我可以在主代碼裏傳入一個exclude的FilenameFilter,實際上它這個collect返回了dataSource,很不利於再次分析.
也就是說,這塊代碼其實沒什麽向後兼容的可能了.
既然PMD的作者,寫死了,那我也就稍做修改.
Filter的精髓其實是OrFilter和AndFilter這種類,理解也很簡單,一個OrFilter裏維護一個Filter的List,只要有一個返回true,那就返回true
AndFilter裏維護著Filter的List,全部返回true才會返回true.我記得apache commons裏也有類似的io的file的Filter類.
以後如果想自己做這種功能,也可以用apache的包.
其實Filter也很簡單,就講這麽多.


 

從靜態代碼掃描引擎PMD源碼學習-多線程任務模型和File過濾設計