Skip to content

Commit 825bc31

Browse files
committed
update to spel
1 parent ec3151b commit 825bc31

14 files changed

Lines changed: 485 additions & 45 deletions

File tree

plugin/src/main/java/com/intellij/spring/impl/ide/gutter/SpELAnnotator.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@
2323
import consulo.language.editor.annotation.AnnotationHolder;
2424
import consulo.language.editor.annotation.Annotator;
2525
import consulo.language.editor.ui.navigation.NavigationGutterIconBuilder;
26+
import consulo.language.inject.InjectedLanguageManager;
2627
import consulo.language.psi.PsiElement;
28+
import consulo.language.psi.PsiLanguageInjectionHost;
29+
import consulo.language.util.ModuleUtilCore;
2730
import consulo.module.Module;
2831
import consulo.spring.impl.icon.SpringImplIconGroup;
2932
import consulo.spring.spel.language.impl.psi.SpELBeanReferenceImpl;
33+
import org.jspecify.annotations.Nullable;
3034

3135
public class SpELAnnotator implements Annotator {
3236
@Override
@@ -44,7 +48,7 @@ private void annotateBeanReference(SpELBeanReferenceImpl beanRef, AnnotationHold
4448
return;
4549
}
4650

47-
Module module = beanRef.getModule();
51+
Module module = findModule(beanRef);
4852
if (module == null) {
4953
return;
5054
}
@@ -69,4 +73,22 @@ private void annotateBeanReference(SpELBeanReferenceImpl beanRef, AnnotationHold
6973
.setTooltipText("Navigate to bean '" + beanName + "'")
7074
.install(holder, beanRef);
7175
}
76+
77+
@RequiredReadAction
78+
private @Nullable Module findModule(PsiElement element) {
79+
// try direct module lookup first
80+
Module module = ModuleUtilCore.findModuleForPsiElement(element);
81+
if (module != null) {
82+
return module;
83+
}
84+
85+
// for injected SpEL, get the host element and find module from there
86+
PsiLanguageInjectionHost host = InjectedLanguageManager.getInstance(element.getProject())
87+
.getInjectionHost(element);
88+
if (host != null) {
89+
return ModuleUtilCore.findModuleForPsiElement(host);
90+
}
91+
92+
return null;
93+
}
7294
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2013-2026 consulo.io
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.intellij.spring.impl.ide.inject;
18+
19+
import com.intellij.lang.properties.IProperty;
20+
import com.intellij.lang.properties.PropertiesFilesManager;
21+
import consulo.annotation.component.ExtensionImpl;
22+
import consulo.document.util.TextRange;
23+
import consulo.language.Language;
24+
import consulo.language.psi.*;
25+
import consulo.language.util.ProcessingContext;
26+
import consulo.spring.spel.language.SpELLanguage;
27+
import consulo.spring.spel.language.impl.psi.SpELPlaceholderKeyImpl;
28+
import consulo.spring.spel.language.impl.psi.SpELPropertyPlaceholderImpl;
29+
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
33+
import static consulo.language.pattern.PlatformPatterns.psiElement;
34+
35+
@ExtensionImpl
36+
public class SpELPropertyReferenceContributor extends PsiReferenceContributor {
37+
@Override
38+
public void registerReferenceProviders(PsiReferenceRegistrar registrar) {
39+
registrar.registerReferenceProvider(
40+
psiElement(SpELPropertyPlaceholderImpl.class),
41+
new PsiReferenceProvider() {
42+
@Override
43+
public PsiReference[] getReferencesByElement(PsiElement element, ProcessingContext context) {
44+
if (!(element instanceof SpELPropertyPlaceholderImpl placeholder)) {
45+
return PsiReference.EMPTY_ARRAY;
46+
}
47+
48+
String key = placeholder.getPropertyKey();
49+
if (key == null || key.isEmpty()) {
50+
return PsiReference.EMPTY_ARRAY;
51+
}
52+
53+
SpELPlaceholderKeyImpl keyElement = placeholder.getKeyElement();
54+
if (keyElement == null) {
55+
return PsiReference.EMPTY_ARRAY;
56+
}
57+
58+
int startOffset = keyElement.getStartOffsetInParent();
59+
TextRange range = new TextRange(startOffset, startOffset + keyElement.getTextLength());
60+
61+
return new PsiReference[]{new SpELPropertyReference(element, range, key)};
62+
}
63+
}
64+
);
65+
}
66+
67+
@Override
68+
public Language getLanguage() {
69+
return SpELLanguage.INSTANCE;
70+
}
71+
72+
private static class SpELPropertyReference extends PsiReferenceBase<PsiElement> implements PsiPolyVariantReference {
73+
private final String myKey;
74+
75+
SpELPropertyReference(PsiElement element, TextRange range, String key) {
76+
super(element, range, true);
77+
myKey = key;
78+
}
79+
80+
@Override
81+
public ResolveResult[] multiResolve(boolean incompleteCode) {
82+
List<ResolveResult> results = new ArrayList<>();
83+
84+
PropertiesFilesManager.getInstance(myElement.getProject()).processAllPropertiesFiles((s, propertiesFile) -> {
85+
List<? extends IProperty> properties = propertiesFile.findPropertiesByKey(myKey);
86+
for (IProperty property : properties) {
87+
results.add(new PsiElementResolveResult(property.getPsiElement()));
88+
}
89+
return true;
90+
});
91+
92+
return results.toArray(ResolveResult.EMPTY_ARRAY);
93+
}
94+
95+
@Override
96+
public PsiElement resolve() {
97+
ResolveResult[] results = multiResolve(false);
98+
return results.length >= 1 ? results[0].getElement() : null;
99+
}
100+
101+
@Override
102+
public Object[] getVariants() {
103+
List<Object> variants = new ArrayList<>();
104+
PropertiesFilesManager.getInstance(myElement.getProject()).processAllPropertiesFiles((s, propertiesFile) -> {
105+
for (IProperty property : propertiesFile.getProperties()) {
106+
String propKey = property.getKey();
107+
if (propKey != null && !propKey.isEmpty()) {
108+
variants.add(propKey);
109+
}
110+
}
111+
return true;
112+
});
113+
return variants.toArray();
114+
}
115+
}
116+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2013-2026 consulo.io
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.intellij.spring.impl.ide.inject;
18+
19+
import com.intellij.java.language.psi.PsiClass;
20+
import com.intellij.spring.impl.ide.SpringManager;
21+
import com.intellij.spring.impl.ide.SpringModel;
22+
import com.intellij.spring.impl.ide.model.xml.beans.SpringBeanPointer;
23+
import consulo.annotation.component.ExtensionImpl;
24+
import consulo.language.inject.InjectedLanguageManager;
25+
import consulo.language.psi.PsiElement;
26+
import consulo.language.psi.PsiLanguageInjectionHost;
27+
import consulo.language.util.ModuleUtilCore;
28+
import consulo.module.Module;
29+
import consulo.spring.spel.language.SpELBeanResolverProvider;
30+
import org.jspecify.annotations.Nullable;
31+
32+
@ExtensionImpl
33+
public class SpringSpELBeanResolverProvider implements SpELBeanResolverProvider {
34+
@Override
35+
public @Nullable PsiElement resolveBean(String beanName, PsiElement context) {
36+
SpringBeanPointer pointer = findBeanPointer(beanName, context);
37+
return pointer != null ? pointer.getPsiElement() : null;
38+
}
39+
40+
@Override
41+
public @Nullable PsiClass resolveBeanClass(String beanName, PsiElement context) {
42+
SpringBeanPointer pointer = findBeanPointer(beanName, context);
43+
return pointer != null ? pointer.getBeanClass() : null;
44+
}
45+
46+
private @Nullable SpringBeanPointer findBeanPointer(String beanName, PsiElement context) {
47+
Module module = findModule(context);
48+
if (module == null) {
49+
return null;
50+
}
51+
52+
SpringModel model = SpringManager.getInstance(context.getProject()).getModel(module);
53+
if (model == null) {
54+
return null;
55+
}
56+
57+
return model.findBean(beanName);
58+
}
59+
60+
private @Nullable Module findModule(PsiElement element) {
61+
Module module = ModuleUtilCore.findModuleForPsiElement(element);
62+
if (module != null) {
63+
return module;
64+
}
65+
66+
PsiLanguageInjectionHost host = InjectedLanguageManager.getInstance(element.getProject())
67+
.getInjectionHost(element);
68+
if (host != null) {
69+
return ModuleUtilCore.findModuleForPsiElement(host);
70+
}
71+
72+
return null;
73+
}
74+
}

plugin/src/main/java/consulo/spring/impl/boot/AnnotationSpringModel.java

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
import com.intellij.spring.impl.ide.model.jam.SpringJamModel;
1212
import com.intellij.spring.impl.ide.model.jam.javaConfig.JavaSpringConfigurationElement;
1313
import com.intellij.spring.impl.ide.model.jam.javaConfig.SpringJamElement;
14+
import com.intellij.spring.impl.ide.model.jam.javaConfig.SpringJavaBean;
1415
import com.intellij.spring.impl.ide.model.jam.stereotype.SpringComponentScan;
16+
import com.intellij.spring.impl.ide.model.jam.stereotype.SpringStereotypeElement;
1517
import com.intellij.spring.impl.ide.model.xml.beans.Beans;
18+
import com.intellij.spring.impl.ide.model.xml.beans.SpringBeanPointer;
1619
import consulo.language.psi.scope.GlobalSearchScope;
1720
import consulo.module.Module;
1821
import consulo.spring.impl.boot.domOverAnnotation.AnnotatationComponentScan;
@@ -44,32 +47,33 @@ public AnnotationSpringModel(Module module, SpringFileSet fileSet) {
4447
mySpringJamModel = SpringJamModel.getModel(module);
4548
}
4649

47-
// @Nullable
48-
// @Override
49-
// public SpringBeanPointer findBean(@NonNls @Nonnull String beanName) {
50-
// SpringJamModel model = SpringJamModel.getModel(myModule);
51-
//
52-
// List<SpringJavaConfiguration> configurations = model.getConfigurations();
53-
// for (SpringJavaConfiguration configuration : configurations) {
54-
// List<? extends SpringJavaBean> beans = configuration.getBeans();
55-
//
56-
// for (SpringJavaBean bean : beans) {
57-
// String beanName1 = bean.getBeanName();
58-
// if (beanName.equals(beanName1)) {
59-
// return SpringBeanPointer.createSpringBeanPointer(bean);
60-
// }
61-
// }
62-
// }
63-
//
64-
// List<? extends SpringStereotypeElement> allStereotypeComponents = model.getAllStereotypeComponents();
65-
// for (SpringStereotypeElement allStereotypeComponent : allStereotypeComponents) {
66-
// String beanName1 = allStereotypeComponent.getBeanName();
67-
// if (beanName.equals(beanName1)) {
68-
// return SpringBeanPointer.createSpringBeanPointer(allStereotypeComponent);
69-
// }
70-
// }
71-
// return null;
72-
// }
50+
@Override
51+
public SpringBeanPointer findBean(@Nonnull String beanName) {
52+
// try fast direct lookup via JAM before falling back to base mapper
53+
SpringJamModel model = SpringJamModel.getModel(myModule);
54+
55+
// search @Configuration @Bean methods
56+
List<SpringJamElement> configurations = model.getConfigurations();
57+
for (SpringJamElement configuration : configurations) {
58+
List<? extends SpringJavaBean> beans = configuration.getBeans();
59+
for (SpringJavaBean bean : beans) {
60+
if (beanName.equals(bean.getBeanName())) {
61+
return SpringBeanPointer.createSpringBeanPointer(bean);
62+
}
63+
}
64+
}
65+
66+
// search @Component/@Service/@Repository/@Controller stereotype components
67+
List<? extends SpringStereotypeElement> allStereotypeComponents = model.getAllStereotypeComponents();
68+
for (SpringStereotypeElement component : allStereotypeComponents) {
69+
if (beanName.equals(component.getBeanName())) {
70+
return SpringBeanPointer.createSpringBeanPointer(component);
71+
}
72+
}
73+
74+
// fall back to base implementation (BeanNamesMapper with aliases)
75+
return super.findBean(beanName);
76+
}
7377

7478
@Nonnull
7579
@Override
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2013-2026 consulo.io
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package consulo.spring.spel.language;
18+
19+
import com.intellij.java.language.psi.PsiClass;
20+
import consulo.annotation.component.ComponentScope;
21+
import consulo.annotation.component.ExtensionAPI;
22+
import consulo.component.extension.ExtensionPointName;
23+
import consulo.language.psi.PsiElement;
24+
import org.jspecify.annotations.Nullable;
25+
26+
/**
27+
* Extension point for resolving Spring bean references from SpEL expressions.
28+
* Implemented by the Spring plugin module which has access to SpringManager/SpringModel.
29+
*/
30+
@ExtensionAPI(ComponentScope.APPLICATION)
31+
public interface SpELBeanResolverProvider {
32+
ExtensionPointName<SpELBeanResolverProvider> EP_NAME = ExtensionPointName.create(SpELBeanResolverProvider.class);
33+
34+
/**
35+
* Resolve a Spring bean by name and return its declaring PsiElement
36+
* (the @Component class, @Bean method, or XML bean tag).
37+
*/
38+
@Nullable
39+
PsiElement resolveBean(String beanName, PsiElement context);
40+
41+
/**
42+
* Resolve the PsiClass type of a Spring bean by name.
43+
*/
44+
@Nullable
45+
PsiClass resolveBeanClass(String beanName, PsiElement context);
46+
}

spel-lang-api/src/main/java/consulo/spring/spel/language/SpELElementTypes.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public final class SpELElementTypes {
4444
public static final IElementType SELECT_LAST_EXPRESSION = new SpELElementType("SELECT_LAST_EXPRESSION");
4545
public static final IElementType SELECT_FIRST_EXPRESSION = new SpELElementType("SELECT_FIRST_EXPRESSION");
4646
public static final IElementType PROPERTY_PLACEHOLDER = new SpELElementType("PROPERTY_PLACEHOLDER");
47+
public static final IElementType PLACEHOLDER_KEY = new SpELElementType("PLACEHOLDER_KEY");
48+
public static final IElementType PLACEHOLDER_DEFAULT_VALUE = new SpELElementType("PLACEHOLDER_DEFAULT_VALUE");
4749
public static final IElementType INLINE_LIST = new SpELElementType("INLINE_LIST");
4850
public static final IElementType INLINE_MAP = new SpELElementType("INLINE_MAP");
4951
public static final IElementType MAP_ENTRY = new SpELElementType("MAP_ENTRY");

spel-lang-impl/src/main/java/consulo/spring/spel/language/impl/lexer/_SpELLexer.flex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import consulo.spring.spel.language.SpELTokenTypes;
3434

3535
%state STRING
3636
%state PLACEHOLDER
37+
%state PLACEHOLDER_DEFAULT
3738

3839
WHITE_SPACE=[ \t\r\n]+
3940
DIGIT=[0-9]
@@ -112,6 +113,14 @@ IDENTIFIER_PART=[a-zA-Z0-9_$]
112113
}
113114

114115
<PLACEHOLDER> {
116+
"}" { yybegin(YYINITIAL); return SpELTokenTypes.RBRACE; }
117+
":" { yybegin(PLACEHOLDER_DEFAULT); return SpELTokenTypes.COLON; }
118+
"." { return SpELTokenTypes.DOT; }
119+
{LETTER} {IDENTIFIER_PART}* { return SpELTokenTypes.IDENTIFIER; }
120+
[^}:.a-zA-Z_$]+ { return SpELTokenTypes.PLACEHOLDER_CONTENT; }
121+
}
122+
123+
<PLACEHOLDER_DEFAULT> {
115124
"}" { yybegin(YYINITIAL); return SpELTokenTypes.RBRACE; }
116125
[^}]+ { return SpELTokenTypes.PLACEHOLDER_CONTENT; }
117126
}

0 commit comments

Comments
 (0)