【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(六):IStructuredModel(DOM Document)分析视图
前面的几节中,我们都已经完整的介绍过了WTP最核心的几个数据模型:语法Document(IStructuredDocument)、语义Document(IDOMDocument、ICSSDocument)和WTP模型(IStructuredModel)。IStructuredModel在某种程度上可以看作是语义Document和语法Document的门面,三者关系再罗唆一下:
前面在讲完WTP 语法Document(IStructuredDocument)的时候,我们开发过一个Structured Document分析视图,我想通过那个视图可以加深对IStructuredDocument的理解。在本节中,我们在开发一个视图,来分析一下WTP的语义Document(我们只分析最常用的IDOMDocument),希望也有类似的作用。
PS:这两个视图其实可以作为一个工具来用,对于想修改或者定制WTP源码(当然也包括基于WTP开发一些工具)的开发者可以做一个工具,当写代码分析IStructuredDocument(Text Region)和IDOMDocument(Indexed Region)遇到障碍的时候,这两个视图应该做为一个助手^_^。而且通过这两个视图内容显示的比较,应该会明白为什么IStructuredDocument是语法Document,为什么IDOMDocument(ICSSDocument)是语义Document。
开发本IStructuredModel(DOM Document)分析视图很多地方和前面的Structured Document分析视图类似,有不明白的地方(涉及到技术实现的地方),可以参考一下前面的第四节。
【需求】
和前面的Structured Document分析视图需求比较类似,大致如下:
1、提供一个Structured Model分析视图,以树状方式将当前编辑器中的IDOMDocument展示出来
2、交互(编辑器 ---> Structured Model分析视图):
激活WTP JSP编辑器(或者是我们前面自己定制的编辑器),即时更新Structured Model分析视图
当用户光在编辑器中标移动时,自动选中Structured Model分析视图中对应的节点
当编辑器中的内容改变时,即时更新Structured Model分析视图
当前激活编辑器关闭时,清空Structured Model分析视图内容
3、交互(Structured Model分析视图 ---> 编辑器)
双击视图中树状控件中特定节点,对应内容在编辑器中被选中
4、显示内容:
因为每个节点都是IDOMNode,则分别显示其实现类名称、位置信息和文本内容
【效果预览】
上面显示的效果是,双击视图中对应的IDOMNode,对应的文本内容在编辑器中被选中。
【实现摘要(文章后门会附上对应的源码)】
1、 创建插件工程wtp.stucturedmodel,创建视图。视图IViewPart对应实现类为StructuredModelView,这个和前面讲过的Structured Document分析视图类似,这边就不细讲了。
2、利用workbench中的选择服务(seleciton service)。前面需求中说过,我们要监听光标在编辑器中的位置选择,所以使用此服务,所以我们的StructuredModelView要实现org.eclipse.ui.ISelectionListener接口。
注册、销毁selection listener和前面开发Structured Document分析视图是一样的,在视图实现类init方法中注册,在dispose方法中销毁。
我们看一下selection事件的处理代码:
1、如果当前选择事件来自TextEditor类型的编辑器中(文本编辑器的共同超类),则去获取编辑器对应的IDocument,如果是IStrucuturedDocument则判断是否需要更新;如果不是,则不是我们的菜^_^
2、利用WTP提供的模型管理器IModelManager(这个在下一节会详细讲,很重要^_^)获取和以上IStructuredDocument对应的IStructuredModel(通过IStructuredModel,可以获取到对应的语义Document--IDOMDocument,前面说过的^_^)。然后判断是否需要刷新模型tree viewer,判断的依据是看tree viewer中现有的input和本IStructuredModel是否一致。
PS:这边有两点需要注意:一是IModelStateListener的注册;二是IModelManager的使用。
3、处理text selection(参见org.eclipse.jface.text.ITexSelection),定位对应的dom node。大致过程为:首先判根据IStructuredModel.getIndexedRegion获取对应的节点(注意这边只能定位到对应的IDOMElement元素,并不能定到对应的IDOMAttr或者IDOMText);其次判断光标是否位于节点的attribute中,如果是,则定位到dom attr(具体可以参见代码)
3、 处理视图中tree viewer双击,定位编辑器中对应内容。
4、 利用IModelStateListener同步更新视图。我们在上一节在介绍IStructuredModel的时候,提到过WTP提供了一个IModelStateListener来允许用户监听IStructuredModel的状态变化,IStructuredModel本身又作为一个target,接受用户注册IModelStateListener实现。我们的IModelStateListener实现非常简单,只在目标IStructuredModel变化了之后,刷新视图中的tree viewer
5、 处理编辑器关闭行为,利用workbench的part service特性。当关联编辑器关闭时,削减目标IStructuredModel的引用计数,并注销之前注册的IModelStateListener,清空视图中的tree viewer。
6、tree viewer对应的content provider实现。(其实和我们遍历一个普通的xml dom document很类似)
以上基本上就是本视图的主要代码了,开发这个视图代码基本上也是300行左右。
本插件工程需要依赖的插件列表为:
org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.wst.sse.core,
org.eclipse.wst.xml.core,
org.eclipse.wst.sse.ui,
org.eclipse.jface.text,
org.eclipse.ui.workbench.texteditor
【源码下载】
源码为实际工程以Export ---> Archive File方式导出的,下载链接: Structured Model(Dom Document)分析视图源码
前面在讲完WTP 语法Document(IStructuredDocument)的时候,我们开发过一个Structured Document分析视图,我想通过那个视图可以加深对IStructuredDocument的理解。在本节中,我们在开发一个视图,来分析一下WTP的语义Document(我们只分析最常用的IDOMDocument),希望也有类似的作用。
PS:这两个视图其实可以作为一个工具来用,对于想修改或者定制WTP源码(当然也包括基于WTP开发一些工具)的开发者可以做一个工具,当写代码分析IStructuredDocument(Text Region)和IDOMDocument(Indexed Region)遇到障碍的时候,这两个视图应该做为一个助手^_^。而且通过这两个视图内容显示的比较,应该会明白为什么IStructuredDocument是语法Document,为什么IDOMDocument(ICSSDocument)是语义Document。
开发本IStructuredModel(DOM Document)分析视图很多地方和前面的Structured Document分析视图类似,有不明白的地方(涉及到技术实现的地方),可以参考一下前面的第四节。
【需求】
和前面的Structured Document分析视图需求比较类似,大致如下:
1、提供一个Structured Model分析视图,以树状方式将当前编辑器中的IDOMDocument展示出来
2、交互(编辑器 ---> Structured Model分析视图):
激活WTP JSP编辑器(或者是我们前面自己定制的编辑器),即时更新Structured Model分析视图
当用户光在编辑器中标移动时,自动选中Structured Model分析视图中对应的节点
当编辑器中的内容改变时,即时更新Structured Model分析视图
当前激活编辑器关闭时,清空Structured Model分析视图内容
3、交互(Structured Model分析视图 ---> 编辑器)
双击视图中树状控件中特定节点,对应内容在编辑器中被选中
4、显示内容:
因为每个节点都是IDOMNode,则分别显示其实现类名称、位置信息和文本内容
【效果预览】
上面显示的效果是,双击视图中对应的IDOMNode,对应的文本内容在编辑器中被选中。
【实现摘要(文章后门会附上对应的源码)】
1、 创建插件工程wtp.stucturedmodel,创建视图。视图IViewPart对应实现类为StructuredModelView,这个和前面讲过的Structured Document分析视图类似,这边就不细讲了。
public
class
StructuredModelView
extends
ViewPart
implements
ISelectionListener{
private TreeViewer viewer;
private ITreeContentProvider contentProvider;
private ILabelProvider labelProvider;
//
}
private TreeViewer viewer;
private ITreeContentProvider contentProvider;
private ILabelProvider labelProvider;
//
}
2、利用workbench中的选择服务(seleciton service)。前面需求中说过,我们要监听光标在编辑器中的位置选择,所以使用此服务,所以我们的StructuredModelView要实现org.eclipse.ui.ISelectionListener接口。
注册、销毁selection listener和前面开发Structured Document分析视图是一样的,在视图实现类init方法中注册,在dispose方法中销毁。
1
/*
(non-Javadoc)
2 * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite)
3 */
4 public void init(IViewSite site) throws PartInitException {
5 super .init(site);
6
7 this .getSite().getPage().getWorkbenchWindow().getSelectionService().addPostSelectionListener( this );
8 this .getSite().getPage().getWorkbenchWindow().getPartService().addPartListener(partListener);
9 }
2 * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite)
3 */
4 public void init(IViewSite site) throws PartInitException {
5 super .init(site);
6
7 this .getSite().getPage().getWorkbenchWindow().getSelectionService().addPostSelectionListener( this );
8 this .getSite().getPage().getWorkbenchWindow().getPartService().addPartListener(partListener);
9 }
我们看一下selection事件的处理代码:
1
/*
(non-Javadoc)
2 * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection)
3 */
4 public void selectionChanged(IWorkbenchPart part, ISelection selection) {
5 if (part instanceof TextEditor) {
6 IEditorInput editorInput = ((TextEditor)part).getEditorInput();
7 IDocument document = ((TextEditor)part).getDocumentProvider().getDocument(editorInput);
8
9 // 判断是否是IStructuredDocument
10 if ( ! (document instanceof IStructuredDocument)) {
11 this .viewer.setInput( new Object[ 0 ]);
12 return ;
13 }
14
15 // 对于editor使用的IStructuredModel,是用IModelManager来管理的
16 IModelManager modelManager = StructuredModelManager.getModelManager();
17 IStructuredModel structuredModel = modelManager.getModelForRead((IStructuredDocument)document);
18 if (structuredModel == null )
19 return ;
20
21 // 根据判断是否需要更新tree vier输入做不同处理
22 if ( this .needUpdateInput(structuredModel)) {
23 // 减少old model的引用计数,并注销对应的model listener
24 if ( this .viewer.getInput() instanceof IStructuredModel) {
25 IStructuredModel oldStructuredModel = ((IStructuredModel) this .viewer.getInput());
26
27 oldStructuredModel.removeModelStateListener(modelStateListener);
28 oldStructuredModel.releaseFromRead();
29 }
30
31 // 设置输入,并注册模型状态监听器
32 structuredModel.addModelStateListener( this .modelStateListener);
36 }
37 else {
38 // 如果不需要此structuredModel作为输入,则将此structuredModel的引用计数复原
39 structuredModel.releaseFromRead();
40 }
41
42 // 根据编辑器中选择定位到model tree viewer中相应节点
43 if (selection instanceof ITextSelection)
44 this .processTextSelection((ITextSelection)selection, structuredModel);
45
46 this .sourcePart = part;
47 }
48 }
49
50 /**
51 * 判断当前structuredModel和tree viewer中已有的structured model是否一致(判断是否==,而非equals)
52 *
53 * @param structuredModel
54 * @return
55 */
56 private boolean needUpdateInput(IStructuredModel structuredModel) {
57 if ( this .viewer.getInput() != null )
58 return this .viewer.getInput() != structuredModel;
59
60 return true ;
61 }
看的出来,我们的selection处理逻辑如下:
2 * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection)
3 */
4 public void selectionChanged(IWorkbenchPart part, ISelection selection) {
5 if (part instanceof TextEditor) {
6 IEditorInput editorInput = ((TextEditor)part).getEditorInput();
7 IDocument document = ((TextEditor)part).getDocumentProvider().getDocument(editorInput);
8
9 // 判断是否是IStructuredDocument
10 if ( ! (document instanceof IStructuredDocument)) {
11 this .viewer.setInput( new Object[ 0 ]);
12 return ;
13 }
14
15 // 对于editor使用的IStructuredModel,是用IModelManager来管理的
16 IModelManager modelManager = StructuredModelManager.getModelManager();
17 IStructuredModel structuredModel = modelManager.getModelForRead((IStructuredDocument)document);
18 if (structuredModel == null )
19 return ;
20
21 // 根据判断是否需要更新tree vier输入做不同处理
22 if ( this .needUpdateInput(structuredModel)) {
23 // 减少old model的引用计数,并注销对应的model listener
24 if ( this .viewer.getInput() instanceof IStructuredModel) {
25 IStructuredModel oldStructuredModel = ((IStructuredModel) this .viewer.getInput());
26
27 oldStructuredModel.removeModelStateListener(modelStateListener);
28 oldStructuredModel.releaseFromRead();
29 }
30
31 // 设置输入,并注册模型状态监听器
32 structuredModel.addModelStateListener( this .modelStateListener);
36 }
37 else {
38 // 如果不需要此structuredModel作为输入,则将此structuredModel的引用计数复原
39 structuredModel.releaseFromRead();
40 }
41
42 // 根据编辑器中选择定位到model tree viewer中相应节点
43 if (selection instanceof ITextSelection)
44 this .processTextSelection((ITextSelection)selection, structuredModel);
45
46 this .sourcePart = part;
47 }
48 }
49
50 /**
51 * 判断当前structuredModel和tree viewer中已有的structured model是否一致(判断是否==,而非equals)
52 *
53 * @param structuredModel
54 * @return
55 */
56 private boolean needUpdateInput(IStructuredModel structuredModel) {
57 if ( this .viewer.getInput() != null )
58 return this .viewer.getInput() != structuredModel;
59
60 return true ;
61 }
1、如果当前选择事件来自TextEditor类型的编辑器中(文本编辑器的共同超类),则去获取编辑器对应的IDocument,如果是IStrucuturedDocument则判断是否需要更新;如果不是,则不是我们的菜^_^
2、利用WTP提供的模型管理器IModelManager(这个在下一节会详细讲,很重要^_^)获取和以上IStructuredDocument对应的IStructuredModel(通过IStructuredModel,可以获取到对应的语义Document--IDOMDocument,前面说过的^_^)。然后判断是否需要刷新模型tree viewer,判断的依据是看tree viewer中现有的input和本IStructuredModel是否一致。
PS:这边有两点需要注意:一是IModelStateListener的注册;二是IModelManager的使用。
3、处理text selection(参见org.eclipse.jface.text.ITexSelection),定位对应的dom node。大致过程为:首先判根据IStructuredModel.getIndexedRegion获取对应的节点(注意这边只能定位到对应的IDOMElement元素,并不能定到对应的IDOMAttr或者IDOMText);其次判断光标是否位于节点的attribute中,如果是,则定位到dom attr(具体可以参见代码)
3、 处理视图中tree viewer双击,定位编辑器中对应内容。
this
.viewer.addDoubleClickListener(
new
IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
TreeSelection selection = (TreeSelection)event.getSelection();
// 树上的每个节点都是indexed region
IndexedRegion indexedRegion = (IndexedRegion)selection.getFirstElement();
// 处理编辑器选中
int selectionOffset = indexedRegion.getStartOffset();
int length = indexedRegion.getEndOffset() - selectionOffset;
((StructuredTextEditor)sourcePart).getTextViewer().setSelectedRange(selectionOffset, length);
}
});
上面我们根据tree viewer中选中的indexed region对应的坐标,直接通过ITextViewer.setSelectedRange(int offset, int length)接口来进行文本选中。(PS:WTP提供的StructuredText本身就是一种ISourceViewer,ISourceViewer本身又是一种ITextViewer^_^)
public void doubleClick(DoubleClickEvent event) {
TreeSelection selection = (TreeSelection)event.getSelection();
// 树上的每个节点都是indexed region
IndexedRegion indexedRegion = (IndexedRegion)selection.getFirstElement();
// 处理编辑器选中
int selectionOffset = indexedRegion.getStartOffset();
int length = indexedRegion.getEndOffset() - selectionOffset;
((StructuredTextEditor)sourcePart).getTextViewer().setSelectedRange(selectionOffset, length);
}
});
4、 利用IModelStateListener同步更新视图。我们在上一节在介绍IStructuredModel的时候,提到过WTP提供了一个IModelStateListener来允许用户监听IStructuredModel的状态变化,IStructuredModel本身又作为一个target,接受用户注册IModelStateListener实现。我们的IModelStateListener实现非常简单,只在目标IStructuredModel变化了之后,刷新视图中的tree viewer
private
class
ModelStateListener
implements
IModelStateListener {
/* (non-Javadoc)
* @see org.eclipse.wst.sse.core.internal.provisional.IModelStateListener#modelChanged(org.eclipse.wst.sse.core.internal.provisional.IStructuredModel)
*/
public void modelChanged(IStructuredModel model) {
viewer.refresh();
}
// 只覆写了该方法,其他方法代码省略
}
/* (non-Javadoc)
* @see org.eclipse.wst.sse.core.internal.provisional.IModelStateListener#modelChanged(org.eclipse.wst.sse.core.internal.provisional.IStructuredModel)
*/
public void modelChanged(IStructuredModel model) {
viewer.refresh();
}
// 只覆写了该方法,其他方法代码省略
}
5、 处理编辑器关闭行为,利用workbench的part service特性。当关联编辑器关闭时,削减目标IStructuredModel的引用计数,并注销之前注册的IModelStateListener,清空视图中的tree viewer。
private
class
PartListener
implements
IPartListener {
public void partActivated(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
public void partBroughtToTop(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
/*
* 如果被关闭的workbench part是提供structured model信息的source part,则:
* 1、削减该structured model的引用计数(因为已经不再引用)
* 2、注销之前注册的IModelStateListener
* 3、清空tree viewer
*
* @see org.eclipse.ui.IPartListener#partClosed(org.eclipse.ui.IWorkbenchPart)
*/
public void partClosed(IWorkbenchPart part) {
// 削减引用计数,并注销对应的model listener
if (part == StructuredModelView. this ) {
if (viewer.getInput() instanceof IStructuredModel) {
IStructuredModel structuredModel = ((IStructuredModel)viewer.getInput());
structuredModel.releaseFromRead();
structuredModel.removeModelStateListener(modelStateListener);
}
}
// update model tree viewer
if (sourcePart == part) {
sourcePart = null ;
viewer.setInput( null );
}
}
public void partDeactivated(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
public void partOpened(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
}
public void partActivated(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
public void partBroughtToTop(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
/*
* 如果被关闭的workbench part是提供structured model信息的source part,则:
* 1、削减该structured model的引用计数(因为已经不再引用)
* 2、注销之前注册的IModelStateListener
* 3、清空tree viewer
*
* @see org.eclipse.ui.IPartListener#partClosed(org.eclipse.ui.IWorkbenchPart)
*/
public void partClosed(IWorkbenchPart part) {
// 削减引用计数,并注销对应的model listener
if (part == StructuredModelView. this ) {
if (viewer.getInput() instanceof IStructuredModel) {
IStructuredModel structuredModel = ((IStructuredModel)viewer.getInput());
structuredModel.releaseFromRead();
structuredModel.removeModelStateListener(modelStateListener);
}
}
// update model tree viewer
if (sourcePart == part) {
sourcePart = null ;
viewer.setInput( null );
}
}
public void partDeactivated(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
public void partOpened(IWorkbenchPart part) {
// TODO Auto-generated method stub
}
}
6、tree viewer对应的content provider实现。(其实和我们遍历一个普通的xml dom document很类似)
public
class
ModelTreeContentProvider
implements
ITreeContentProvider {
/*
* IStructuredModel分为两种:IDOMModel和ICSSModel,对应的document实现分别为IDOMDocument和ICSSDocument。
* 我们只分析IDOMModel(IDOMDocument)的情况,对于ICSSModel(ICSSDocument)的分析留给大家吧^_^
*
* @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
*/
public Object[] getChildren(Object parentElement) {
if (parentElement == null )
return new Object[ 0 ];
// 如果是IDOMModel,则获取对应的IDOMDocument
if (parentElement instanceof IDOMModel) {
IDOMDocument domDocument = ((IDOMModel)parentElement).getDocument();
return new Object[]{domDocument};
}
// 对于遵守xml dom规范的node,则按照xml node的结构来遍历
if (parentElement instanceof IDOMNode) {
List children = new ArrayList();
NamedNodeMap attributes = ((IDOMNode)parentElement).getAttributes();
if (attributes != null ) {
for ( int i = 0 ; i < attributes.getLength(); i ++ ) {
children.add(attributes.item(i));
}
}
NodeList childNodes = ((IDOMNode)parentElement).getChildNodes();
for ( int i = 0 ; i < childNodes.getLength(); i ++ ) {
children.add(childNodes.item(i));
}
return children.toArray();
}
return new Object[ 0 ];
}
// 其他方法省略
}
/*
* IStructuredModel分为两种:IDOMModel和ICSSModel,对应的document实现分别为IDOMDocument和ICSSDocument。
* 我们只分析IDOMModel(IDOMDocument)的情况,对于ICSSModel(ICSSDocument)的分析留给大家吧^_^
*
* @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
*/
public Object[] getChildren(Object parentElement) {
if (parentElement == null )
return new Object[ 0 ];
// 如果是IDOMModel,则获取对应的IDOMDocument
if (parentElement instanceof IDOMModel) {
IDOMDocument domDocument = ((IDOMModel)parentElement).getDocument();
return new Object[]{domDocument};
}
// 对于遵守xml dom规范的node,则按照xml node的结构来遍历
if (parentElement instanceof IDOMNode) {
List children = new ArrayList();
NamedNodeMap attributes = ((IDOMNode)parentElement).getAttributes();
if (attributes != null ) {
for ( int i = 0 ; i < attributes.getLength(); i ++ ) {
children.add(attributes.item(i));
}
}
NodeList childNodes = ((IDOMNode)parentElement).getChildNodes();
for ( int i = 0 ; i < childNodes.getLength(); i ++ ) {
children.add(childNodes.item(i));
}
return children.toArray();
}
return new Object[ 0 ];
}
// 其他方法省略
}
以上基本上就是本视图的主要代码了,开发这个视图代码基本上也是300行左右。
本插件工程需要依赖的插件列表为:
org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.wst.sse.core,
org.eclipse.wst.xml.core,
org.eclipse.wst.sse.ui,
org.eclipse.jface.text,
org.eclipse.ui.workbench.texteditor
【源码下载】
源码为实际工程以Export ---> Archive File方式导出的,下载链接: Structured Model(Dom Document)分析视图源码
本博客中的所有文章、随笔除了标题中含有引用或者转载字样的,其他均为原创。转载请注明出处,谢谢!