Skip to content

Commit 7903280

Browse files
committed
missed files
1 parent 26fcc33 commit 7903280

3 files changed

Lines changed: 344 additions & 2 deletions

File tree

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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.impl.boot.properties;
18+
19+
import com.intellij.lang.properties.IProperty;
20+
import com.intellij.lang.properties.psi.PropertiesFile;
21+
import consulo.annotation.component.ComponentScope;
22+
import consulo.annotation.component.ServiceAPI;
23+
import consulo.annotation.component.ServiceImpl;
24+
import consulo.language.psi.PsiElement;
25+
import consulo.language.psi.PsiFile;
26+
import consulo.language.psi.PsiManager;
27+
import consulo.module.Module;
28+
import consulo.module.content.ModuleRootManager;
29+
import consulo.project.Project;
30+
import consulo.virtualFileSystem.VirtualFile;
31+
import jakarta.inject.Inject;
32+
import jakarta.inject.Singleton;
33+
import org.jspecify.annotations.Nullable;
34+
35+
import java.util.ArrayList;
36+
import java.util.List;
37+
38+
/**
39+
* Project-scoped service for resolving Spring Boot configuration properties.
40+
* Searches application.properties / application.yml in resource roots,
41+
* and spring-configuration-metadata.json in library JARs.
42+
*/
43+
@ServiceAPI(ComponentScope.PROJECT)
44+
@ServiceImpl
45+
@Singleton
46+
public class SpringConfigurationPropertySearch {
47+
private static final String[] CONFIG_FILENAMES = {
48+
"application.properties",
49+
"application.yml",
50+
"application.yaml"
51+
};
52+
53+
private static final String[] CONFIG_DIRS = {"", "config/"};
54+
55+
private final Project myProject;
56+
57+
@Inject
58+
public SpringConfigurationPropertySearch(Project project) {
59+
myProject = project;
60+
}
61+
62+
public static SpringConfigurationPropertySearch getInstance(Project project) {
63+
return project.getInstance(SpringConfigurationPropertySearch.class);
64+
}
65+
66+
/**
67+
* Resolve a property key to its definition(s) in config files.
68+
* Returns IProperty elements from .properties files and YAMLKeyValue from .yml files.
69+
*/
70+
public List<PsiElement> resolvePropertyKey(String key, @Nullable Module module) {
71+
if (module == null) {
72+
return List.of();
73+
}
74+
75+
List<PsiElement> results = new ArrayList<>();
76+
PsiManager psiManager = PsiManager.getInstance(myProject);
77+
78+
for (VirtualFile configFile : findConfigFiles(module)) {
79+
PsiFile psiFile = psiManager.findFile(configFile);
80+
if (psiFile instanceof PropertiesFile propertiesFile) {
81+
List<? extends IProperty> properties = propertiesFile.findPropertiesByKey(key);
82+
for (IProperty property : properties) {
83+
results.add(property.getPsiElement());
84+
}
85+
}
86+
else {
87+
// try YAML
88+
resolveYamlKey(psiFile, key, results);
89+
}
90+
}
91+
92+
return results;
93+
}
94+
95+
/**
96+
* Get all property keys from config files + metadata for completion.
97+
*/
98+
public List<String> getAllPropertyKeys(@Nullable Module module) {
99+
if (module == null) {
100+
return List.of();
101+
}
102+
103+
List<String> keys = new ArrayList<>();
104+
PsiManager psiManager = PsiManager.getInstance(myProject);
105+
106+
for (VirtualFile configFile : findConfigFiles(module)) {
107+
PsiFile psiFile = psiManager.findFile(configFile);
108+
if (psiFile instanceof PropertiesFile propertiesFile) {
109+
for (IProperty property : propertiesFile.getProperties()) {
110+
String propKey = property.getKey();
111+
if (propKey != null && !propKey.isEmpty()) {
112+
keys.add(propKey);
113+
}
114+
}
115+
}
116+
else {
117+
collectYamlKeys(psiFile, keys);
118+
}
119+
}
120+
121+
// also add keys from spring-configuration-metadata.json in library JARs
122+
for (SpringMetadataPropertyLoader.MetadataProperty metaProp : SpringMetadataPropertyLoader.loadFromModule(module)) {
123+
keys.add(metaProp.name());
124+
}
125+
126+
return keys;
127+
}
128+
129+
private List<VirtualFile> findConfigFiles(Module module) {
130+
List<VirtualFile> files = new ArrayList<>();
131+
VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots();
132+
133+
for (VirtualFile root : sourceRoots) {
134+
for (String dir : CONFIG_DIRS) {
135+
VirtualFile configDir = dir.isEmpty() ? root : root.findChild("config");
136+
if (configDir == null) {
137+
continue;
138+
}
139+
140+
for (String filename : CONFIG_FILENAMES) {
141+
VirtualFile file = configDir.findChild(filename);
142+
if (file != null) {
143+
files.add(file);
144+
}
145+
}
146+
147+
// profile-specific files
148+
for (VirtualFile child : configDir.getChildren()) {
149+
String name = child.getName();
150+
if (name.startsWith("application-") &&
151+
(name.endsWith(".properties") || name.endsWith(".yml") || name.endsWith(".yaml"))) {
152+
files.add(child);
153+
}
154+
}
155+
}
156+
}
157+
158+
return files;
159+
}
160+
161+
private void resolveYamlKey(PsiFile psiFile, String key, List<PsiElement> results) {
162+
try {
163+
Class<?> yamlFileClass = Class.forName("org.jetbrains.yaml.psi.YAMLFile");
164+
Class<?> yamlUtilClass = Class.forName("org.jetbrains.yaml.YAMLUtil");
165+
166+
if (!yamlFileClass.isInstance(psiFile)) {
167+
return;
168+
}
169+
170+
// YAMLUtil.getQualifiedKeyInFile(yamlFile, key.split("\\."))
171+
java.lang.reflect.Method method = yamlUtilClass.getMethod("getQualifiedKeyInFile",
172+
yamlFileClass, String[].class);
173+
Object result = method.invoke(null, psiFile, key.split("\\."));
174+
if (result instanceof PsiElement element) {
175+
results.add(element);
176+
}
177+
}
178+
catch (Exception ignored) {
179+
// YAML plugin not available
180+
}
181+
}
182+
183+
private void collectYamlKeys(PsiFile psiFile, List<String> keys) {
184+
try {
185+
Class<?> yamlFileClass = Class.forName("org.jetbrains.yaml.psi.YAMLFile");
186+
if (!yamlFileClass.isInstance(psiFile)) {
187+
return;
188+
}
189+
190+
collectYamlKeysRecursive(psiFile, keys);
191+
}
192+
catch (Exception ignored) {
193+
// YAML plugin not available
194+
}
195+
}
196+
197+
private void collectYamlKeysRecursive(PsiElement element, List<String> keys) {
198+
try {
199+
Class<?> yamlKeyValueClass = Class.forName("org.jetbrains.yaml.psi.YAMLKeyValue");
200+
Class<?> yamlUtilClass = Class.forName("org.jetbrains.yaml.YAMLUtil");
201+
Class<?> yamlPsiElementClass = Class.forName("org.jetbrains.yaml.psi.YAMLPsiElement");
202+
203+
if (yamlKeyValueClass.isInstance(element)) {
204+
// check if this is a leaf (scalar value, not a mapping)
205+
java.lang.reflect.Method getValueMethod = yamlKeyValueClass.getMethod("getValue");
206+
Object value = getValueMethod.invoke(element);
207+
Class<?> yamlScalarClass = Class.forName("org.jetbrains.yaml.psi.YAMLScalar");
208+
if (value != null && yamlScalarClass.isInstance(value)) {
209+
java.lang.reflect.Method getConfigFullName = yamlUtilClass.getMethod("getConfigFullName", yamlPsiElementClass);
210+
Object fullName = getConfigFullName.invoke(null, element);
211+
if (fullName instanceof String key && !key.isEmpty()) {
212+
keys.add(key);
213+
}
214+
}
215+
}
216+
217+
for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
218+
collectYamlKeysRecursive(child, keys);
219+
}
220+
}
221+
catch (Exception ignored) {
222+
// YAML plugin not available
223+
}
224+
}
225+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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.impl.boot.properties;
18+
19+
import com.intellij.json.psi.*;
20+
import consulo.language.psi.PsiFile;
21+
import consulo.language.psi.PsiManager;
22+
import consulo.module.Module;
23+
import consulo.module.content.layer.orderEntry.OrderEntryType;
24+
import consulo.content.library.Library;
25+
import consulo.module.content.ModuleRootManager;
26+
import consulo.virtualFileSystem.VirtualFile;
27+
import consulo.virtualFileSystem.archive.ArchiveVfsUtil;
28+
import org.jspecify.annotations.Nullable;
29+
30+
import java.util.ArrayList;
31+
import java.util.Collections;
32+
import java.util.List;
33+
34+
/**
35+
* Reads spring-configuration-metadata.json from library JARs using JSON plugin PSI.
36+
*/
37+
public final class SpringMetadataPropertyLoader {
38+
public record MetadataProperty(String name, @Nullable String type, @Nullable String description) {
39+
}
40+
41+
public static List<MetadataProperty> loadFromModule(Module module) {
42+
List<MetadataProperty> result = new ArrayList<>();
43+
PsiManager psiManager = PsiManager.getInstance(module.getProject());
44+
45+
ModuleRootManager.getInstance(module).orderEntries().librariesOnly().forEachLibrary(library -> {
46+
VirtualFile[] files = library.getFiles(consulo.content.base.BinariesOrderRootType.getInstance());
47+
for (VirtualFile root : files) {
48+
VirtualFile archiveRoot = ArchiveVfsUtil.getArchiveRootForLocalFile(root);
49+
if (archiveRoot == null) {
50+
archiveRoot = root;
51+
}
52+
VirtualFile metaFile = archiveRoot.findFileByRelativePath("META-INF/spring-configuration-metadata.json");
53+
if (metaFile != null) {
54+
loadFromFile(psiManager, metaFile, result);
55+
}
56+
}
57+
return true;
58+
});
59+
60+
return result;
61+
}
62+
63+
private static void loadFromFile(PsiManager psiManager, VirtualFile file, List<MetadataProperty> result) {
64+
PsiFile psiFile = psiManager.findFile(file);
65+
if (!(psiFile instanceof JsonFile jsonFile)) {
66+
return;
67+
}
68+
69+
// top-level value should be a JSON object
70+
JsonValue topValue = jsonFile.getTopLevelValue();
71+
if (!(topValue instanceof JsonObject topObject)) {
72+
return;
73+
}
74+
75+
// find "properties" array
76+
JsonProperty propertiesProp = topObject.findProperty("properties");
77+
if (propertiesProp == null) {
78+
return;
79+
}
80+
81+
JsonValue propertiesValue = propertiesProp.getValue();
82+
if (!(propertiesValue instanceof JsonArray propertiesArray)) {
83+
return;
84+
}
85+
86+
for (JsonValue element : propertiesArray.getValueList()) {
87+
if (!(element instanceof JsonObject propObj)) {
88+
continue;
89+
}
90+
91+
String name = getStringValue(propObj, "name");
92+
if (name == null) {
93+
continue;
94+
}
95+
96+
String type = getStringValue(propObj, "type");
97+
String description = getStringValue(propObj, "description");
98+
99+
result.add(new MetadataProperty(name, type, description));
100+
}
101+
}
102+
103+
private static @Nullable String getStringValue(JsonObject obj, String propertyName) {
104+
JsonProperty prop = obj.findProperty(propertyName);
105+
if (prop == null) {
106+
return null;
107+
}
108+
JsonValue value = prop.getValue();
109+
if (value instanceof JsonStringLiteral stringLiteral) {
110+
return stringLiteral.getValue();
111+
}
112+
return null;
113+
}
114+
115+
private SpringMetadataPropertyLoader() {
116+
}
117+
}

plugin/src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
<depends>consulo.java</depends>
1515
<depends>com.intellij.xml</depends>
1616
<depends optional="true">org.jetbrains.idea.maven</depends>
17-
<depends optional="true">org.jetbrains.plugins.yaml</depends>
18-
<depends optional="true">consulo.json</depends>
17+
<depends>org.jetbrains.plugins.yaml</depends>
18+
<depends>consulo.json</depends>
1919

2020
<tags>
2121
<tag>software.framework</tag>

0 commit comments

Comments
 (0)