Posted on 2007-04-26 18:00
nemo 閱讀(2150)
評(píng)論(1) 編輯 收藏 所屬分類:
EclipseRCP/SWT/JFACE
今天發(fā)現(xiàn)了Eclispe的XMLMemento的一個(gè)Bug。看來(lái)不是自己寫的程序的確應(yīng)該謹(jǐn)慎使用,即使是Eclipse的官方包也要小心。發(fā)現(xiàn)這個(gè)bug花了我3個(gè)小時(shí),最后只得自己重寫一個(gè)XMLMemento.
當(dāng)我們往XMLMemento對(duì)象中寫入數(shù)據(jù)并保存后,XMLMemento會(huì)識(shí)別XML文件每行的終止位置,并在保存的時(shí)候會(huì)自動(dòng)調(diào)用println()方法。而println()方法是與平臺(tái)無(wú)關(guān)的,我們可以隨便使用。
但是,假如我們重新從保存的文件中抽取XMLMemento對(duì)象,就會(huì)發(fā)現(xiàn),這個(gè)XMLMemento對(duì)象同我們?cè)瓉?lái)的XMLMemento對(duì)象相比,增加了文本(Text)節(jié)點(diǎn),而這個(gè)文本節(jié)點(diǎn)中僅包含一個(gè)字符'\n',這在Unix系統(tǒng)下是一個(gè)換行符,因而XMLMemento類對(duì)其做了相應(yīng)的轉(zhuǎn)換:

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;
}
}
但是這種轉(zhuǎn)換相對(duì)于Windows平臺(tái)來(lái)說(shuō)卻相當(dāng)糟糕。因?yàn)閣indows平臺(tái)下的換行符是"\r\n",而不是"\n",因而如果再次保存該XMLMemento對(duì)象就會(huì)出現(xiàn)亂碼,這會(huì)在每個(gè)/>結(jié)束時(shí)出現(xiàn)
字符串。
XMLMemento對(duì)象是為了保存當(dāng)前會(huì)話的快照,以便使用戶能夠在兩次使用應(yīng)用程序過(guò)程中得到一致性的操作體驗(yàn)。這對(duì)于一般的程序來(lái)說(shuō),因?yàn)閺膩?lái)不使用XMLMement對(duì)象中的textData,而且在最后保存的過(guò)程中會(huì)重新創(chuàng)建一個(gè)XMLMemento對(duì)象來(lái)保存當(dāng)前的狀態(tài),這個(gè)XMLMemento對(duì)象同上一次的XMLMemento對(duì)象沒(méi)有關(guān)系。但是對(duì)于想要在整個(gè)應(yīng)用程序期間使用同一個(gè)XMLMemento的需求來(lái)說(shuō),可能是一個(gè)很壞的消息。因?yàn)閮纱伪4娴慕Y(jié)果會(huì)不一致,最終原來(lái)使用的代碼將無(wú)法解析模型。
XMLMemento還有對(duì)中國(guó)人來(lái)說(shuō)一個(gè)更討厭的地方,這就是它只支持UTF-8格式,在它的內(nèi)部創(chuàng)建了一個(gè)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$



}
真讓人不爽。因?yàn)殡m然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可以支持的功能實(shí)際上非常強(qiáng)大,可以作為現(xiàn)成的簡(jiǎn)單XML文件解析器。使用其創(chuàng)建XML,以及從XML文件中提取模型很方便。
所以,如果想要使用XMLMemento做這方面的事情的話,只能改Eclipse的代碼了……幸好XMLMemento只是實(shí)現(xiàn)了一個(gè)IMemento接口,是頂層類。因而我們可以簡(jiǎn)單的將其復(fù)制出來(lái)。
然后改動(dòng)以下地方:
1.改變出錯(cuò)信息的提示(默認(rèn)寫入系統(tǒng).log文件,取消該依賴關(guān)系,改為在終端輸出或刪掉都可);
2.改變DOMWriter中的XML_VERSION值,改為你需要的字符集(也可以將這個(gè)字段提取出來(lái),根據(jù)需要?jiǎng)討B(tài)的改變)。
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");