Posted on 2007-04-26 18:00
nemo 阅读(2133)
评论(1) 编辑 收藏 所属分类:
EclipseRCP/SWT/JFACE
今天发现了Eclispe的XMLMemento的一个Bug。看来不是自己写的程序的确应该谨慎使用,即使是Eclipse的官方包也要小心。发现这个bug花了我3个小时,最后只得自己重写一个XMLMemento.
当我们往XMLMemento对象中写入数据并保存后,XMLMemento会识别XML文件每行的终止位置,并在保存的时候会自动调用println()方法。而println()方法是与平台无关的,我们可以随便使用。
但是,假如我们重新从保存的文件中抽取XMLMemento对象,就会发现,这个XMLMemento对象同我们原来的XMLMemento对象相比,增加了文本(Text)节点,而这个文本节点中仅包含一个字符'\n',这在Unix系统下是一个换行符,因而XMLMemento类对其做了相应的转换:
private static final class DOMWriter extends PrintWriter {
private static String getReplacement(char c) {
// Encode special XML characters into the equivalent character references.
// The first five are defined by default for all XML documents.
// The next three (#xD, #xA, #x9) are encoded to avoid them
// being converted to spaces on deserialization
// (fixes bug 93720)
switch (c) {
......
case '\r':
return "#x0D"; //$NON-NLS-1$
case '\n':
return "#x0A"; //$NON-NLS-1$
......
}
return null;
}
}
但是这种转换相对于Windows平台来说却相当糟糕。因为windows平台下的换行符是"\r\n",而不是"\n",因而如果再次保存该XMLMemento对象就会出现乱码,这会在每个/>结束时出现
字符串。
XMLMemento对象是为了保存当前会话的快照,以便使用户能够在两次使用应用程序过程中得到一致性的操作体验。这对于一般的程序来说,因为从来不使用XMLMement对象中的textData,而且在最后保存的过程中会重新创建一个XMLMemento对象来保存当前的状态,这个XMLMemento对象同上一次的XMLMemento对象没有关系。但是对于想要在整个应用程序期间使用同一个XMLMemento的需求来说,可能是一个很坏的消息。因为两次保存的结果会不一致,最终原来使用的代码将无法解析模型。
XMLMemento还有对中国人来说一个更讨厌的地方,这就是它只支持UTF-8格式,在它的内部创建了一个DOMWriter私有类,将首行代码写死在了程序里:
private static final class DOMWriter extends PrintWriter {
/**//* constants */
private static final String XML_VERSION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; //$NON-NLS-1$
}
真让人不爽。因为虽然XMLMemento有其被建议使用的范围:
org.eclipse.ui.IMemento
Interface to a memento used for saving the important state of an object in a form that can be persisted in the file system.
Mementos were designed with the following requirements in mind:
Certain objects need to be saved and restored across platform sessions.
When an object is restored, an appropriate class for an object might not be available. It must be possible to skip an object in this case.
When an object is restored, the appropriate class for the object may be different from the one when the object was originally saved. If so, the new class should still be able to read the old form of the data.
Mementos meet these requirements by providing support for storing a mapping of arbitrary string keys to primitive values, and by allowing mementos to have other mementos as children (arranged into a tree). A robust external storage format based on XML is used.
The key for an attribute may be any alpha numeric value. However, the value of TAG_ID is reserved for internal use.
This interface is not intended to be implemented or extended by clients.
但是XMLMemento可以支持的功能实际上非常强大,可以作为现成的简单XML文件解析器。使用其创建XML,以及从XML文件中提取模型很方便。
所以,如果想要使用XMLMemento做这方面的事情的话,只能改Eclipse的代码了……幸好XMLMemento只是实现了一个IMemento接口,是顶层类。因而我们可以简单的将其复制出来。
然后改动以下地方:
1.改变出错信息的提示(默认写入系统.log文件,取消该依赖关系,改为在终端输出或删掉都可);
2.改变DOMWriter中的XML_VERSION值,改为你需要的字符集(也可以将这个字段提取出来,根据需要动态的改变)。
3.改变getReplaceMent()方法,改变例示如下:
private static String getReplacement(char c) {
// Encode special XML characters into the equivalent character references.
// The first five are defined by default for all XML documents.
// The next three (#xD, #xA, #x9) are encoded to avoid them
// being converted to spaces on deserialization
// (fixes bug 93720)
switch (c) {
case '<' :
return "lt"; //$NON-NLS-1$
case '>' :
return "gt"; //$NON-NLS-1$
case '"' :
return "quot"; //$NON-NLS-1$
case '\'' :
return "apos"; //$NON-NLS-1$
case '&' :
return "amp"; //$NON-NLS-1$
case '\r':
return "#x0D"; //$NON-NLS-1$
case '\n':
return System.getProperty("line.separator"); //$NON-NLS-1$
case '\u0009':
return "#x09"; //$NON-NLS-1$
}
return null;
}
4.改变appendEscapedChar方法,改变例示如下:
private static void appendEscapedChar(StringBuffer buffer, char c) {
String replacement = getReplacement(c);
if (replacement != null) {
if(replacement.equals(System.getProperty("line.separator")))
buffer.append(replacement);
else{
buffer.append('&');
buffer.append(replacement);
buffer.append(';');
}
} else {
buffer.append(c);
}
}
}
附:提交的Bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=184175
Steps To Reproduce:
1.create an XMLMemento instance
2.create elements
3.save the instance into .xml file
Note: i haven't put textData into the memento, but when i save contents of the
memento into the file, the system will put '\n' into the file.
4.close the file
5.create a new XMLMemento instance from the .xml file
6.save the new XMLMemento instance.
7.then strange characters appears in the .xml file:
More information:
platform independence:
automatically put '\n' into the .xml file is nice, but the line separator is
0x0D0A in Windows, and it is 0x0D in Unix, so the fix should be platform
independence.
we can get this property from System.getProperty("line.separator");