XmlTagValue reference to Java methods

I have read up on most of all that I could find for creating a PsiReference
for a given XmlTagValue that refers to a Java method (think XML configuration
files for a given Java class), and can't seem to find a solution.

Since the XmlTagValue does not inherit from XmlElement, I called XmlTagValue.getTextElements()
to get a an array containing XmlText's (which does inherit from XmlElement,
and thus have a getReference()). The first (and lesser important) issue
that I see here is that I don't understand the reasoning for not including
an XmlTagValue as a sub-interface of XmlElement.

The big issue is that I don't know what I need to do next to create a reference
of my XmlText (null from the start for a reason). I looked at the ReferenceProvidersRegistry
class, and it has a similar desireable function called registerXmlAttributeValueReferenceProvider();
however, this only works for XmlAttribute's, and not an XmlTagValue / XmlText.
I have read elsewhere on the newsgroup that I can create a subclass of PsiReference,
and within the resolve() function it returns the desired Java method (PsiMethod).
There are already some subclasses that are available, but being that they
are not documented I have no idea which I could possibly use (or even if
I'm supposed to use them).

Then once the reference is created, which function should I be calling within
the ReferenceProvidersRegistry? Again, hardly any documentation for this
so it's hard for me to take an educated guess without taking a divide and
conquer approach to the situation (which hasn't worked thus far).

Additionally, are there any plans to improve the current documentation for
some of the non-"Open API" functionality (e.g., stuff seen in idea.jar) that
I seem to have to rely upon frequently with my in-house XML plugin?

Thanks,
Mike


14 comments

It's more like a visitor pattern. You should use
ReferenceProvidersRegistry when your plugin is loaded (in initComponent
or projectOpened or something), which creates a new instance of
PsiReference whenever asked to do so. You should implement PsiReference
directly.

The point of the open API vs. closed API is that the open API is not
documented and is only available if you want to get dirty.

Michael McCullough wrote:

I have read up on most of all that I could find for creating a
PsiReference for a given XmlTagValue that refers to a Java method (think
XML configuration files for a given Java class), and can't seem to find
a solution.

Since the XmlTagValue does not inherit from XmlElement, I called
XmlTagValue.getTextElements() to get a an array containing XmlText's
(which does inherit from XmlElement, and thus have a getReference()).
The first (and lesser important) issue that I see here is that I don't
understand the reasoning for not including an XmlTagValue as a
sub-interface of XmlElement.
The big issue is that I don't know what I need to do next to create a
reference of my XmlText (null from the start for a reason). I looked at
the ReferenceProvidersRegistry class, and it has a similar desireable
function called registerXmlAttributeValueReferenceProvider(); however,
this only works for XmlAttribute's, and not an XmlTagValue / XmlText. I
have read elsewhere on the newsgroup that I can create a subclass of
PsiReference, and within the resolve() function it returns the desired
Java method (PsiMethod). There are already some subclasses that are
available, but being that they are not documented I have no idea which I
could possibly use (or even if I'm supposed to use them).

Then once the reference is created, which function should I be calling
within the ReferenceProvidersRegistry? Again, hardly any documentation
for this so it's hard for me to take an educated guess without taking a
divide and conquer approach to the situation (which hasn't worked thus
far).

Additionally, are there any plans to improve the current documentation
for some of the non-"Open API" functionality (e.g., stuff seen in
idea.jar) that I seem to have to rely upon frequently with my in-house
XML plugin?

Thanks,
Mike

0

Michael McCullough wrote:

Since the XmlTagValue does not inherit from XmlElement, I called
XmlTagValue.getTextElements() to get a an array containing XmlText's
(which does inherit from XmlElement, and thus have a getReference()).
The first (and lesser important) issue that I see here is that I don't
understand the reasoning for not including an XmlTagValue as a
sub-interface of XmlElement.


This is correct: XmlTagValue is a wrapper for the possibly several XmlText elements inside
the XmlTag, it provides access to the raw and trimmed text content, etc. Consider it as a
"view" on the XmlTag's text content.

The big issue is that I don't know what I need to do next to create a
reference of my XmlText (null from the start for a reason). I looked at
the ReferenceProvidersRegistry class, and it has a similar desireable
function called registerXmlAttributeValueReferenceProvider(); however,
this only works for XmlAttribute's, and not an XmlTagValue / XmlText.


You need to call registry.registerReferenceProvider(XmlTag.class, provider);
The Class-parameter tells IDEA for what kinds of PsiElements it should call your provider.
It seems XmlTag is the value you need here, e.g. IDEA does not query providers for XmlText.

I have read elsewhere on the newsgroup that I can create a subclass of
PsiReference, and within the resolve() function it returns the desired
Java method (PsiMethod). There are already some subclasses that are
available, but being that they are not documented I have no idea which I
could possibly use (or even if I'm supposed to use them).


You should probably create your own PsiReference implementation - it's quite simple
anyway, there are not many methods to implement and there's some decent Javadoc for
PsiReference.

Then once the reference is created, which function should I be calling
within the ReferenceProvidersRegistry? Again, hardly any documentation
for this so it's hard for me to take an educated guess without taking a
divide and conquer approach to the situation (which hasn't worked thus
far).


It's the other way around: You register a ReferenceProvider that can create ("inject") one
or more references into a certain element. Here you instantiate and return your
PsiReference implementation that "sits" on this element.

Additionally, are there any plans to improve the current documentation
for some of the non-"Open API" functionality (e.g., stuff seen in
idea.jar) that I seem to have to rely upon frequently with my in-house
XML plugin?


AFAIK the completion- and reference-API will be opened/revamped in Demetra. There's no
documentation for non-OpenAPI stuff, it's officially unsupported and you're totally on
your own in that area. It's the stuff for the dirty, but cool things in IDEA ;)

You're lucky though, I've been in the "let's try it" mood and have attached a very
simple example class that might give you a point where to start from. :)
No guarantees, use at your own risk. Known issue: IDEA does not look for usages of methods
in XML files when renaming them!

Have fun.

Sascha
package org.intellij.plugins;

import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.PsiReferenceProvider;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import com.intellij.psi.impl.source.resolve.reference.ReferenceType;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlTagValue;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

// Registers simple references to XML tags that look like this:
//
// // equals // toString // finalize // public class ReferenceDemo implements ProjectComponent { public ReferenceDemo(final PsiManager psiManager, ReferenceProvidersRegistry registry) { registry.registerReferenceProvider(XmlTag.class, new PsiReferenceProvider() { @NotNull public PsiReference[] getReferencesByElement(PsiElement psiElement) { final XmlTag tag = (XmlTag)psiElement; if (tag.getLocalName().equals("method") && tag.getNamespace().equals("java")) { final String name = tag.getParentTag().getAttributeValue("name"); final PsiClass psiClass = psiManager.findClass(name, tag.getResolveScope()); if (psiClass == null) return PsiReference.EMPTY_ARRAY; final XmlTagValue tagValue = tag.getValue(); final PsiMethod[] psiMethods = psiClass.findMethodsByName(tagValue.getTrimmedText(), false); return new PsiReference[]{ new PsiReference() { public PsiElement getElement() { return tag; } public TextRange getRangeInElement() { final int start = tag.getTextRange().getStartOffset(); return tag.getValue().getTextRange().shiftRight(-start); } @Nullable public PsiElement resolve() { return psiMethods.length >]]> 0 ? psiMethods[0] : null;
}

public String getCanonicalText() {
return tagValue.getTrimmedText();
}

public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
throw new UnsupportedOperationException();
}

public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException {
throw new UnsupportedOperationException();
}

public boolean isReferenceTo(PsiElement element) {
return resolve() == element;
}

public Object[] getVariants() {
return psiClass.getMethods();
}

public boolean isSoft() {
return false;
}
}
};
}
return PsiReference.EMPTY_ARRAY;
}

@NotNull
public PsiReference[] getReferencesByElement(PsiElement psiElement, ReferenceType referenceType) {
return PsiReference.EMPTY_ARRAY;
}

@NotNull
public PsiReference[] getReferencesByString(String string, PsiElement psiElement, ReferenceType referenceType, int i) {
return PsiReference.EMPTY_ARRAY;
}

public void handleEmptyContext(PsiScopeProcessor psiScopeProcessor, PsiElement psiElement) {
}
});
}

public void projectOpened() {
}

public void projectClosed() {
}

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

public void initComponent() {
}

public void disposeComponent() {
}
}

0

Thanks for the guidance...took me a bit to realize that I couldn't do this
particular plugin as an ApplicationComponent, and needed to be a ProjectComponent
such that I could provide a constructor containing the Project instance (and
thus could get the ReferenceProvidersRegistry and PsiManager).

I am really surprised at the type of stuff that I can provide on top of what
IntelliJ already does. Makes development a hell of a lot easier and quicker
for our internal framework.

I'll be looking at how to handle method renames, and how to propogate this
to the corresponding XML names...but not anytime in the next couple days.

Thanks for the help.

Mike


0

Thanks for this example, Sascha. Nice start for openapi beginners like me.

Two questions:
(1) I've tried to extend your example to include static methods, with no success.
In your code, the result of getVariants() already includes static methods, I just updated the code around "findMethodsByName" etc to do the same thing: include static methods.

However, I cannot get completion for there methods for some reason. (But navigation works) Any ideas?

(2) The current code inserts "foo();" completion. Is it possible to get just "foo" inserted instead?

Regards

0

Some progress on (1):
It appears that my sandbox configuration had the "Show static members after
instance qualifier" setting off.
Now the question becomes: how do I convince IDEA that I'm not interested
in instance/static destinction in this case. I don't want this "smart" filtering
in the variants list.


0

Taras Tielkes wrote:

Some progress on (1):
It appears that my sandbox configuration had the "Show static members
after instance qualifier" setting off.
Now the question becomes: how do I convince IDEA that I'm not interested
in instance/static destinction in this case. I don't want this "smart"
filtering in the variants list.


The easiest way to fix (1) and (2) is not to directly return a PsiMethod array from
getVariants(). Either just return Strings or an array of LookupValue* implementations from
the com.intellij.codeInsight.lookup package.

Sascha

0

Hello Sascha,

Thanks! I had just found an old forum post that pointed me in the same direction.
The plugin I'm playing with/building recognizes some specific Java string
literals as method references.

For instance (what I'm using now to test):
-string literal passed to System.out.println is interpreted as a reference
to a method on class "com.foo.Bar".

I have some new questions now:
(3) I have adapted your example to work from (some) string literals in Java
code.

Unfortunately, there are two built-in reference providers that are interested
in string literals as well: FilePathReferenceProvider & PropertiesReferenceProvider.

I want to have a specific String literal all for myself, and force other
providers out (they clutter the completion menu). Any ideas?

(4) In your original example, the names of nonexisting methods in the XML
automatically get rejected. (red color + message).

In my case (string literal), this does not work unfortunately
-Any idea way? (perhaps because there are multiple providers at work?)
-Should I just implement an editor inspection that checks specific literal
values using custom rules?

Regards,

Taras

Taras Tielkes wrote:

>> Some progress on (1):
>> It appears that my sandbox configuration had the "Show static members
>> after instance qualifier" setting off.
>> Now the question becomes: how do I convince IDEA that I'm not
>> interested
>> in instance/static destinction in this case. I don't want this
>> "smart"
>> filtering in the variants list.

The easiest way to fix (1) and (2) is not to directly return a
PsiMethod array from getVariants(). Either just return Strings or an
array of LookupValue* implementations from the
com.intellij.codeInsight.lookup package.

Sascha



0

Hello Taras,


I have some new questions now: (3) I have adapted your example to work from
(some) string literals in Java code.

Unfortunately, there are two built-in reference providers that are interested
in string literals as well: FilePathReferenceProvider &
PropertiesReferenceProvider.

I want to have a specific String literal all for myself, and force other
providers out (they clutter the completion menu). Any ideas?


I've come across the same problem, but haven't found a solution yet - actually I
didn't really look for one, but I suppose that it means to mess with the
CompletionData for the Java FileType.

(4) In your original example, the names of nonexisting methods in the XML
automatically get rejected. (red color + message).

In my case (string literal), this does not work unfortunately -Any idea way?
(perhaps because there are multiple providers at work?) -Should I just
implement an editor inspection that checks specific literal values using
custom rules?


This is something the built-in XML highlighter does for unresolved references,
but in Java code, you have to do this yourself. Either implement an inspection
or an Annotator that you can inject into the Java Language to do the highlighting.

Sascha

0

Hello Sascha,

>> I have some new questions now: (3) I have adapted your example to
>> work from (some) string literals in Java code.
>>
>> Unfortunately, there are two built-in reference providers that are
>> interested in string literals as well: FilePathReferenceProvider &
>> PropertiesReferenceProvider.
>>
>> I want to have a specific String literal all for myself, and force
>> other providers out (they clutter the completion menu). Any ideas?
>>

I've come across the same problem, but haven't found a solution yet -
actually I didn't really look for one, but I suppose that it means to
mess with the CompletionData for the Java FileType.


I'll explore that, and other idea. (And file a JIRA item, perhaps)

What happens with custom languages injected into Java string literals?
Does this convince the built-in reference providers to back off?

I remember reading in JIRA that you have been experimenting with embedded
languages.

>> (4) In your original example, the names of nonexisting methods in the
>> XML automatically get rejected. (red color + message).
>>
>> In my case (string literal), this does not work unfortunately -Any
>> idea way? (perhaps because there are multiple providers at work?)
>> -Should I just implement an editor inspection that checks specific
>> literal values using custom rules?
>>

This is something the built-in XML highlighter does for unresolved
references, but in Java code, you have to do this yourself. Either
implement an inspection or an Annotator that you can inject into the
Java Language to do the highlighting.


This is what I did actually (LocalInspectionTool). As a bonus, I also implemented
handleElementRename() on my reference.
Now I have:
-rename support
-usages support
-validation (via inspection)
-completion (without custom icons and alot of clutter, but still...)

Anything important missing?
Any ideas how to get custom icons in completion suggestions?
(So far I have been using getVariants() returning LookupValue* impls only,
never touched the CompletionData stuff.)

Regards,

Taras


0

Hello Taras,

What happens with custom languages injected into Java string literals?
Does this convince the built-in reference providers to back off?

I remember reading in JIRA that you have been experimenting with
embedded languages.


Yes, an injected language would work, though it "feels" a bit heavy-weight.

Any ideas how to get custom icons in completion suggestions?
(So far I have been using getVariants() returning LookupValue* impls
only, never touched the CompletionData stuff.)


I think one of the LookupValue* classes has a getIcon() method. If not, try
making your item-class implement the Iconable interface.

Sascha

0

Hello Sascha,

>> What happens with custom languages injected into Java string
>> literals? Does this convince the built-in reference providers to back
>> off?
>>
>> I remember reading in JIRA that you have been experimenting with
>> embedded languages.
>>

Yes, an injected language would work, though it "feels" a bit
heavy-weight.


Can this be used in 5.1 as well, or is this Demetra-only?
Is there any sample code for a trivial, into-Java-literals embedded language
to be found in the newsgroups or JIRA?

>> Any ideas how to get custom icons in completion suggestions?
>> (So far I have been using getVariants() returning LookupValue* impls
>> only, never touched the CompletionData stuff.)

I think one of the LookupValue* classes has a getIcon() method. If
not, try making your item-class implement the Iconable interface.


It looks as if I'm rebuilding the regular Java method completion menu bit-by-bit,
and for only one reason: to get rid of the "();" inserted after the method
name. This feels wrong...

Anyway, thanks!


0

I've tried to extend you example to include use of PsiPolyVariantReference.

In getReferencesByElement(), I return a regular PsiReferece when there is
one match, and a PsiPolyVariantReference when there are multiple matches
(that is: overloaded methods, for instance: java.lang.String#contentEquals)

In my PsiPolyVariantReference implementation, I return a minimal ResolvedResults
implemenation array, pointing to the different overloaded methods.

The resolve() implementation points to the first match, and isReferenceTo()
checks all resolved (overloaded) methods.

However, Ctrl-click and Ctrl-B still only resolve to the first method (with
matching name) in the referenced class. If I swap the methods, the resolve
will go to the new first method. This is not what I expected. Is there any
trick to implementing PsiPolyVariantReference?


0

This is indeed a pitfall I've been through as well: In case you're returning more than one result from multiResolve(), the normal resolve() method must return null in order to get the popup on ctrl-click/ctrl-b. Something like


HTH,
Sascha

0

Hello Sascha,

Thanks, this works! I'll file a JIRA to add this to the javadoc and/or the
languages article (which mentions polyvariant refs).

Out of curiosity, I also tried returning multiple (non-polyvariant) references
from getReferencesByElement(), but this did not have the same result. (Looks
like only the last element of the reference array is used when you do that)

Regards,

Taras


0

Please sign in to leave a comment.