【问题标题】:How can I identify references to Java classes using Perl?如何使用 Perl 识别对 Java 类的引用?
【发布时间】:2010-09-13 00:08:47
【问题描述】:

我正在编写一个 Perl 脚本,我已经到了需要逐行解析 Java 源文件以检查对完全限定 Java 类名的引用的地步。我预先知道我正在寻找的课程;也是正在搜索的源文件的完全限定名称(基于其路径)。

例如,在 com/bob/is/YourUncle.java 文件中查找对 foo.bar.Baz 的所有有效引用。

目前我能想到的需要考虑的情况是:

  1. 被解析的文件与搜索类在同一个包中。

    在 foo/bar/Boing.java 中查找 foo.bar.Baz 引用

  2. 它应该忽略 cmets。

    // this is a comment saying this method returns a foo.bar.Baz or Baz instance 
    // it shouldn't count
    
    /*   a multiline comment as well
     this shouldn't count
     if I put foo.bar.Baz or Baz in here either */
    
  3. 内嵌完全限定引用。

    foo.bar.Baz fb = new foo.bar.Baz();
    
  4. 基于导入语句的引用。

    import foo.bar.Baz;
    ...
    Baz b = new Baz();
    

在 Perl 5.8 中最有效的方法是什么?也许是一些花哨的正则表达式?

open F, $File::Find::name or die;
# these three things are already known
# $classToFind    looking for references of this class
# $pkgToFind      the package of the class you're finding references of
# $currentPkg     package name of the file being parsed
while(<F>){
  # ... do work here   
}
close F;
# the results are availble here in some form

【问题讨论】:

    标签: java perl parsing


    【解决方案1】:

    你还需要跳过带引号的字符串(如果你不处理带引号的字符串,你甚至不能正确跳过 cmets)。

    我可能会编写一个相当简单、高效且不完整的分词器,与我在node 566467 中写的非常相似。

    基于该代码,我可能只是挖掘非注释/非字符串块以查找 \bimport\b\b\Q$toFind\E\b 匹配项。可能类似于:

    if( m[
            \G
            (?:
                [^'"/]+
              | /(?![/*])
            )+
        ]xgc
    ) {
        my $code = substr( $_, $-[0], $+[0] - $-[0] );
        my $imported = 0;
        while( $code =~ /\b(import\s+)?\Q$package\E\b/g ) {
            if( $1 ) {
                ... # Found importing of package
                while( $code =~ /\b\Q$class\E\b/g ) {
                    ... # Found mention of imported class
                }
                last;
            }
            ... # Found a package reference
        }
    } elsif( m[ \G ' (?: [^'\\]+ | \\. )* ' ]xgc
        ||   m[ \G " (?: [^"\\]+ | \\. )* " ]xgc
    ) {
        # skip quoted strings
    } elsif(  m[\G//.*]g­c  ) {
        # skip C++ comments
    

    【讨论】:

      【解决方案2】:

      Regex 可能是最好的解决方案,尽管我确实在 CPAN 中找到了您可能可以使用的以下模块

      • Java::JVM::Classfile - 解析编译的类文件并返回有关它们的信息。您必须先编译文件才能使用它。

      另外,请记住,使用正则表达式捕获多行注释的所有可能变体可能会很棘手。

      【讨论】:

        【解决方案3】:

        这实际上只是 Baz 的直接 grep(或 /(foo.bar.| )Baz/,如果您担心来自 some.other.Baz 的误报),但忽略 cmets,不是吗?

        如果是这样,我会拼凑一个状态引擎来跟踪您是否在多行注释中。所需的正则表达式没有什么特别的。类似于(未经测试的代码):

        my $in_comment;
        my %matches;
        my $line_num = 0;
        my $full_target = 'foo.bar.Baz';
        my $short_target = (split /\./, $full_target)[-1];  # segment after last . (Baz)
        
        while (my $line = <F>) {
            $line_num++;
            if ($in_comment) {
                next unless $line =~ m|\*/|;  # ignore line unless it ends the comment
                $line =~ s|.*\*/||;           # delete everything prior to end of comment
            } elsif ($line =~ m|/\*|) {
                if ($line =~ m|\*/|) {        # catch /* and */ on same line
                    $line =~ s|/\*.*\*/||;
                } else {
                    $in_comment = 1;
                    $line =~ s|/\*.*||;       # clear from start of comment to end of line
                }
            }
        
            $line =~ s/\\\\.*//;   # remove single-line comments
            $matches{$line_num} = $line if $line =~ /$full_target| $short_target/;
        }
        
        for my $key (sort keys %matches) {
            print $key, ': ', $matches{$key}, "\n";
        }
        

        这并不完美,嵌套的多行 cmets 或同一行上有多个多行 cmets 可能会弄乱注释的输入/输出状态,但这对于大多数实际情况来说可能已经足够了。

        要在没有状态引擎的情况下执行此操作,您需要 slurp 成一个字符串,删除 /.../ cmets,然后将其拆分回单独的行,然后用 grep 表示非//-评论命中。但是您不能以这种方式在输出中包含行号。

        【讨论】:

          【解决方案4】:

          这是我想出的,适用于我遇到的所有不同情况。我仍然是 Perl 菜鸟,它可能不是世界上最快的东西,但它应该可以满足我的需要。感谢他们帮助我以不同方式看待它的所有答案。

            my $className = 'Baz';
            my $searchPkg = 'foo.bar';
            my @potentialRefs, my @confirmedRefs;
            my $samePkg = 0;
            my $imported = 0;
            my $currentPkg = 'com.bob';
            $currentPkg =~ s/\//\./g;
            if($currentPkg eq $searchPkg){
              $samePkg = 1;  
            }
            my $inMultiLineComment = 0;
            open F, $_ or die;
            my $lineNum = 0;
            while(<F>){
              $lineNum++;
              if($inMultiLineComment){
                if(m|^.*?\*/|){
                  s|^.*?\*/||; #get rid of the closing part of the multiline comment we're in
                  $inMultiLineComment = 0;
                }else{
                  next;
                }
              }
              if(length($_) > 0){
                s|"([^"\\]*(\\.[^"\\]*)*)"||g; #remove strings first since java cannot have multiline string literals
                s|/\*.*?\*/||g;  #remove any multiline comments that start and end on the same line
                s|//.*$||;  #remove the // comments from what's left
                if (m|/\*.*$|){
                  $inMultiLineComment = 1 ;#now if you have any occurence of /* then then at least some of the next line is in the multiline comment
                  s|/\*.*$||g;
                }
              }else{
                next; #no sense continuing to process a blank string
              }
          
              if (/^\s*(import )?($searchPkg)?(.*)?\b$className\b/){
                if($imported || $samePkg){
                  push(@confirmedRefs, $lineNum);
                }else {
                  push(@potentialRefs, $lineNum);
                }
                if($1){
                  $imported = 1;
                } elsif($2){
                  push(@confirmedRefs, $lineNum);
                }
              }
            }
            close F;      
            if($imported){
              push(@confirmedRefs,@potentialRefs);
            }
          
            for (@confirmedRefs){
              print "$_\n";
            }
          

          【讨论】:

            【解决方案5】:

            如果您有足够的冒险精神,可以查看Parse::RecDescent

            【讨论】:

              猜你喜欢
              • 2019-01-20
              • 1970-01-01
              • 1970-01-01
              • 2012-05-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-08-03
              • 1970-01-01
              相关资源
              最近更新 更多