前言
在前一篇写目录遍历XML资源时,在IDE-eclipse中运行良好。当我写完大部分XML资源,测试通过,打包成jar,运行就crash。于是就有了本篇作续篇。
从 URI is not hierarchical 异常说起
File file = new File(url.toURI());
上篇的这行代码在jar中运行会报非法参数异常。
其原因
- 在eclipse开发时,uri被加载后映射到 classes/ 下的每一个资源文件
- 在 jar 运行时,uri 映射的则是资源名的定位,而 jar 不是目录,它没有目录层级关系,所以无法转换成 file。
若不是从零开始写应用程序,我还真难遇到这类异常。
起源:时光倒流回前两天
我在编写string.xml资源时,当整理完230多个菜单项后,已经筋疲力竭了。于是我意识到,不可能把全部资源写到一个xml中,至少不能手工写,非常容易出错。比如:
- 重复项
- 拼写错误
还有一些费精力,比如:
- 查找定位行非常困难
- 眼花缭乱
于是我想到把它拆分成若干独立的xml,放在同一个目录下,遍历即可。
现状
为了可维护性,我仍然要编写多个独立的 xml,然后写工具程序,自动合并成一个完整的xml。
于是在开发时,仍然使用这个有缺陷的方式,但在程序打包运行时,jar中调用的是另一套API。简单讲:
- 在开发时,用 URL class.getResource(String name);
- 在运行时,用 InputStream class.getResourceAsStream(String name);
为了防止以后再踩坑,我把异常说明写到源头的类上。
package editor.res.xml.util;
import java.io.File;
import java.io.FilenameFilter;
import java.net.URISyntaxException;
import java.net.URL;
/**
* <pre>
* when IDE-eclipse run OK.
* when java -jar editor.jar run Exception:
*
* class :Directory
* method:listXmlFile()
* line :File file = new File(url.toURI());
* </pre>
*
* <pre>
* Exception in thread "main" java.lang.IllegalArgumentException: URI is not hierarchical
* at java.base/java.io.File.<init>(File.java:418)
* at editor.resource.xml.declaration.Directory.listXmlFile(Directory.java:16)
* at editor.resource.ResourceString.load(ResourceString.java:25)
* at editor.resource.Resource.load(Resource.java:16)
* at editor.Editor.<init>(Editor.java:19)
* at editor.Editor.main(Editor.java:42)
* </pre>
*/
@Deprecated
public class Directory {
private static final String RES = "/editor/res/xml/part/";
public static final String STRING = RES + "string";
public static final String ACCELERATOR = RES + "accelerator";
public static final String MENU = RES + "menu";
private Directory(String name) {
super();
this.name = name;
}
private String name;
public String getName() {
return name;
}
public static Directory string = new Directory(STRING);
public static Directory accelerator = new Directory(ACCELERATOR);
public static Directory menu = new Directory(MENU);
@Deprecated
public File[] listXmlFile() throws URISyntaxException {
URL url = Directory.class.getResource(name);
/**
* File file = new File(url.toURI());
*
* <pre>
* Exception in thread "main" java.lang.IllegalArgumentException: URI is not hierarchical
* at java.base/java.io.File.<init>(File.java:418)
* at editor.resource.xml.declaration.Directory.listXmlFile(Directory.java:16)
* at editor.resource.ResourceString.load(ResourceString.java:25)
* at editor.resource.Resource.load(Resource.java:16)
* at editor.Editor.<init>(Editor.java:19)
* at editor.Editor.main(Editor.java:42)
* </pre>
*/
if (url != null) {
File file = new File(url.toURI());
if (file.isDirectory()) {
return file.listFiles(filenameFilter);
} else {
return null;
}
} else {
System.err.printf("资源名称错误,可能是单词拼写错误: %dn", name);
return null;
}
}
private FilenameFilter filenameFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".xml");
}
};
}
digraph G{
rankdir=LR;
a[label="string_a.xml"];
b[label="string_ab.xml"];
c[label="string_ac.xml"];
string[label="string.xml"];
a->string[label="merge"];
b->string[label="merge"];
c->string[label="merge"];
string->jar;
}
中间还有一些去重/名称关键词检查等步骤,这里不重复讲,见上篇。
字符串和快捷键都很好处理,但菜单XML我写了两份XML处理器代码。
- XmlHandlerJMenuItem
- XmlHandlerXMenuItem
区别是前者直接解析出JMenuItem的对象实例,让程序调用。后者是转为一个中间对象,用来进行合并/去重等处理。
package editor.res.xml.part.menu;
public class XMenuItem {
private String name;
private String text;
private String accelerator;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getAccelerator() {
return accelerator;
}
public void setAccelerator(String accelerator) {
this.accelerator = accelerator;
}
public String toXmlString() {
if (accelerator != null) {
return String.format("<menuItem name="%s" text="%s" accelerator="%s"></menuItem>", name, text,
accelerator);
} else {
return String.format("<menuItem name="%s" text="%s"></menuItem>", name, text);
}
}
}
上图已经是多轮重构后的代码树,已经非常直观。为了扩展,回调用Object传值,曾试着用泛型<T>,结果过于耦合,放弃了。Object用类型转换,来支持以后更多的用XML定义的资源。
package editor.xml.reader.handler.listener;
public interface XmlListener {
void onDocumentStart();
void onDocumentFinish();
void onItem(String name, Object object);
}
仍然只维护一个editor工程
像多个xml合并的逻辑,应该不归editor工程自身,有两种方法
- 新建一个子工程专门写工具链
- 在编译时,把工具链包/类/资源不打包进jar。
我目前2种方案都没用,直接默认全部打包,因为还没写完,先不管。
进度
资源部分已经写了一周了,还没完成,是因为资源管理模块非常重要,以后的图标甚至布局页面和对话框等,都可以写成XML资源。所以不是我写得慢,是它本身就费时。
目前菜单项还有search project run window四大菜单没有调整。
此外菜单项是动态,比如:当前上下文是java或xml时,两者的source菜单, refactor菜单的菜单页几乎完全不同。
如果支持n个文件类型,理论上菜单项要写n倍,相应的字符串也是n倍。
别急,慢慢来。
“遇到困难说明方向对了。”----佚名
以上~