仅从for循环块中移除System.out语句
有没有简单的方法可以只从文件中的for循环块里去掉它们...
之前:
for( ... ) {
...
System.out.println("string");
...
System.out.println("string");
...
}
之后:
for( ... ) {
...
...
...
}
4 个回答
编辑:
1. 修正了嵌套的 for
循环
2. 现在可以递归获取 .java
文件
注意:
当你对代码有信心时,把第45行:
open( hanw , "+>".$file.".txt" );
替换成这一行:
open( hanw , "+>".$file );
application.pl
use strict;
use File::Find qw( finddepth );
our $root = "src/";
our $file_data = {};
our @java_files;
finddepth( sub {
if( $_ eq '.' || $_ eq '..' ) {
return;
} else {
if( /\.java$/i ) {
push( @java_files , $File::Find::name );
}
}
} , $root );
sub clean {
my $file = shift;
open( hanr , $file );
my @input_lines = <hanr>;
my $inside_for = 0;
foreach( @input_lines ) {
if( $_ =~ /(\s){0,}for(\s){0,}\((.*)\)(\s){0,}\{(\s){0,}/ ) {
$inside_for++;
push( @{$file_data->{$file}} , $_ );
} elsif( $inside_for > 0 ) {
if( $_ =~ /(\s){0,}System\.out\.println\(.*/ ) {
} elsif( $_ =~ /(\s){0,}\}(\s){0,}/ ) {
$inside_for--;
push( @{$file_data->{$file}} , $_ );
} else {
push( @{$file_data->{$file}} , $_ );
}
} else {
push( @{$file_data->{$file}} , $_ );
}
}
}
foreach ( @java_files ) {
$file_data->{$_} = [];
clean( $_ );
}
foreach my $file ( keys %$file_data ) {
open( hanw , "+>".$file.".txt" );
foreach( @{$file_data->{$file}} ) {
print hanw $_;
}
}
data1.java
class Employee {
/* code */
public void Employee() {
System.out.println("string");
for( ... ) {
System.out.println("string");
/* code */
System.out.println("string");
for( ... ) {
System.out.println("string");
/* code */
System.out.println("string");
}
}
}
}
for( ... ) {
System.out.println("string");
/* code */
System.out.println("string");
}
data2.java
for( ... ) {
/* code */
System.out.println("string");
/* code */
System.out.println("string");
/* code */
for( ... ) {
System.out.println("string");
/* code */
System.out.println("string");
for( ... ) {
System.out.println("string");
/* code */
System.out.println("string");
}
}
}
public void display() {
/* code */
System.out.println("string");
for( ... ) {
System.out.println("string");
/* code */
System.out.println("string");
for( ... ) {
System.out.println("string");
/* code */
System.out.println("string");
}
}
}
data1.java.txt
class Employee {
/* code */
public void Employee() {
System.out.println("string");
for( ... ) {
/* code */
for( ... ) {
/* code */
}
}
}
}
for( ... ) {
/* code */
}
data2.java.txt
for( ... ) {
/* code */
/* code */
/* code */
for( ... ) {
/* code */
for( ... ) {
/* code */
}
}
}
public void display() {
/* code */
System.out.println("string");
for( ... ) {
/* code */
for( ... ) {
/* code */
}
}
}
我建议用两种方法结合起来,使用静态代码分析工具 PMD 来找到问题代码,然后用一个简单的脚本来删除这些行。下面的内容包括了所有的源代码和配置,编辑中还包含了 Python 和 Groovy 的替代方案。
PMD 有一个扩展机制,可以很简单地使用 XPath 表达式 添加新规则。在我下面的实现中,我使用了:
//WhileStatement/Statement/descendant-or-self::
Statement[./StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image="System.out.println"]]
|
//ForStatement/Statement/descendant-or-self::
Statement[./StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image="System.out.println"]]
使用这种方法的好处有:
- 不需要正则表达式
- 图形化编辑器来开发和完善规则 - 你可以根据自己的需求微调我提供的规则,以应对其他情况
- 可以处理 Java 源代码中所有奇怪的格式 - PMD 使用 JavaCC 编译器来理解所有有效 Java 文件的结构
- 可以处理在循环中的条件语句里的
System.out.println
- 不论深度如何 - 可以处理跨多行的语句。
- 可以在 Eclipse 和 IntelliJ 中使用
操作步骤
在自定义规则集中创建一个 PMD 规则。
- 在
CLASSPATH
中的某个地方创建一个目录rulesets/java
- 如果路径中有“.”,可以放在工作目录下。 在这个目录中,创建一个名为
custom.xml
的规则集 XML 文件,内容为:<?xml version="1.0"?> <ruleset name="Custom" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> <description>Detecting System.out.println's</description> <rule name="LoopedSystemOutPrintlns" message="System.out.println() statements in a for or while loop" language="java" class="net.sourceforge.pmd.lang.rule.XPathRule"> <description> Find System.out.println() statements in for or while loops. </description> <priority>1</priority> <properties> <property name="xpath"> <value> <![CDATA[ //WhileStatement/Statement/descendant-or-self:: Statement[./StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image="System.out.println"]] | //ForStatement/Statement/descendant-or-self:: Statement[./StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image="System.out.println"]] ]]> </value> </property> </properties> </rule> </ruleset>
创建一个 rulesets.properties 文件,内容为:
rulesets.filenames=rulesets/java/custom.xml
太好了!PMD 现在已经配置了你的新规则,可以识别代码中任何循环内的所有
System.out.println
调用。你的规则集现在叫做 'java-custom',因为它是 'java' 目录下的 'custom.xml'
- 在
在你的代码库上运行 PMD,只选择你的规则集 java-custom。使用 XML 报告来获取起始和结束行。将结果保存到文件 "violations.xml" 中:
$ pmd -d <SOURCEDIR> -f xml -r java-custom > violations.xml
生成的文件类似于:
<?xml version="1.0" encoding="UTF-8"?> <pmd version="5.0.1" timestamp="2013-01-28T11:22:25.688"> <file name="SOURCEDIR/Example.java"> <violation beginline="7" endline="11" begincolumn="13" endcolumn="39" rule="LoopedSystemOutPrintlns" ruleset="Custom" class="Example" method="bar" priority="1"> System.out.println() statements in a for or while loop </violation> <violation beginline="15" endline="15" begincolumn="13" endcolumn="38" rule="LoopedSystemOutPrintlns" ruleset="Custom" class="Example" method="bar" priority="1"> System.out.println() statements in a for or while loop </violation> <violation beginline="18" endline="18" begincolumn="13" endcolumn="38" rule="LoopedSystemOutPrintlns" ruleset="Custom" class="Example" method="bar" priority="1"> System.out.println() statements in a for or while loop </violation> <violation beginline="20" endline="21" begincolumn="17" endcolumn="39" rule="LoopedSystemOutPrintlns" ruleset="Custom" class="Example" method="bar" priority="1"> System.out.println() statements in a for or while loop </violation> </file> </pmd>
你可以用这个报告来检查 PMD 是否正确识别了相关语句。
创建一个 Python 脚本(注意:在答案底部提供了 Groovy 的替代方案),读取 violations XML 文件并处理源文件
- 在类路径中的某个目录下创建一个名为
remover.py
的文件 在其中添加以下 Python 代码:
from xml.etree.ElementTree import ElementTree from os import rename, path from sys import argv def clean_file(source, target, violations): """Read file from source outputting all lines, *except* those in the set violations, to the file target""" infile = open(source, 'r' ) outfile = open(target, "w") for num, line in enumerate(infile.readlines(), start=1): if num not in violations: outfile.write(line) infile.close() outfile.close() def clean_all(pmd_xml): """Read a PMD violations XML file; for each file identified, remove all lines that are marked as violations""" tree = ElementTree() tree.parse(pmd_xml) for file in tree.findall("file"): # Create a list of lists. Each inner list identifies all the lines # in a single violation. violations = [ range(int(violation.attrib['beginline']), int(violation.attrib['endline'])+1) for violation in file.findall("violation")] # Flatten the list of lists into a set of line numbers violations = set( i for j in violations for i in j ) if violations: name = file.attrib['name'] bak = name + ".bak" rename(name, bak) clean_file(bak, name, violations) if __name__ == "__main__": if len(argv) != 2 or not path.exists(argv[1]): exit(argv[0] + " <PMD violations XML file>") clean_all(argv[1])
- 在类路径中的某个目录下创建一个名为
运行这个 Python 脚本。它会通过添加 ".bak" 来重命名匹配的文件,然后重写 Java 文件,去掉那些有问题的行。这可能会破坏文件,所以请确保你的文件已经备份好。特别是,不要连续两次运行这个脚本 - 第二次运行时会天真地删除相同的行号,即使它们已经被删除:
$ python remover.py violations.xml
编辑
对于那些更喜欢用 Java 脚本来从 violations.xml
中删除 System.out.println
语句的人,我提供了以下 Groovy 代码:
def clean_file(source, target, violations) {
new File(target).withWriter { out ->
new File(source).withReader { reader ->
def i = 0
while (true) {
def line = reader.readLine()
if (line == null) {
break
} else {
i++
if(!(i in violations)) {
out.println(line)
}
}
}
}
}
}
def linesToRemove(file_element) {
Set lines = new TreeSet()
for (violation in file_element.children()) {
def i = Integer.parseInt(violation.@beginline.text())
def j = Integer.parseInt(violation.@endline.text())
lines.addAll(i..j)
}
return lines
}
def clean_all(file_name) {
def tree = new XmlSlurper().parse(file_name)
for (file in tree.children()) {
def violations = linesToRemove(file)
if (violations.size() > 0) {
def origin = file.@name.text()
def backup = origin + ".bak"
new File(origin).renameTo(new File(backup))
clean_file(backup, origin, violations)
}
}
}
clean_all("violations.xml")
一般来说,System.out.println
的调用不一定是问题 - 可能你的语句是 "Calling method on " + obj1 + " with param " + obj2 + " -> " + (obj1.myMethod(obj2))
这样的形式,真正的开销在于字符串连接(用 StringBuffer/StringBuilder 会更好)和方法调用的成本。
这个问题有点棘手:哪个闭合大括号是用来结束for
循环的呢?你要么分析整个代码,要么使用一些简单的规则。在下面的解决方案中,我要求闭合大括号的缩进和for
关键字的缩进要一致:
$ perl -nE'
if( /^(\s*)for\b/ .. /^$ws\}/ ) {
$ws = $1 // $ws;
/^\s*System\.out\.println/ or print;
} else { print }'
这里使用了翻转操作符COND1 .. COND2
。这个脚本可以作为一个简单的过滤器使用。
$ perl -nE'...' <source >processed
或者也可以带有备份功能:
$ perl -i.bak -nE'...' source
(会创建一个名为source.bak
的备份文件)。
只在示例输入上测试过;没有在合理的测试套件上测试。
这个脚本通过了GLES Prateek Nina的测试。
要在一个目录下的所有Java文件上运行这个脚本,可以这样做:
$ perl -i.bak -nE'...' *.java
编辑
在Windows系统上,分隔符需要改成"
。另外,我们需要自己处理所有的通配符匹配。
> perl -nE"if(/^(\s*)for\b/../^$ws\}/){$ws=$1//$ws;/^\s*System\.out\.println/ or print}else{print}BEGIN{@ARGV=$#ARGV?@ARGV:glob$ARGV[0]}" *.java
编辑 2
这里是我在评论中提到的括号计数算法的实现。这个解决方案也会进行备份。命令行参数会被解释为通配符表达式。
#!/usr/bin/perl
use strict; use warnings;
clean($_) for map glob($_), @ARGV;
sub clean {
local @ARGV = @_;
local $^I = ".bak";
my $depth = 0;
while (<>) {
$depth ||= /^\s*for\b/ ? "0 but true" : 0;
my $delta = ( ()= /\{/g ) - ( ()= /\}/g );
$depth += $delta if $depth && $delta;
$depth = 0 if $depth < 0;
print unless $depth && /^\s*System\.out\.println/;
}
return !!1;
}
这个脚本也不会处理注释。它只会识别以System.out.println
开头的新行语句。
示例用法:> perl thisScript.pl *.java
。
这里有一个我用来测试的伪Java语法的测试文件。所有标记为XXX
的行在脚本运行后都会被删除。
/** Java test suite **/
bare block {
System.out.println(...); // 1 -- let stand
}
if (true) {
for (foo in bar) {
System.out.println; // 2 XXX
if (x == y) {
// plz kill this
System.out.println // 3 XXX
} // don't exit here
System.out.println // 4 XXX
}
}
for (...) {
for {
// will this be removed?
System.out.println // 5 XXX
}
}
/* pathological cases */
// intendation
for (...) { System.out.println()/* 6 */}
// intendation 2
for (...)
{
if (x)
{
System.out.println // 7 XXX
}}
// inline weirdness
for (...) {
// "confuse" script here
foo = new baz() {void qux () {...}
};
System.out.println // 8 XXX
}
编号1的行应该保留,并且确实保留了。编号6的行应该被删除;但是这些脚本无法做到这一点。