公司介绍 产品展示 业务支持 解决方案 文档资料
网站导航 访客留言 技术论坛
     
     
  产品资料
  业界动态
  JAVA技术


首  页 > 文档资料 > 产品资料  
 
  插件篇:实现Taglib的代码自动提示

Taglib在JSP的开发中扮演很重要的角色,许多框架都会使用到这项技术,如Struts、Webwork2、SpringFramework等,相信很多的公司和个人都有一套自己的Taglib,方便日常JSP的开发。很多工具,包括IntelliJ IDEA,能够很好地进行Taglib的Tag和属性的提示,但是却很少能够进行Tag的属性值提示。下面让让我们看看在IDE工具中使用Webwork2的Taglib的情况。

IDEA可以自动提示Tag及Tag的各个属性名称:

但是IDEA却不能依据Tag和属性名称进行属性值的提示,如:

这些属性值通常都有一定的含义的,如引用Java类名、相关的资源文件、一定的取值范围、ResourceBundle的key等等。如果IDEA能够进行这样有意义的代码提示,同时支持导航功能,那将会为我们的编码提供更多的便捷。下面就让我们看看如何在IntelliJ IDEA下编写一个插件来完成这样的功能。

在编程进行之前,让我们看看实现的机理:

IntelliJ IDEA通过PSI来管理文档模型,文档中的每一个元素都是特定的类型,在这里我们只需将特定的属性值和PsiReferenceProvider关联起来,由PsiReferenceProvider提供相关的背景信息,然后通过ReferenceProvidersRegistry进行注册,这样可以实现代码提示和导航。 下面我们就看一下如何实现Webwork2中ww:include Tag的page属性值的自动提示。

首先创建一个ProjectComponent,代码如下:


public class AssistantProjectComponent implements ProjectComponent
{
private Project project;
private ReferenceProvidersRegistry registry;
private NamespaceFilter webwork2NamespaceFilter = new NamespaceFilter(new String[]{"http://www.opensymphony.com/tags/webwork2"});

public AssistantProjectComponent(Project project)
{
this.project = project;
}

@NonNls
public String getComponentName()
{
return "CodeAssistant_Taglib";
}

public void initComponent()
{
registry = ReferenceProvidersRegistry.getInstance(project);
registerCompleteIncludeAssistant ();
}

private void registerCompleteIncludeAssistant()
{
registry.registerXmlAttributeValueReferenceProvider(
new String[]{"page"},
new ScopeFilter(new ParentElementFilter(new AndFilter(new ClassFilter(XmlTag.class), new AndFilter(new OrFilter(new ElementFilter[]{new TextFilter("include")}), webwork2NamespaceFilter)), 2)),
new URIReferenceProvider());
}
}

首先我们基于Project对象创建一个ReferenceProvidersRegistry对象,下一步我们就需要注册相关的ReferenceProvider,这里我们调用registerXmlAttributeValueReferenceProvider方法,将属性值和URIReferenceProvider关联起来。URIReferenceProvider是IDEA自带的类,主要是进行各种资源引用的提示,如jsp,image等文件信息及路径信息。这里有一个NamespaceFilter类,这里我们可以将Taglib的url理解为xml中的namespace,通过各种filter结合,来确定适用的namespace和tag,然后再结合属性名称,这样就可以将属性值和某个ReferenceProvider关联起来。这个例子中,我们将 http://www.opensymphony.com/tags/webwork2 这个namespace下的include Tag的page属性值和URIReferenceProvider关联起来,运行这个插件,就可以看到如下的效果:

这时就方便了许多,同时你可以根据属性值快速定位到指定的文件。如果引用的文件不存在,IDEA还会提示你错误,彻底告别了Ctrl+C、Ctrl+V等烦人操作,如果你对资源进行重构操作,如rename、move,这里同样可以更新。

IntelliJ IDEA内置了很多ReferenceProvider,如java类、ResourceBundle的key引用、CSS等,用好这些内置的Provider会给你插件提供很好的功能。

如何去创建一个自定义的Provider,实现个性的提示呢?下面我们就以ww:set的scope属性为例子做一说明。 scope的属性值属于一定范围,只能为page, requestion, session和application,下面我们就要实现这样的提示:

创建一个类,代码如下:


public class VariableScopeReferenceProvider extends BaseReferenceProvider
{
protected RangeReference[] createReferencesByStringImpl(String string, PsiElement psiElement, ReferenceType referenceType, int i)
{
return new MyReference[]{new MyReference(string, psiElement)};
}

public class MyReference implements RangeReference
{
private String string;
private PsiElement psiElement;

public MyReference(String string, PsiElement psiElement)
{
this.string = string; this.psiElement = psiElement;
}

public void shiftRight(int i)
{

}

public void expandRight(int i)
{

}

public PsiElement getElement()
{
return psiElement;
}

public TextRange getRangeInElement()
{
return new TextRange(1, psiElement.getText().length());
}

@Nullable public PsiElement resolve()
{
return null;
}

public String getCanonicalText()
{
return string;
}

public PsiElement handleElementRename(String string) throws IncorrectOperationException
{
return psiElement;
}

public PsiElement bindToElement(PsiElement psiElement) throws IncorrectOperationException
{
return null;
}

public boolean isReferenceTo(PsiElement psiElement)
{
return false;
}

public Object[] getVariants()
{
return new Object[]{"page", "request", "session", "application"};
}

public boolean isSoft()
{
return true;
}
}
}

这里我们只需继承BaseReferenceProvider,然后实现该抽象类的抽象方法即可。这里我们作一说明:

  • getRangeInElement方法需要按照以上的代码编写即可。
  • resolve方法主要是为定位资源服务,如果属性值表示为一定的资源引用,可以实现该方法来实现定位。
  • getVariants方法返回代码提示的列表,也就是弹出的代码提示窗口。

实现以上的方法后,我们只需调用ReferenceProvidersRegistry注册一下该Provider即可,代码如下:


registry.registerXmlAttributeValueReferenceProvider(
new String[]{"scope"},
new ScopeFilter(new ParentElementFilter(new AndFilter(new ClassFilter(XmlTag.class), new AndFilter(new OrFilter(new ElementFilter[]{new TextFilter("set")}), webwork2NamespaceFilter)), 2)),
new VariableScopeReferenceProvider());

重新运行这个插件,我们就可以看到如下效果:

这样的代码提示会方便许多,而且不容易出错。这只是一个Demo,你可以增强其功能以满足你的需要,代码提示菜单的数据可以来自其他地方,如Spring的Bean名称,ActionForm的属性,Hibernate PO的属性等等,都由你去发挥。

总结:如果你的框架很流行,或者是公司内基础的Taglib,编写一个这样的插件将非常方便他人使用,避免犯错,提供开发效率。以上的机理同样适用于xml、html文档的代码提示,如果你的程序包含相关xml配置文件,象Spring、Struts等,编写这样的插件同样非常实用,如果你想扩展IDEA的HTML编辑功能,如整合Tapstry提示等等,这些都是可能的,而且不复杂。如果你想更好地扩充其功能,了解一下PSI,将有助于你开发出有思想的优质插件。