Linking java string literals and some xml elements so that "Go to Declaration" on a literal goes to an element in xml

Say, I have some java code like

actionsPerformer.perform("foo.bar");


And some arbitrarily named xml file/files with contents like

<actions id="foo">
    <action id="bar" attribute1="ok" attribute="lol"/>
    ...
</actions>


I want to write a plugin that enables jumping to an action declaration from my java code, i.e. I click Ctrl+B/Ctrl+Click ("Go to Declaration") when my cursor is inside "foo.bar" string literal and jump right into declaration of action with attribute id="bar" that is located inside actions with id="foo".

What extension points and what approach should I use? Maybe you can point at some code I can get inspiration from.

9 comments

You need to use the PsiReferenceContributor and PsiReferenceProvider APIs for this purpose. You can find plenty of examples of their usage in the CE source code.

0

Ok, looks like I must treat my string literals as references to some dom elements. Now the challenge is to create a reference to a dom element that is located in an unknown location.

0

max.ishchenko wrote:

Now the challenge is to create a reference to a dom element that is located in an unknown location.

Actually creating a reference is not the challenge, and it is fully ok that the location is unknown. It is not until resolve() is called that you need to find the location (i.e. when the challenge begins ;)).

0

That's right. Maybe it's easier to start coding, but I had no time to go deeper yet, thus I will discuss it here )

Looks like I have two options here:
1) Perform some kind of global dom element search, using a filter/callback that will accept only elements that reside in a certain namespace, have certain parent element, have a certain name and certain attribute value.
2) Somehow prepare my dom elements when IDEA's parser parses them and store them in a known location, so that no global search is performed, but perform a simple map lookup using identifier. But that smells like a premature optimization )

0

There are a couple approaches that fit different scenarios. For instance, if your xml file(s) are located in a specific location I would go for traversing the PSI of each file with an XmlRecursiveElementVisitor in order to find candidates. But if the files are spread all around a global name search might be easier using PsiSearchHelper..

For better efficiency, make sure your reference implements CachingReference or PsiPolyVariantCachingReference. And avoid premature optimizations until you know you need it ;)

0

I have successfully implemented naive approach, i.e. using XmlRecursiveElementVisitor and traversing module source roots to find the element in question. That works well for a small project, but is extremely slow in real-life project with huge source tree. I guess I should think of using DomModel and DomModelFactory infrastructure to prepare my dom elements and search in some prebuilt "view" on my xml file set.

0

Ok, another little step done, I am providing a custom DomModel for my configuration files. This is still superslow when run for the first time, but the model generated is cached, and "Go to Declaration" works fast for subsequent invocations, but when you change your xmls, the cache gets resetted and next "Go to Declaration" will be as slow as the first one.

For now on, I will not claim that I want to go to some abstract xml tags, in fact I want to go to iBatis mappings )) Looks like the old iBatis/MyBatis plugin is not supported, in recent versions it throws tons of exceptions, plus won't support multiple sqlmap configurations per module and won't Go to Declaration from arbitrary method parameters (say, spring's SqlTemplateClientOperations). I tried to fix the current one, I even succeeded in eliminating some exceptions, but it is way too big and some features I need are not present there. Thus, I am writing my own plugin for my project needs. Currently it can "Go to Declaration" from string literals inside method calls that look like "Namespace.identifier", where "Namespace" is your <sqlMap id="Namespace">, and "identifier" is your <sql id="identifier">, or <select id="identifier"> etc that is located inside this sqlMap. Plus, autocompletion for such literals. And it works like a charm for sample project, but not for real life project with 239 (sic) sqlmaps.
Have a look at https://github.com/ishchenko/idea-mini-ibatis Any ideas how to improve performance?

0

Ok, I asked - I replied.

Dramatically optimized the speed, using com.intellij.util.xml.DomService#getFileElements to iterate over sqlMap files instead of visiting all source roots. Autocompletion variants generation ~500ms for 250 sqlMap files, each containing 20 statements. Looks like that is fast enough, but looping over all files and all statements each time ctrl+space is pressed in target literals seems to me too straigtforward. Must find a way to cache variants until any of my sqlMap files change. I will file one more post then.

0

you can maybe do a full scan once and watch for file changes of those files by using filewatcher and update your caches
something like:
Foo implements ApplicationComponent, BulkFileListener{
// ..implement listener
}

and register it wihin plugin.xml

<application-components>
    <component>
      <implementation-class>Foo</implementation-class>
      <interface-class>Foo</interface-class>
    </component>
  </application-components>

0

Please sign in to leave a comment.