Stub索引

走着路睡觉大约 6 分钟

Stub索引

Stub Trees

Stub Tree是PSI tree的子集,以紧凑的二进制格式进行存储。PSI tree有2种格式,一种是AST(Abstract Syntax Tree:抽象语法树,解析文件构建而成),别一种是从硬盘上反序列化的stub tree , 这2种格式可以互相切换。

Stub Tree只包含一部分节点。通常,它只包含外部文件有访问权限的(public protected default修饰的) 的节点。如果访问Stub Tree不包含的节点,或访问像 PSI element的文本Stub tree不支持的操作,都会引起文件的解析由PSI解析切换到 AST backing。

Stub Tree中的每个stub都是一个没有任何行为的bean class 。stub存储了相关PSI Element 相关信息的子集(例如名称,public,final等),还能获取到它的父子stubs

当你想要 stubs 支持你自定义的编辑语言时,你需要确定PSI tree 中的哪些元素需要被存储进 Stubs。通常,Stubs 需要存储外部文件有访问权限的(public protected default修饰的) 方法和成员变量,不需要存储外部文件无访问权限的局部变量,private修改的方法和变量等元素

实现Stub索引

一个编程语言想要支持stubs ,需要执行以下几步:

  <extensions defaultExtensionNs="com.intellij">
        <!--  JavaStubElementTypes 是一个接口, 包含了 编程语言解析器所用到的所有 IElementType 常量  -->
        <!--  externalIdPrefix:编辑语言中所有stub element type使用的公共前缀  -->
        <stubElementTypeHolder class="com.intellij.psi.impl.java.stubs.JavaStubElementTypes" externalIdPrefix="java."/>
    </extensions>

对于要存储在Stub tree中的每种Element type ,你需要参考下面示例的实现步骤:

  1. 定义 stub 接口 PropertyStubopen in new window ,该接口需要继承StubElementopen in new window

  2. 定义实现类PropertyStubImplopen in new window ,该接口需要实现上一步定义的接口

  3. 定义 PSI Element接口 Propertyopen in new window , 该接口需要继承泛型为PropertyStub(第一步定义的接口)StubBasedPsiElementopen in new window 接口。

  4. 定义实现类PropertyImplopen in new window ,该类需要实现 Property接口(第3步定义的接口) ,同时也要继承泛型为PropertyStub(第一步定义的接口)StubBasedPsiElementBaseopen in new window 接口。另外该实现类需要提供2个构造器,一个参数为 ASTNode 的构造器,和一个参数为 PropertyStub(第一步定义的接口) 的构造器 。

  5. 定义 PropertyStubElementTypeopen in new window ,该类需要继承泛型为 PropertyStub(第一步定义的接口)Property接口(第3步定义的接口)IStubElementType接口 (ILightStubElementType<PropertyStub, Property> ),实现创建PSI的 createPsi() 方法和创建stub的createStub() 方法,实现用于序列化存储serialize() 方法和反序列化的deserialize() 方法

  6. 解析代码的时候,使用 IStubElementType 的子类 PropertyStubElementType(第5步定义的) 作为 element type 常量。参考示例 PropertiesElementTypesopen in new window

  7. 实现 Property.getKey()(示例:PropertyImpl.getKey()open in new window )方法, 确保 PSI element 接口中的所有方法在适当的时候访问stub数据 而不是 PSI tree

提示

如果使用 Grammar-Kitopen in new window 来生成编程语言的 PSI ,可以通过 Stub indices supportopen in new window 文档了解如何让你的语法整合 stub

默认情况下,所有继承了 StubBasedPsiElementPSI element都会存到 stub tree 。如果不需要存储某类型元素,可以在该类型元素中重写 IStubElementType.shouldCreateStub() 方法并返回 false ,这个策略只作用于元素自身,如果该PSI元素的子元素继承了 StubBasedPsiElement,该子元素依然会存储到stub tree 中。

序列化数据

推荐使用 StubOutputStream.writeName()StubInputStream.readName() 方法来序列化元素名称等String类型的数据。这些方法可以保证在数据流中同一个数据只存储一次,从而减少 stub tree的占用空间。也可以参考 DataInputOutputUtilopen in new window

如果需要改变存储Stub的二进制格式(例如:想存储额外的数据或一些新元素),你需要升级通过 IStubFileElementType.getStubVersion() 获取到的 stub 版本。版本升级后,为了避免 Stub 和项目的代码不匹配,会重新构建Stubs和Stub索引

必须要保证在 stub tree 中存储的数据只依赖于stub的文件的内容,不依赖于任何外部文件。这是因为外部文件更新后,stub tree 并不会重新构建,从而导致stub tree 中数据不准确。

Stub索引

在构建 stub tree 的同时,可以把 stub elements 的相关数据存储到其它索引里,这些索引就可以用来查找 PSI elements 。和文件索引不一样,stub索引的value不支持存储自定义数据,只会存储 PSI elements 。stub索引中的key一般都是String类型(例如使用类名),如果需要,也可以使用其它类型。

stub index的类必须继承 AbstractStubIndexopen in new window 。但是,大多数情况下,如果 stub index 的key的类型是string,可以通过继承 StringStubIndexExtensionopen in new window 来实现 ,然后在 plugin.xml 里注册为 com.intellij.stubIndex 类型的扩展。

向某个Stub索引中存储数据,需要实现 IStubElementType.indexStub() 方法 (示例:JavaClassElementType.indexStub()open in new window )。该方法接收 IndexSink 参数,并存储每一个需要存储的元素的key和 索引id

可以使用下面2个方法来访问stub索引:

  • AbstractStubIndex.getAllKeys() :可以获取指定索引,指定项目的所有key (例如:获取项目中所有类的名称)

  • AbstractStubIndex.get() :可以获取指定作用域,指定key(key通常为类名)的 PSI elements集合

相关论坛

Lifecycle of stub creationopen in new window

上次编辑于:
贡献者: zhaojingbo
Loading...