背景:我们项目中多语言文件是放在xml文件中,时间一长,一些多语言在代码中并没有用到,老大要我写个小工具将未使用到的多语言ID给找出来,需要考虑的就是:查找源代码文件的效率和判断ID是否被使用(注释中用到的不算),想用windows下的find findstr命令的,但java注释不好区分,因此还是只能对每个文件进行读取判断。
其中注释中出现ID的情况有以下几种情况:
1:// 开头的行,对应正则是 ^\\s*//.*
2:*开头的行,java的块注释中除首末两行其它是以*开头,对应正则是 ^\\s*\\*.*
3:/*开头的行,正则是 ^\\s*/\\*.*
4:*/结尾的行,正则 \\*/\\s*$
5:我把空行也认为是注释,正则 ^\\s+
第2种情况,有可能块注释中除首末外其它行并不以*开头,也是注释,因此这里比较麻烦,要在代码中判断。
代码如下:
public class FindStrInFiles implements Listener {
private Text msrText;
private Button msrBtn;
private Text projectText;
private Button projectBtn;
private ProgressBar progressBar;
private Button searchBtn;
private Text resultText;
private Label statusLabel;
private Pattern commentPattern;
private Matcher commentMatcher;
private BufferedReader br;
private String line;
private boolean preComment = false;
private Display display;
private Shell shell;
private LinkedList<String> msrList;
private boolean isUsed;
private WorkThread workThread;
private boolean isStopped;
private boolean isSuspended;
public static void main(String... args) {
FindStrInFiles find = new FindStrInFiles();
find.init();
}
private void init() {
// 以// * /*开头或者以*/结尾的都是注释,空行也认为是注释,
// 还有一种情况是在块注释中间的行,在Pattern中无法判断,需要在代码中单独判断
this.commentPattern = Pattern
.compile("(^\\s*//.*)|(^\\s*\\*.*)|(^\\s*/\\*.*)|(\\*/\\s*$)|(^\\s+)");
display = new Display();
shell = new Shell(display);
GridLayout layout = new GridLayout(3, false);
layout.horizontalSpacing = 10;
layout.marginLeft = layout.marginRight = layout.marginBottom = 5;
shell.setLayout(layout);
Label label = new Label(shell, SWT.NONE);
label.setText("多语言文件");
GridData gridData = new GridData(SWT.BEGINNING, SWT.CENTER, false,
false, 1, 1);
label.setLayoutData(gridData);
this.msrText = new Text(shell, SWT.BORDER | SWT.SINGLE);
gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1);
gridData.heightHint = 18;
this.msrText.setLayoutData(gridData);
this.msrBtn = new Button(shell, SWT.BORDER | SWT.NONE);
this.msrBtn.setText("选择");
gridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 1, 1);
gridData.widthHint = 50;
this.msrBtn.setLayoutData(gridData);
this.msrBtn.addListener(SWT.Selection, this);
label = new Label(shell, SWT.NONE | SWT.UNDERLINE_SINGLE);
label.setText("工程父目录");
label.setForeground(display.getSystemColor(SWT.COLOR_BLUE));
label.setToolTipText("该目录下的所有项目中src下的源代码都会被搜索,不是工程的文件夹被忽略");
gridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 1, 1);
label.setLayoutData(gridData);
this.projectText = new Text(shell, SWT.BORDER | SWT.SINGLE);
gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1);
gridData.heightHint = 18;
this.projectText.setLayoutData(gridData);
this.projectBtn = new Button(shell, SWT.BORDER);
this.projectBtn.setText("选择");
gridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 1, 1);
gridData.widthHint = 50;
this.projectBtn.setLayoutData(gridData);
this.projectBtn.addListener(SWT.Selection, this);
this.progressBar = new ProgressBar(shell, SWT.SIMPLE);
gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1);
this.progressBar.setLayoutData(gridData);
this.searchBtn = new Button(shell, SWT.BORDER);
this.searchBtn.setText("搜索");
this.searchBtn.addListener(SWT.Selection, this);
gridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 1, 1);
gridData.widthHint = 50;
this.searchBtn.setLayoutData(gridData);
label = new Label(shell, SWT.NONE);
label.setText("结果");
gridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, true, 1, 1);
label.setLayoutData(gridData);
this.resultText = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL | SWT.READ_ONLY);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
FontData fontData = new FontData();
fontData.setHeight(11);
this.resultText.setFont(new Font(display, fontData));
this.resultText.setLayoutData(gridData);
this.statusLabel = new Label(shell, SWT.NONE);
gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1);
this.statusLabel.setLayoutData(gridData);
shell.setBounds(100, 20, 800, 600);
shell.setDefaultButton(this.msrBtn);
shell.setText("搜索未使用的多语言ID");
shell.addListener(SWT.Dispose, this);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
shell.dispose();
}
@Override
public void handleEvent(Event event) {
int type = event.type;
Widget widget = event.widget;
if (SWT.Selection == type) {
if (widget == this.msrBtn) {
FileDialog dialog = new FileDialog(shell, SWT.SIMPLE | SWT.OPEN
| SWT.SINGLE);
dialog.setFilterPath("E:\\workspace\\eclipsej2ee3.7\\XXX\\conf");
dialog.setFilterExtensions(new String[] { "*.xml" });
String filePath = dialog.open();
if (filePath == null) {
return;
}
if (!dialog.getFileName().startsWith("message.")) {
MessageBox box = new MessageBox(shell, SWT.ICON_INFORMATION);
box.setText("Tip");
box.setMessage("选择的不是语言文件");
box.open();
return;
}
this.msrText.setText(filePath);
} else if (widget == this.projectBtn) {
DirectoryDialog dialog = new DirectoryDialog(shell, SWT.OPEN
| SWT.SINGLE);
dialog.setFilterPath("E:\\workspace\\eclipsej2ee3.7");
dialog.setMessage("选择工程父目录,该目录下的所有项目中src下的源代码都会被搜索,不是工程的文件夹被忽略");
String container = dialog.open();
if (container == null) {
return;
}
this.projectText.setText(container);
} else if (widget == this.searchBtn) {
String btnText = this.searchBtn.getText();
if ("搜索".equals(btnText)) {
String msr = msrText.getText().trim();
String father = projectText.getText().trim();
if (msr.length() == 0 || father.length() == 0) {
return;
}
this.resultText.setText("");
this.statusLabel.setText("");
this.isStopped = false;
workThread = new WorkThread(msr, father);
workThread.start();
this.searchBtn.setText("暂停");
} else if ("暂停".equals(btnText)) {
this.isSuspended = true;
this.searchBtn.setText("继续");
} else if ("继续".equals(btnText)) {
synchronized(this.msrList){
this.isSuspended = false;
this.msrList.notifyAll();
}
this.searchBtn.setText("暂停");
}
}
}
if (SWT.Dispose == type) {
this.isStopped = true;
display.dispose();
System.exit(0);
}
}
private void checkFile(final File project, final File src,
final String id) {
if (this.isStopped) {
return;
}
if(this.isSuspended){
synchronized(this.msrList){
try {
this.msrList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (src.isDirectory()) {
File[] files = src.listFiles();
for (final File f : files) {
if (this.isUsed) {
return;
}
// 跳过类似.svn这样的目录
if (f.isHidden()) {
continue;
}
this.checkFile(project, f, id);
}
} else {
// 跳过非java文件
if (!src.getName().endsWith(".java")) {
return;
}
if (display.isDisposed()) {
return;
}
display.asyncExec(new Runnable() {
@Override
public void run() {
if (statusLabel.isDisposed()) {
return;
}
statusLabel.setText("查找ID: "+id+" 分析文件: " + src.getPath());
}
});
try {
br = new BufferedReader(new FileReader(src));
int index = -1;
while ((line = br.readLine()) != null) {
index++;
// 注释跳过
if (this.isComment(line)) {
} else {
if (line.indexOf("\"" + id + "\"") != -1) {
this.isUsed = true;
System.out.println(id + " occurs at project: "
+ project.getName() + ", file: "
+ src.getPath() + ", row number: " + index);
return;
}
}
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
private boolean isComment(String line) {
this.commentMatcher = this.commentPattern.matcher(line);
if (this.commentMatcher.matches()) {
// 注释块的第一行
if (line.trim().startsWith("/*")) {
this.preComment = true;
}
// 注释块的最后一行
if (line.trim().endsWith("*/")) {
this.preComment = false;
}
return true;
}
// 如果在注释块中间
if (this.preComment) {
return true;
}
return false;
}
private void setBtnStatus(final boolean flag) {
display.asyncExec(new Runnable() {
@Override
public void run() {
if (msrBtn.isDisposed() || projectBtn.isDisposed()) {
return;
}
msrBtn.setEnabled(flag);
projectBtn.setEnabled(flag);
}
});
}
// ==========================
class WorkThread extends Thread {
private String xmlPath;
private String container;
public WorkThread(String xmlPath, String container) {
this.xmlPath = xmlPath;
this.container = container;
}
@Override
public void run() {
setBtnStatus(false);
msrList = new LinkedList<String>();
Document doc = null;
try {
doc = new SAXBuilder().build(this.xmlPath);
} catch (JDOMException e1) {
e1.printStackTrace();
return;
} catch (IOException e1) {
e1.printStackTrace();
return;
}
Element root = doc.getRootElement();
List<Element> children = root.getChildren("msr");
if (children == null || children.size() == 0) {
return;
}
Iterator<Element> it = children.iterator();
while (it.hasNext()) {
msrList.add(it.next().getChildText("id"));
}
doc = null;
final File[] projects = new File(this.container).listFiles();
int msrIndex = 0;
int projectIndex = 0;
for (final String id : msrList) {
if (isStopped) {
return;
}
msrIndex++;
isUsed = false;
for (File f : projects) {
if (isStopped) {
return;
}
projectIndex++;
final int ratio = (int) ((projects.length * (msrIndex - 1) + msrIndex) / (projects.length
* msrList.size() / 100));
if (display.isDisposed()) {
return;
}
display.asyncExec(new Runnable() {
@Override
public void run() {
if (progressBar.isDisposed()) {
return;
}
progressBar.setSelection(ratio);
}
});
// 非目录则跳过
if (f.isFile()) {
continue;
}
// 不是工程则直接跳过
if (!new File(f, ".project").exists()) {
continue;
}
// 隐藏目录跳过
if (f.isHidden()) {
continue;
}
File src = new File(f, "src");
if (!src.exists()) {
continue;
}
checkFile(f, src, id);
if (isUsed) {
break;
} else {
}
}
if (!isUsed && !isStopped) {
if (display.isDisposed()) {
return;
}
display.asyncExec(new Runnable() {
@Override
public void run() {
if (resultText.isDisposed()) {
return;
}
resultText.append(id
+ System.getProperty("line.separator"));
}
});
}
}
setBtnStatus(true);
display.asyncExec(new Runnable() {
@Override
public void run() {
if (statusLabel.isDisposed()) {
return;
}
searchBtn.setText("搜索");
statusLabel.setText("搜索完毕,共有"
+ (resultText.getLineCount() - 1) + " 条记录");
}
});
}
}
}
这里遇到的几个问题:FileDialog和DirectoryDialog不能指定size和location, 如果想指定size和location,只能用Shell去模拟Dialog了。
还有两个常见的技术:在暂停搜索的时候,用了wait和notifyAll,由于搜索是个比较耗时的工作,因此不能直接在GUI线程中去完成,这里用WorkThread去完成搜索,由于swt和swing都是GUI 单线程机制,如果在GUI/EDT中去完成搜索的话,会造成UI假死。解决方法就是display.asyncExec和display.syncExec,SwintUtilities.invokeLater,SwingUtilities.invokeAndWait,前者表示直接让UI事件插队而返回,后者表示等待UI事件执行完才返回。