Skip to content
This repository has been archived by the owner on Nov 3, 2024. It is now read-only.

Implementation of LombokReferenceSearcher and LombokMethodReferenceSearcher (fix for #685) #722

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package de.plushnikov.intellij.plugin.extension;

import com.intellij.openapi.application.QueryExecutorBase;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiNewExpression;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceBase;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.util.Processor;
import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder;
import lombok.Builder;
import lombok.With;
import lombok.experimental.Wither;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class LombokMethodReferenceSearcher extends QueryExecutorBase<PsiReference, MethodReferencesSearch.SearchParameters> {

public LombokMethodReferenceSearcher(boolean requireReadAction) {
super(requireReadAction);
System.out.println("LombokMethodReferenceSearcher(boolean)");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Care to remove System.out statements?

}

public LombokMethodReferenceSearcher() {
System.out.println("LombokMethodReferenceSearcher()");
}

@Override
public void processQuery(@NotNull MethodReferencesSearch.SearchParameters queryParameters, @NotNull Processor<? super PsiReference> consumer) {
PsiMethod queryMethod = queryParameters.getMethod();
LombokLightMethodBuilder lombokMethod = LombokLightMethodBuilder.getLightMethodBuilder(queryMethod);
PsiMethod actualMethod = lombokMethod == null ? queryMethod : lombokMethod;

// Each Lombok-generated PsiMethod exists twice.
// - Once as an instance of LombokLightMethodBuilder which is properly wired by to its containing class.
// - Once as a degenerated instance of PsiMethodImpl which is built by LombokLightMethodBuilder and is not properly wired to its containing class

if (lombokMethod != null && queryMethod != lombokMethod) {
// We replace the search for the degenerated PsiMethodImpl by a search for the rich LombokLightMethodBuilder
// (but only the query is not already the LombokLightMethodBuilder, otherwise we'll cause an infinite loop)
MethodReferencesSearch.SearchParameters newParameters = new MethodReferencesSearch.SearchParameters(
lombokMethod,
queryParameters.getScopeDeterminedByUser(),
queryParameters.isStrictSignatureSearch(),
queryParameters.getOptimizer());
MethodReferencesSearch.search(newParameters).forEach(consumer);
}


if (shouldLookForConstructorReferences(actualMethod)) {
findConstructorReferencesInClassMethods(actualMethod.getContainingClass(), actualMethod, consumer);
findConstructorReferencesInInnerClasses(actualMethod.getContainingClass(), actualMethod, consumer);
}
}

/**
* If the method is a constructor, we must also look into class methods (it might be used in @With methods),
* and inner classes (it might be used in @Builder methods). Note that @With and @Builder will use physical
* constructors if available, so we have to look for constructor references even if the constructor is not
* generated by lombok.
* <p>
* However, in order to avoid searching deep into method implementations for *each* Constructor reference search and
* save a bit of CPU, we will only search for constructor references if the containing class is annotated with
*
* @Builder, @Wither or @With
*/
private boolean shouldLookForConstructorReferences(PsiMethod constructor) {
if (!constructor.isConstructor()) {
return false;
}

PsiClass containingClass = constructor.getContainingClass();
if (containingClass == null) {
return false;
}

return containingClass.getAnnotation(Builder.class.getName()) != null
|| containingClass.getAnnotation(With.class.getName()) != null
|| containingClass.getAnnotation(Wither.class.getName()) != null;
}

private void findConstructorReferencesInInnerClasses(PsiClass containingClass, PsiMethod constructor, Processor<? super PsiReference> consumer) {
for (PsiClass clazz : containingClass.getInnerClasses()) {
findConstructorReferencesInClassMethods(clazz, constructor, consumer);
}
}

private boolean findConstructorReferencesInClassMethods(PsiClass clazz, PsiMethod constructor, Processor<? super PsiReference> consumer) {
for (PsiMethod method : clazz.getMethods()) {
if (method instanceof LombokLightMethodBuilder) {
// only look in the methods we have generated ourselves. Other methods are already indexed and the references they contain can
// already be found by the native ReferenceSearch
if (!reportConstructorReferencesInElement(method, constructor, consumer)) {
return false;
}
}
}
return true;
}

private boolean reportConstructorReferencesInElement(PsiElement haystack, PsiMethod needle, Processor<? super PsiReference> consumer) {
if (haystack instanceof PsiNewExpression) {
PsiNewExpression newExpression = (PsiNewExpression) haystack;
PsiMethod resolvedConstructor = newExpression.resolveConstructor();
if (resolvedConstructor == needle) {
consumer.process(new LombokConstructorReference<>(
newExpression,
new TextRange(3, 4),
resolvedConstructor
));
}
}

PsiElement[] children = haystack.getChildren();
for (PsiElement child : children) {
if (!reportConstructorReferencesInElement(child, needle, consumer)) {
return false;
}
}

return true;
}

private static class LombokConstructorReference<T extends PsiElement> extends PsiReferenceBase<T> {
private final PsiMethod resolved;

public LombokConstructorReference(@NotNull T element, TextRange rangeInElement, PsiMethod resolved) {
super(element, rangeInElement);
this.resolved = resolved;
}

@Nullable
@Override
public PsiElement resolve() {
return resolved;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
package de.plushnikov.intellij.plugin.extension;

import com.intellij.openapi.application.QueryExecutorBase;
import com.intellij.openapi.project.DumbService;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.SearchRequestCollector;
import com.intellij.psi.search.UsageSearchContext;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.util.Processor;
import de.plushnikov.intellij.plugin.psi.LombokLightFieldBuilder;
import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.Objects;

/**
* Annas example: org.jetbrains.plugins.javaFX.fxml.refs.JavaFxControllerFieldSearcher
* Alternative Implementation for LombokFieldFindUsagesHandlerFactory
Expand All @@ -28,46 +22,48 @@ public LombokReferenceSearcher() {
}

@Override
public void processQuery(@NotNull ReferencesSearch.SearchParameters queryParameters, @NotNull Processor consumer) {
public void processQuery(@NotNull ReferencesSearch.SearchParameters queryParameters, @NotNull Processor<? super PsiReference> consumer) {
PsiElement refElement = queryParameters.getElementToSearch();

if (refElement instanceof PsiField) {
DumbService.getInstance(queryParameters.getProject()).runReadActionInSmartMode(() ->
processPsiField((PsiField) refElement, queryParameters.getOptimizer()));
searchInContainingClass((PsiField) refElement, consumer);
}
}

private void processPsiField(final PsiField refPsiField, final SearchRequestCollector collector) {
private void searchInContainingClass(final PsiField refPsiField, Processor<? super PsiReference> consumer) {
final PsiClass containingClass = refPsiField.getContainingClass();
if (null != containingClass) {
processClassMethods(refPsiField, collector, containingClass);

final PsiClass[] innerClasses = containingClass.getInnerClasses();
Arrays.stream(innerClasses)
.forEach(psiClass -> processClassMethods(refPsiField, collector, psiClass));
boolean mayContinueSearching = searchInClassMethods(containingClass, refPsiField, consumer);

Arrays.stream(innerClasses)
.forEach(psiClass -> processClassFields(refPsiField, collector, psiClass));
// TODO : look in generated inner classes methods (like Builders)
}
}

private void processClassMethods(PsiField refPsiField, SearchRequestCollector collector, PsiClass containingClass) {
Arrays.stream(containingClass.getMethods())
.filter(LombokLightMethodBuilder.class::isInstance)
.filter(psiMethod -> psiMethod.getNavigationElement() == refPsiField)
.forEach(psiMethod -> {
collector.searchWord(psiMethod.getName(), psiMethod.getUseScope(), UsageSearchContext.IN_CODE, true, psiMethod);
});
private boolean searchInClassMethods(PsiClass containingClass, PsiElement element, Processor<? super PsiReference> consumer) {
for(PsiMethod method : containingClass.getMethods()){
if(method instanceof LombokLightMethodBuilder){
// only look in the methods we have generated ourselves. Other methods are already indexed and the references they contain can
// already be found by the native ReferenceSearch
if(!reportReferencesInElement(method, element, consumer)){
return false;
}
}
}
return true;
}

private void processClassFields(PsiField refPsiField, SearchRequestCollector collector, PsiClass containingClass) {
Arrays.stream(containingClass.getFields())
.filter(LombokLightFieldBuilder.class::isInstance)
.filter(psiField -> psiField.getNavigationElement() == refPsiField)
.filter(psiField -> Objects.nonNull(psiField.getName()))
.forEach(psiField -> {
collector.searchWord(psiField.getName(), psiField.getUseScope(), UsageSearchContext.IN_CODE, true, psiField);
});
}
private boolean reportReferencesInElement(PsiElement haystack, PsiElement needle, Processor<? super PsiReference> consumer) {
PsiReference ref = haystack.getReference();
if (ref != null && ref.isReferenceTo(needle)) {
return consumer.process(ref);
}
PsiElement[] children = haystack.getChildren();
for (PsiElement child : children) {
if(!reportReferencesInElement(child, needle, consumer)){
return false;
}
}

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ public String renderSuperBuilderConstruction() {
}

public String renderBuildCall() {
return fieldInBuilderName;
return builderClass.getName() + ".this." + fieldInBuilderName;
}

public CharSequence renderToBuilderCall() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ public Collection<PsiField> renderBuilderFields(@NotNull BuilderInfo info) {
return Collections.singleton(
new LombokLightFieldBuilder(info.getManager(), info.getFieldName(), builderFieldType)
.withContainingClass(info.getBuilderClass())
.withModifier(PsiModifier.PRIVATE)
.withNavigationElement(info.getVariable()));
.withModifier(PsiModifier.PRIVATE));
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import de.plushnikov.intellij.plugin.psi.LombokLightFieldBuilder;
import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder;
import de.plushnikov.intellij.plugin.util.PsiMethodUtil;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -24,13 +25,12 @@ public Collection<PsiField> renderBuilderFields(@NotNull BuilderInfo info) {
return Collections.singleton(
new LombokLightFieldBuilder(info.getManager(), info.getFieldName(), info.getFieldType())
.withContainingClass(info.getBuilderClass())
.withModifier(PsiModifier.PRIVATE)
.withNavigationElement(info.getVariable()));
.withModifier(PsiModifier.PRIVATE));
}

@Override
public Collection<PsiMethod> renderBuilderMethod(@NotNull BuilderInfo info) {
final String blockText = getAllMethodBody(info.getFieldName(), info.getBuilderChainResult());
final String blockText = getAllMethodBody(info);
final LombokLightMethodBuilder methodBuilder = new LombokLightMethodBuilder(info.getManager(), info.getFieldName())
.withContainingClass(info.getBuilderClass())
.withMethodReturnType(info.getBuilderType())
Expand All @@ -51,8 +51,8 @@ public String createSingularName(PsiAnnotation singularAnnotation, String psiFie
return psiFieldName;
}

private String getAllMethodBody(@NotNull String psiFieldName, @NotNull String builderChainResult) {
final String codeBlockTemplate = "this.{0} = {0};\nreturn {1};";
return MessageFormat.format(codeBlockTemplate, psiFieldName, builderChainResult);
private String getAllMethodBody(@NonNull BuilderInfo info) {
final String codeBlockTemplate = "{2}.this.{0} = {0};\nreturn {1};";
return MessageFormat.format(codeBlockTemplate, info.getFieldName(), info.getBuilderChainResult(), info.getBuilderClass().getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ public Collection<PsiField> renderBuilderFields(@NotNull BuilderInfo info) {
return Collections.singleton(
new LombokLightFieldBuilder(info.getManager(), info.getFieldName(), builderFieldKeyType)
.withContainingClass(info.getBuilderClass())
.withModifier(PsiModifier.PRIVATE)
.withNavigationElement(info.getVariable()));
.withModifier(PsiModifier.PRIVATE));
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ public Collection<PsiField> renderBuilderFields(@NotNull BuilderInfo info) {
return Collections.singleton(
new LombokLightFieldBuilder(info.getManager(), info.getFieldName(), builderFieldKeyType)
.withContainingClass(info.getBuilderClass())
.withModifier(PsiModifier.PRIVATE)
.withNavigationElement(info.getVariable()));
.withModifier(PsiModifier.PRIVATE));
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,10 @@ public Collection<PsiField> renderBuilderFields(@NotNull BuilderInfo info) {
return Arrays.asList(
new LombokLightFieldBuilder(info.getManager(), info.getFieldName() + LOMBOK_KEY, builderFieldKeyType)
.withContainingClass(info.getBuilderClass())
.withModifier(PsiModifier.PRIVATE)
.withNavigationElement(info.getVariable()),
.withModifier(PsiModifier.PRIVATE),
new LombokLightFieldBuilder(info.getManager(), info.getFieldName() + LOMBOK_VALUE, builderFieldValueType)
.withContainingClass(info.getBuilderClass())
.withModifier(PsiModifier.PRIVATE)
.withNavigationElement(info.getVariable()));
.withModifier(PsiModifier.PRIVATE));
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiType;
import com.intellij.psi.impl.CheckUtil;
import com.intellij.psi.impl.light.LightFieldBuilder;
import com.intellij.psi.impl.light.LightModifierList;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.impl.source.DummyHolderFactory;
import com.intellij.util.IncorrectOperationException;
import de.plushnikov.intellij.plugin.icon.LombokIcons;
import org.jetbrains.annotations.NonNls;
Expand All @@ -26,12 +29,14 @@ public class LombokLightFieldBuilder extends LightFieldBuilder {
private String myName;
private final LombokLightIdentifier myNameIdentifier;
private final LombokLightModifierList myModifierList;
private final DummyHolder myHolder;

public LombokLightFieldBuilder(@NotNull PsiManager manager, @NotNull String name, @NotNull PsiType type) {
super(manager, name, type);
myName = name;
myNameIdentifier = new LombokLightIdentifier(manager, name);
myModifierList = new LombokLightModifierList(manager, JavaLanguage.INSTANCE, Collections.emptyList());
myHolder = DummyHolderFactory.createHolder(manager, getContext());
setBaseIcon(LombokIcons.FIELD_ICON);
}

Expand Down Expand Up @@ -77,6 +82,11 @@ public LombokLightFieldBuilder withNavigationElement(PsiElement navigationElemen
return this;
}

@Override
public PsiFile getContainingFile() {
return myHolder;
}

@NotNull
@Override
public String getName() {
Expand Down
Loading