zoukankan      html  css  js  c++  java
  • 从静态代码扫描引擎PMD源码学习-多线程任务模型和File过滤设计

    不知不觉在工作中研究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也很简单,就讲这么多.


     
    转载请注明来源,谢谢
  • 相关阅读:
    BZOJ3149 CTSC2013 复原 搜索
    BZOJ5016 SNOI2017 一个简单的询问 莫队、前缀和、容斥
    THUWC2019-1:Reach out
    Luogu4630 APIO2018 Duathlon 圆方树、树形DP
    Luogu4606 SDOI2018 战略游戏 圆方树、虚树、链并
    BZOJ3720 Gty的妹子树 询问分块、主席树
    CF809E Surprise me! 莫比乌斯反演、虚树
    LOJ2542 PKUWC2018 随机游走 min-max容斥、树上高斯消元、高维前缀和、期望
    LOJ2541 PKUWC2018 猎人杀 期望、容斥、生成函数、分治FFT
    CF797F Mice and Holes 贪心、栈维护DP
  • 原文地址:https://www.cnblogs.com/zhhiyp/p/9348082.html
Copyright © 2011-2022 走看看