PsiAugmentProvider throws ArrayStoreException

Answered

I'm trying to make IDEA think that class member is present, using implementation of PsiAugmentProvider.

I created a @Singleton annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Singleton {
String factoryMethod() default "get";
}

Use it like this:

@Singleton
public class SingletonExample {}

I started with providing a static method first. Field and constructor are on the way.

And my implementation of PsiAugmentProvider is:

public class AugmentProvider extends PsiAugmentProvider {

@NotNull
@Override
public <T extends PsiElement> List<T> getAugments(@NotNull PsiElement element, @NotNull final Class<T> type, @Nullable String nameHint) {
List<T> emptyResult = new ArrayList<>();
if (!(element instanceof PsiClass)) {
return emptyResult;
}
PsiClass psiClass = (PsiClass) element;
if (psiClass.isAnnotationType() || psiClass.isInterface()) {
return emptyResult;
}
final String annotationName = "my.pack.Singleton";
if (!psiClass.hasAnnotation(annotationName)) {
return emptyResult;
}
PsiAnnotation annotation = psiClass.getAnnotation(annotationName);
PsiLiteralExpression nameAttribute = (PsiLiteralExpression) annotation.findAttributeValue("factoryMethod");
String name = (String) nameAttribute.getValue();

List<T> psis = new ArrayList<>();

LightMethodBuilder method = new LightMethodBuilder(psiClass.getManager(), name)
.setModifiers("public", "static")
.setMethodReturnType(psiClass.getName())
.setBaseIcon(IconLoader.getIcon("/icons/icon.png", this.getClass()));
method.setNavigationElement(annotation);

psis.add((T) method);
return psis;
}
}

But IDEA throws an exception:

java.lang.ArrayStoreException: com.intellij.psi.impl.light.LightMethodBuilder
    at com.intellij.util.ArrayUtil.mergeCollections(ArrayUtil.java:238)
    at com.intellij.psi.impl.source.ClassInnerStuffCache.calcFields(ClassInnerStuffCache.java:120)
    at com.intellij.psi.impl.source.ClassInnerStuffCache.lambda$getFields$0(ClassInnerStuffCache.java:47)
    at com.intellij.psi.util.CachedValuesManager.lambda$getProjectPsiDependentCache$0(CachedValuesManager.java:134)
    at com.intellij.psi.util.CachedValuesManager$1.compute(CachedValuesManager.java:158)
    at com.intellij.psi.impl.PsiCachedValueImpl.doCompute(PsiCachedValueImpl.java:39)
    at com.intellij.util.CachedValueBase.lambda$getValueWithLock$3(CachedValueBase.java:227)
    at com.intellij.util.CachedValueBase.computeData(CachedValueBase.java:42)
    at com.intellij.util.CachedValueBase.lambda$getValueWithLock$4(CachedValueBase.java:227)
    at com.intellij.openapi.util.RecursionManager$1.computePreventingRecursion(RecursionManager.java:114)
    at com.intellij.openapi.util.RecursionGuard.doPreventingRecursion(RecursionGuard.java:44)
    at com.intellij.openapi.util.RecursionManager.doPreventingRecursion(RecursionManager.java:68)
    at com.intellij.util.CachedValueBase.getValueWithLock(CachedValueBase.java:228)
    at com.intellij.psi.impl.PsiCachedValueImpl.getValue(PsiCachedValueImpl.java:28)
    at com.intellij.util.CachedValuesManagerImpl.getCachedValue(CachedValuesManagerImpl.java:72)
    at com.intellij.psi.util.CachedValuesManager.getCachedValue(CachedValuesManager.java:155)
    at com.intellij.psi.util.CachedValuesManager.getProjectPsiDependentCache(CachedValuesManager.java:132)
    at com.intellij.psi.impl.source.ClassInnerStuffCache.getFields(ClassInnerStuffCache.java:47)
    at com.intellij.psi.impl.source.PsiClassImpl.getFields(PsiClassImpl.java:276)
    at com.intellij.ide.structureView.impl.java.JavaClassTreeElement.doGetOwnChildren(JavaClassTreeElement.java:73)
    at com.intellij.ide.structureView.impl.java.JavaClassTreeElement.lambda$getOwnChildren$0(JavaClassTreeElement.java:68)
    at com.intellij.util.SlowOperations.allowSlowOperations(SlowOperations.java:141)
    at com.intellij.ide.structureView.impl.java.JavaClassTreeElement.getOwnChildren(JavaClassTreeElement.java:68)
    at com.intellij.ide.structureView.impl.java.JavaClassTreeElement.getClassChildren(JavaClassTreeElement.java:40)
    at com.intellij.ide.structureView.impl.java.JavaClassTreeElement.getChildrenBase(JavaClassTreeElement.java:34)
    at com.intellij.ide.structureView.impl.common.PsiTreeElementBase.doGetChildren(PsiTreeElementBase.java:84)
    at com.intellij.ide.structureView.impl.common.PsiTreeElementBase.lambda$getChildren$0(PsiTreeElementBase.java:74)
    at com.intellij.ide.util.treeView.AbstractTreeUi.calculateYieldingToWriteAction(AbstractTreeUi.java:1715)
    at com.intellij.ide.structureView.impl.common.PsiTreeElementBase.getChildren(PsiTreeElementBase.java:74)
    at com.intellij.ide.structureView.impl.common.PsiTreeElementBase.getChildren(PsiTreeElementBase.java:23)
    at com.intellij.ide.util.treeView.smartTree.TreeElementWrapper.initChildren(TreeElementWrapper.java:50)
    at com.intellij.ide.util.treeView.smartTree.CachingChildrenTreeNode.rebuildSubtree(CachingChildrenTreeNode.java:207)
    at com.intellij.ide.util.treeView.smartTree.CachingChildrenTreeNode.ensureChildrenAreInitialized(CachingChildrenTreeNode.java:41)
    at com.intellij.ide.util.treeView.smartTree.CachingChildrenTreeNode.getChildren(CachingChildrenTreeNode.java:33)
    at com.intellij.ide.structureView.newStructureView.StructureViewComponent$MyNodeWrapper.getChildren(StructureViewComponent.java:796)
    at com.intellij.ide.util.treeView.smartTree.SmartTreeStructure.getChildElements(SmartTreeStructure.java:62)
    at com.intellij.ui.tree.StructureTreeModel.getValidChildren(StructureTreeModel.java:383)
    at com.intellij.ui.tree.StructureTreeModel.validateChildren(StructureTreeModel.java:299)
    at com.intellij.ui.tree.StructureTreeModel.getNode(StructureTreeModel.java:293)
    at com.intellij.ui.tree.StructureTreeModel.getChildren(StructureTreeModel.java:313)
    at com.intellij.ui.tree.AsyncTreeModel$CmdGetChildren.getNode(AsyncTreeModel.java:545)
    at com.intellij.ui.tree.AsyncTreeModel$Command.get(AsyncTreeModel.java:440)
    at com.intellij.ui.tree.AsyncTreeModel$Command.get(AsyncTreeModel.java:406)
    at com.intellij.util.concurrency.Invoker$Task.run(Invoker.java:314)
    at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1152)
    at com.intellij.openapi.progress.util.ProgressIndicatorUtils.lambda$runInReadActionWithWriteActionPriority$0(ProgressIndicatorUtils.java:75)
    at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runActionAndCancelBeforeWrite(ProgressIndicatorUtils.java:158)
    at com.intellij.openapi.progress.util.ProgressIndicatorUtils.lambda$runWithWriteActionPriority$1(ProgressIndicatorUtils.java:115)
    at com.intellij.openapi.progress.ProgressManager.lambda$runProcess$0(ProgressManager.java:58)
    at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$runProcess$2(CoreProgressManager.java:189)
    at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$executeProcessUnderProgress$12(CoreProgressManager.java:608)
    at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:683)
    at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:639)
    at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:607)
    at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:60)
    at com.intellij.openapi.progress.impl.CoreProgressManager.runProcess(CoreProgressManager.java:176)
    at com.intellij.openapi.progress.ProgressManager.runProcess(ProgressManager.java:58)
    at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runWithWriteActionPriority(ProgressIndicatorUtils.java:112)
    at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(ProgressIndicatorUtils.java:75)
    at com.intellij.util.concurrency.Invoker.invokeSafely(Invoker.java:203)
    at com.intellij.util.concurrency.Invoker.lambda$offerSafely$0(Invoker.java:181)
    at com.intellij.util.concurrency.Invoker$Background.lambda$offer$0(Invoker.java:481)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at com.intellij.util.concurrency.SchedulingWrapper$MyScheduledFutureTask.run(SchedulingWrapper.java:223)
    at com.intellij.util.concurrency.BoundedTaskExecutor.doRun(BoundedTaskExecutor.java:241)
    at com.intellij.util.concurrency.BoundedTaskExecutor.access$200(BoundedTaskExecutor.java:31)
    at com.intellij.util.concurrency.BoundedTaskExecutor$1.execute(BoundedTaskExecutor.java:214)
    at com.intellij.util.ConcurrencyUtil.runUnderThreadName(ConcurrencyUtil.java:212)
    at com.intellij.util.concurrency.BoundedTaskExecutor$1.run(BoundedTaskExecutor.java:203)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:668)
    at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:665)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1.run(Executors.java:665)
    at java.base/java.lang.Thread.run(Thread.java:829)


After debugging I figured out that it may the the reason in relation between element and type parameters, but this is no more than assumption. Maybe its is not the reason.

... getAugments(@NotNull PsiElement element, @NotNull final Class<T> type, ...)

Where should I dig to solve this?

8 comments
Comment actions Permalink

Hi Serhii,

I think your assumption is correct. The exception is thrown from the: 

com.intellij.psi.impl.source.PsiClassImpl.getFields()

and you produce a method, so there is probably a try to store a PsiMethod object in an array of PsiField.

Try to add the following condition:

if (type != PsiMethod.class) return emptyResult;
0
Comment actions Permalink

Karol Lewandowski
Thank you! I got a bit further.


In dropdown menu it really appears but it cannot get resolved actually.

I changed my code to this:

List<T> emptyResult = new ArrayList<>();
if (type == PsiMethod.class & element instanceof PsiClass) {
PsiClass psiClass = (PsiClass) element;
if (psiClass.isAnnotationType() || psiClass.isInterface()) {
return emptyResult;
}
final String annotationName = "my.pack.Singleton";
if (!psiClass.hasAnnotation(annotationName)) {
return emptyResult;
}
PsiAnnotation annotation = psiClass.getAnnotation(annotationName);
PsiLiteralExpression nameAttribute = (PsiLiteralExpression) annotation.findAttributeValue("factoryMethod");
String name = (String) nameAttribute.getValue();
List<T> psis = new ArrayList<>();
PsiMethod privateConstructor = MethodBuilder.forClass(psiClass, psiClass.getName())
.modifiers("private")
.navigation(annotation)
.build();

PsiMethod factoryMethod = MethodBuilder.forClass(psiClass, name)
.modifiers("public", "static")
.returnType(psiClass.getName())
.icon(IconLoader.getIcon("/icons/icon.png", this.getClass()))
.navigation(annotation)
.build();
psis.add((T) privateConstructor);
psis.add((T) factoryMethod);
return psis;
}
return emptyResult;


And another question is:
If I need to generate fields in a class I need to create separate Augment Provider? Since It returns elements of only one type?

0
Comment actions Permalink

What is MethodBuilder.forClass()? I can’t find it in the platform code.

I don’t think you need to create a separate PsiAugmentProvider. You can create conditions that will return what is needed depending on the provided type.

0
Comment actions Permalink

Karol Lewandowski
Oh sorry, this builder is a wrapper for LightMethodBuilder:

public class MethodBuilder {

private final LightMethodBuilder builder;

private MethodBuilder(PsiManager manager, String methodName) {
builder = new LightMethodBuilder(manager, methodName);
}

public static MethodBuilder forClass(PsiClass psiClass, String methodName) {
return new MethodBuilder(psiClass.getManager(), methodName);
}

public MethodBuilder modifiers(String... modifiers) {
builder.setModifiers(modifiers);
return this;
}

public MethodBuilder returnType(String returnType) {
builder.setMethodReturnType(returnType);
return this;
}

public MethodBuilder parameter(PsiParameter psiParameter) {
builder.addParameter(psiParameter);
return this;
}

public MethodBuilder navigation(PsiElement psiElement) {
builder.setNavigationElement(psiElement);
return this;
}

public MethodBuilder icon(Icon icon) {
builder.setBaseIcon(icon);
return this;
}

public PsiMethod build() {
return builder;
}
}
0
Comment actions Permalink

Could you please try the following?

builder.setContainingClass(element)

 

0
Comment actions Permalink

It worked, appreciate your help!

0
Comment actions Permalink

Karol Lewandowski

One more question regarding generated psi elements.

For a Singleton one of the required parts is private constructor.
By default Java provides public no-args constructor.

As a result my generated private constructor is ignored so I can instantiate singleton from other place.

I assume I need some functionality to delete a constructor that is implicitly defined in a class so intellij can prioritize my private constructor.

What is the best way to do this?

0
Comment actions Permalink

I figured out that I was missing a statement on LightMethodBuilder:

builder.setConstructor(true);

Now it compiles as expected.

0

Please sign in to leave a comment.