【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

WTP数据模型总结和模型管理

前面已经详细介绍过WTP语法Document(IStructuredDocument)、WTP语义Document(IDOMDocument 或ICSSDocument)和WTP Model(IStructuredModel),在本节中将从总体上再看一下对我们后续基于 WTP进行代码定制很重要的点,同时将补充最核心的一个点:WTP中的模型管理机制。

PS:如果前面的几节是探微的过程,那边本节将完成知著的过程,“探微知著”^_^

【语法Document、语义Document、WTP Model】

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

(说明:上图中的实线可以理解为引用关系。)

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

【从引用关系层面看】

1、从上图一可以看的出来,IStructuredDocument并不引用IStrcuturedModel或者IDOMDocument(或 ICSSDocument),也就是说IStructuredDocument本身并不关心IStrcuturedModel或者IDOMDocument(或 ICSSDocument)的存在

2、结合上图一和上图二,可以看的出来IStructuredModel将IStructuredDocument和IDOMDocument( 或ICSSDocument)作为其两个组成部分。换个角度说,如果已知IStructuredModel存在的情况下, IStructuredModel可以作为三者的门面,对IStructuredModel进行管理也就间接对IStructuredDocument 和IDOMDocument(或ICSSDocument)进行管理

3、我们解释一下上图一中的那条蓝色虚线,我们已经说过IStructuredDocument本身并不关心 IStrcuturedModel或者IDOMDocument(或ICSSDocument)的存在,所以要想以IStructuredDocument获取 对应的IStructuredModel,需要一个第三方的角色,来维护从IStructuredDocument到IStructuredModel 的映射关系,这个角色就是后面要说的WTP提供的IModelManager。如果IStructuredDocument通过 IModelManager获取到了对应的IStructuredModel,那么再通过IStructuredModel可以自然获取到对应的 语义Document(IDOMDocument或者ICSSDocument)。这样三者就完全联系起来了^_^

我们看一下,以上的引用关系对应的API接口是什么(需要熟练掌握):

1、WTP Model --》 语法Document

IStructuredModel.getStructuredDocument()

2、WTP Model --》 语义Document

IDOMModel.getDocument  返回的语义Document类型为IDOMDocument

ICSSModel.getDocument   返回的语义Document类型为ICSSDocument

3、语义Document --》WTP Model

IDOMNode.getModel      (IDOMDocument本身就是IDOMNode^_^)

ICSSDocument.getModel

4、语法Document --》 WTP Model

前提:对应的IStructuredModel已经被WTP提供的IModelManager托管了,否则是没有意义的(前面说 过,因为IStructuredDocument并不关心IStructuredModel和语义Document是否存在)

IModelManager.getModelForEdit(IStructuredDocument)

IModelManager.getModelForRead(IStructuredDocument)

5、语法Document --》语义Document

前提:对应的IStructuredModel已经被WTP提供的IModelManager托管了!!!

步骤:语法Document --》 WTP Model --》 语义Document

6、语义Document --》语法Document

方法一:IDOMNode.getStructuredDocument

方法二:语义Document --》 WTP Model --》 语法Document

PS:通过上面的5和6也可以体会到,WTP Model存在的情况下,完全可以在语法Document和语义 Document之前起到一个桥梁的作用

【从依赖关系层面看】

1、通过上图二IStructuredModel的构造过程就可以看的出来,WTP Model和语义Document (IDOMDocument或者ICSSDocument)都是以IStructuredDocument为基础,也就是说依赖于它

2、语法Document(IStructuredDocument)不需要关心其他两者是否存在

3、语义Document(IDOMDocument或者ICSSDocument)依赖于语法Document(IStructuredDocument)

4、一个完整的WTP Model必须有对应的语法Document和语义Document,从这个意义上将,可以理解为 WTP Model需要依赖语法Document和语义Document。

由上面四点,我们可以简要的画一个三者之间的依赖关系图:

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

(依赖关系图:上图中的实线可以理解为依赖关系)

【从动态变化角度看(简要了解一下就可以了^_^)】

上面我们讲了这么多三者之间的关系,好像总感觉是从静态的角度出发的,那么如果三者中的一者发 生变化了,三者直接又会又什么样的互动呢?

1、WTP Model作为变化源,变化情况如下:

IStructuredModel引发的变化,通常情况下就是调用了IStructuredModel的reload和reinit的操作。 这个操作会修改其持有的IStructuredDocument,引起IStructuredDocument改变事件。 IStructuredModel本身又是一个IStructuredDocumentListener,所以会处理这种变化,在变化的处理过 程中包含了修改其持有的语义Document。大致过程示意图如下:

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

2、IStructuredDocument作为变化源,变化情况如下(这将是我们最常见的情况):

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

3、IDOMDocument作为变化源,变化情况如下:

//获取语义Document
IDOMDocument domDocument = ((IDOMModel) structuredModel).getDocument();
//修改语义Document,例如删除其第一个节点
Node node = domDocument.getChildNodes().item(0);
domDocument.removeChild(node);

以上代码会发生如下事情:首先DOM Document被修改,然后会回调DOMModelImpl中对应的更新方法( 例如DOMModelImpl.childReplaced),然后会调用一个XMLModelUpdater的角色,在这个 XMLModelUpdater会去更新IStructuredDocument(replace text操作),这进而会引发 IStructuredDocument改变事件,会进而进入上面已经阐述过的循环。大致示意图如下:

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

说明:语义Document(IDOMDoucment或者ICSSDocument)持有一个IStructuredModel的引用,所以才 有了上图中的回调。可以看的出来语义Document(IDOMDoucment或者ICSSDocument)并没有提供对应的 listener接口,采用的是直接回调的办法。

上面三个动态变化需要经常使用这几个WTP数据模型才能有比较深的印象,考虑到确实有点繁琐,所 以就不做代码分析了,这里留个大概印象就可以了。

提醒:IStructuredModel对应的三个子类(AbstractStructuredModel、DOMModelImpl和 CSSModelImpl)分别都有IStructuredDocumentListener实现,有时间可以看一下这些实现之间的差别, 对加深WTP数据模型的认识会有帮助。

【IModelManager!!!】

(IModelManager:org.eclipse.wst.sse.core.internal.provisional.IModelManager)

【IModelManager为什么存在?】

我们先来考虑几个问题:

1、无论是语法Document还是语义Document的实例化过程(可不是new一个实例那么简单^_^)都十分 繁琐,这个实例化的活是留给客户调用端还是提供一个负责实例化的中间角色(客户端通过调用这个中 间角色来完成实例化的工作)?

答案:肯定尽量选择后者。将客户端和对象的繁琐实例化过程进行解耦,是一个我们应该尽量遵循的 规则。这个封装了实例化过程的中间角色就是IModelLoader: org.eclipse.wst.sse.core.internal.provisional.IModelLoader)和IDocumentLoader (org.eclipse.wst.sse.core.internal.document.IDocumentLoader),我可以宽泛地将这个中间角色 理解为工厂,而且实例化过程可能有变化,所以这个工厂不能是一个简单静态工厂,而应该是一个工厂 方法应用(为什么不是抽象工厂,因为不是创建相关的系列实例,也谈不上什么几个产品系列^_^)。

2、一个WTP Model同时持有一个重量级的语法Document和语义Document,这个两个Document无论是在 时间占用(解析过程十分耗时),还是在内存占用方法都比较客观。那么,如果我们对WTP Model实例进 行缓存管理,在内存占用可接受的情况下可以大大解决时间占用的性能瓶颈问题,不挺好吗?

答案:是挺好的^_^。 IModelManager一部分任务就是干这个事情。注意这边的缓存管理可不简单就 是将对象存储下来这么简单,也需要提供用于更新被缓存对象的方法。

3、如果提供能够将上面两个角色组合在一起,对一般用户而且使用起来是不是会更方便一点?

答案:是的。但是有点不太优雅,职责有点混淆,不过问题不是很丑陋^_^。

回答完以上三个问题之后,我们可以猜测出来IModelManager承担的两个个核心任务:

1、创建型工厂(IDocumentLoader、IModelLoader)的门面,提供创建接口

2、缓存管理:包含缓存实例的存储管理和更新维护等任务。

【IModelManager创建职责】

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

说明:

1、上图中包含了创建IStructuredModel、IStructuredDocument的方法,并没有提供创建语义 Document(IDOMDocument或者ICSSDocument)的方法。为什么?我们前面说过IStructuredDocument可以 并不关心IStructuredModel或者语义Document是否存在,所以可以允许独立创建;而语法Document和语 义Document是IStructuredModel的两个必然组成部分,所以提供了IStructuredModel的创建方法,就间 接提供了语义Document的创建服务。

2、以上创建方法返回的实例都是未经托管的,每次都会创建一个新的实例。千万不要误认为 IModelManager提供的创建方法返回的实例都是缓存的!!!^_^  前面说过,WTP提供的语法Document 和语义Document在时间占用和内存占用方面都是比较可观的,小心!!!!!!!!!!!!!!

3、注意createStructuredDocumentFor(IFile iFile)和createNewStructuredDocumentFor(IFile iFile)两个方法的正确用法,前者基于iFile存在,后者基于iFile不存在。

【IModelManager管理职责】

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

说明:

1、getModelFor*不假定模型已经被托管,没有对应的模型情况下会创建一个新的被托管的实例(但 是不能以IDocument为参数);getExistingModelFor*假设有对应的被托管模型存在于IModelManager种 ,否则返回null,不创建新的被托管实例。

2、getModelFor*和getExistingModelFor*都涉及到引用计数的概念,每次调用都会增加read或者 editor计数,使用完毕之后应该调用IStructuredModel.releaseFromEdit或者 IStructuredModel.releaseFromRead。

3、IModelManager并没有提供IStructuredDocument的获取接口,因为IModelManager管理的目标只是 IStructuredModel。IStructuredDocument虽然可以脱离IStructuredModel独立存在,但是 IModelManager不提供管理。

4、使用getModelFor*(IDocument)和getExistingModelFor*(IDocument)的前提必须是有对应的 IStructuredModel被托管了。示例代码:

try {
IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path("/project/WebContent/Test2.jsp"));
IStructuredDocument document = StructuredModelManager.getModelManager().createStructuredDocumentFor(file);
Object model1 = StructuredModelManager.getModelManager().getModelForRead (document);
Object model2 = StructuredModelManager.getModelManager ().getExistingModelForEdit(document);
} catch (Exception e) {
e.printStackTrace();
}

如果上面代码种的file代表的资源没有对应的IStructuredModel被托管,则model1和model2的获取都 会引发异常。如果调用getModelFor*(IFile)或 getExistingModelFor*(IFile)则不会出现问题。

PS:IModelManager的其他操作就不一一列举了,具体可以看一下对应代码。IModelManager的获取方 式上面代码中已经体现:org.eclipse.wst.sse.core.StructuredModelManager中提供了 getModelManager操作来获取IModelManager实例。

【使用WTP IModelManager一定要注意的地方】

1、根据你的模型是否需要被缓存托管,判断该调用什么方法。如果不需要被托管的模型却被缓存托 管了,则会大大增加内存占用。

2、注意维护引用计数平衡

【语法Region VS 语义Region】

语法Document(IStructuredDocument)提供了语法Region(ITextRegion)的概念,语义Document( IDOMDocument或者ICSSDocument)对应的是语义Region(IndexedRegion、IDOMNode、ICSSNode),那我 们现在来看一下它们之间的区别和联系。

【区别】

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

显而易见,语法Region(ITextRegion)是按照语法进行划分的,既然是structured region,那么划 分时候一个重要的判断依据就是特定的文本是否是结构化的。IStructuredDocumentRegion代表的就是一 个结构化的region,里面会含有一系列的叶子节点的text region。IStructuredDocumentRegion之间并 不会呈现父子关系,例如父子标签之间是独立的IStructuredDocumentRegion,因为这个父子是从语义层 面才有意思。

【Eclipse插件开发】基于WTP开发自定义的JSP编辑器(七)

语义Region(IndexedRegion、IDOMNode、ICSSNode)则是按照语义进行划分的,体现的语义层面的 包含关系。例如,子标签会作为一个child node(IDOMElement)存在于父标签中(同样是一个 IDOMElement);再例如一个IDOMAttr表示一个属性,如果切换到语法region视角,则对应于三个 ITextRegion:AttributeNameRegion、AttributeEqualsRegion、AttributeValueRegion。

一句话,根据应用场景的不同你可以选择借助语法region进行分析或者借助语义region进行分析。例 如:如果要判断一个标签是否在其特定父标签中,则用语义region进行分析会方便很多^_^。

【联系】

那我们如何将语法Region和语义Region比较方便的联系起来呢?

答案:offset(位置信息)!!!

基于语法Document构建语义Document,说白了就是把语法region列表重新组织为语义region列表,语 法region本身就持有位置信息,语义region会持有对应的语法region,所以语义region本身也可以提供 位置信息了。前面曾经说过,所有的语义region都是IndexedRegion接口的实现,IndexedRegion定义的 核心操作也就是获取位置信息的。

根据offset信息可以定位到对应的语法region或者语义region,涉及到的方法前面已经在讲述相关接 口的时候讲述过,这边就不再重复了。(这些东西用用就熟悉了^_^)

IndexedRegion IStructuredModel.getIndexedRegion(int offset)

IStructuredDocumentRegion  IStructuredDocument.getRegionAtCharacterOffset(int offset)

PS:如果你持有的是一个语义region,则可以直接根据语义region去获取其引用的语法region。

【后记】

到目前,WTP数据模型相关的东西真的告一段落了,其实这并不是WTP数据模型的全部,后门我们在定 义具体功能的时候会顺便再讲一下其他的数据模型,我们可以把那些模型称之为元数据模型,很多其实 就是再IStructuredModel基础之上再提供额外的描述信息。