From 2e966925b8160c190a39b6a21977b28dc104ed55 Mon Sep 17 00:00:00 2001 From: azerr Date: Tue, 8 Aug 2023 18:40:35 +0200 Subject: [PATCH] fix: Java file diagnostics is not refreshed when properties file is saved Fixes #860 Signed-off-by: azerr --- .../LSPDocumentationProvider.java | 8 +- .../PropertiesDefinitionParticipant.java | 2 +- .../hover/PropertiesHoverParticipant.java | 2 +- .../core/project/AbstractConfigSource.java | 415 ++++++++------- .../psi/core/project/IConfigSource.java | 4 + .../core/project/PsiMicroProfileProject.java | 481 +++++++++--------- .../PsiMicroProfileProjectManager.java | 10 +- .../java/MicroProfileConfigASTValidator.java | 2 +- .../NoValueAssignedToPropertyQuickFix.java | 2 +- ...oProfileRestClientCodeLensParticipant.java | 2 +- .../quarkus/QuarkusPostStartupActivity.java | 8 + .../quarkus/run/QuarkusRunContext.java | 2 +- .../QuarkusConfigPropertiesProvider.java | 2 +- .../java/QuarkusJaxRsCodeLensParticipant.java | 2 +- .../psi/core/MicroProfileAssert.java | 2 +- 15 files changed, 496 insertions(+), 448 deletions(-) diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java index a686ad8b2..ffb9804c1 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/documentation/LSPDocumentationProvider.java @@ -99,10 +99,8 @@ public List getMarkupContents(PsiElement element, @Nullable PsiEl // (LSP textDocument/completion request) return ((LSPPsiElementForLookupItem) element).getDocumentation(); } - if (originalElement == null || !Objects.equals(element.getContainingFile(), originalElement.getContainingFile())) { - return null; - } - Editor editor = LSPIJUtils.editorForElement(element); + + Editor editor = LSPIJUtils.editorForElement(originalElement); if (editor == null) { return null; } @@ -111,7 +109,7 @@ public List getMarkupContents(PsiElement element, @Nullable PsiEl VirtualFile file = originalElement.getContainingFile().getVirtualFile(); if (LSPVirtualFileWrapper.hasWrapper(file)) { int targetOffset = getTargetOffset(originalElement); - return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file).getHoverContent(element, targetOffset, editor); + return LSPVirtualFileWrapper.getLSPVirtualFileWrapper(file).getHoverContent(originalElement, targetOffset, editor); } return null; } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/java/definition/PropertiesDefinitionParticipant.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/java/definition/PropertiesDefinitionParticipant.java index 990d49954..80bd060b3 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/java/definition/PropertiesDefinitionParticipant.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/java/definition/PropertiesDefinitionParticipant.java @@ -57,7 +57,7 @@ protected List collectDefinitions(String propertyKey, Ra // Collect all properties files (properties, yaml files) where the given // property key is configured List infos = PsiMicroProfileProjectManager.getInstance(context.getJavaProject().getProject()) - .getJDTMicroProfileProject(javaProject).getPropertyInformations(propertyKey); + .getMicroProfileProject(javaProject).getPropertyInformations(propertyKey); if (!infos.isEmpty()) { return infos.stream().map(info -> { MicroProfileDefinition definition = new MicroProfileDefinition(); diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/java/hover/PropertiesHoverParticipant.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/java/hover/PropertiesHoverParticipant.java index 6d4a515c3..5640967b7 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/java/hover/PropertiesHoverParticipant.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/java/hover/PropertiesHoverParticipant.java @@ -181,7 +181,7 @@ public Hover collectHover(JavaHoverContext context) { propertyKey = propertyReplacer.apply(propertyKey); } PsiMicroProfileProject mpProject = PsiMicroProfileProjectManager.getInstance(javaProject.getProject()) - .getJDTMicroProfileProject(javaProject); + .getMicroProfileProject(javaProject); List propertyInformation = getConfigPropertyInformation(propertyKey, annotation, defaultValueAnnotationMemberName, typeRoot, mpProject, utils); return new Hover(getDocumentation(propertyInformation, context.getDocumentFormat(), diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/AbstractConfigSource.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/AbstractConfigSource.java index d89e1d8c2..cf8610d1e 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/AbstractConfigSource.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/AbstractConfigSource.java @@ -1,12 +1,12 @@ /******************************************************************************* -* Copyright (c) 2020 Red Hat Inc. and others. -* All rights reserved. This program and the accompanying materials -* which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* Contributors: -* Red Hat Inc. - initial API and implementation -*******************************************************************************/ + * Copyright (c) 2020 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ package com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project; import com.intellij.openapi.compiler.CompilerPaths; @@ -14,6 +14,8 @@ import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,200 +26,217 @@ /** * Abstract class for config file. - * - * @author Angelo ZERR * * @param the config model (ex: Properties for *.properties file) + * @author Angelo ZERR * @see https://github.com/redhat-developer/quarkus-ls/blob/master/microprofile.jdt/com.redhat.microprofile.jdt.core/src/main/java/com/redhat/microprofile/jdt/internal/core/project/AbstractConfigSource.java */ public abstract class AbstractConfigSource implements IConfigSource { - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConfigSource.class); - - private static final int DEFAULT_ORDINAL = 100; - - private final String configFileName; - - private final String profile; - - private final int ordinal; - - private final Module javaProject; - private VirtualFile outputConfigFile; - private VirtualFile sourceConfigFile; - private long lastModified = -1L; - private T config; - - private Map> propertyInformations; - - public AbstractConfigSource(String configFileName, int ordinal, Module javaProject) { - this(configFileName, null, ordinal, javaProject); - } - - public AbstractConfigSource(String configFileName, Module javaProject) { - this(configFileName, null, javaProject); - } - - public AbstractConfigSource(String configFileName, String profile, Module javaProject) { - this(configFileName, profile, DEFAULT_ORDINAL, javaProject); - } - - public AbstractConfigSource(String configFileName, String profile, int ordinal, Module javaProject) { - this.configFileName = configFileName; - this.profile = profile; - this.ordinal = ordinal; - this.javaProject = javaProject; - // load config file to udpate some fields like lastModified, config instance - // which must be updated when the config source is created. It's important that - // those fields are initialized here (and not in lazy mode) to prevent from - // multi - // thread - // context. - init(); - } - - private void init() { - T config = getConfig(); - if (config != null && propertyInformations == null) { - propertyInformations = loadPropertyInformations(); - } - } - - /** - * Returns the target/classes/$configFile and null otherwise. - * - *

- * Using this file instead of using src/main/resources/$configFile gives the - * capability to get the filtered value. - *

- * - * @return the target/classes/$configFile and null otherwise. - */ - private VirtualFile getOutputConfigFile() { - if (outputConfigFile != null && outputConfigFile.exists()) { - return outputConfigFile; - } - sourceConfigFile = null; - outputConfigFile = null; - if (javaProject.isLoaded()) { - VirtualFile[] sourceRoots = ModuleRootManager.getInstance(javaProject).getSourceRoots(false); - for (VirtualFile sourceRoot : sourceRoots) { - VirtualFile file = sourceRoot.findFileByRelativePath(configFileName); - if (file != null && file.exists()) { - sourceConfigFile = file; - outputConfigFile = file; - } - } - VirtualFile output = CompilerPaths.getModuleOutputDirectory(javaProject, false); - if (output != null) { - output = output.findFileByRelativePath(configFileName); - if (output != null) { - if (sourceConfigFile == null || output.getModificationStamp() >= sourceConfigFile.getModificationStamp()) { - outputConfigFile = output; - } - } - } - return outputConfigFile; - } - return null; - } - - @Override - public String getConfigFileName() { - return configFileName; - } - - @Override - public String getProfile() { - return profile; - } - - @Override - public int getOrdinal() { - return ordinal; - } - - @Override - public String getSourceConfigFileURI() { - getOutputConfigFile(); - if (sourceConfigFile != null) { - String uri = sourceConfigFile.getUrl(); - return fixURI(uri); - } - return null; - } - - private static String fixURI(String uri) { - return VfsUtil.toUri(uri).toString(); - } - - /** - * Returns the loaded config and null otherwise. - * - * @return the loaded config and null otherwise - */ - protected final T getConfig() { - VirtualFile configFile = getOutputConfigFile(); - if (configFile == null) { - reset(); - return null; - } - try { - long currentLastModified = configFile.getModificationStamp(); - if (currentLastModified != lastModified) { - reset(); - try (InputStream input = configFile.getInputStream()) { - config = loadConfig(input); - lastModified = configFile.getModificationStamp(); - } catch (IOException e) { - reset(); - LOGGER.error("Error while loading properties from '" + configFile + "'.", e); - } - } - } catch (RuntimeException e1) { - LOGGER.error("Error while getting last modified time for '" + configFile + "'.", e1); - } - return config; - } - - @Override - public Integer getPropertyAsInt(String key) { - String property = getProperty(key); - if (property != null && !property.trim().isEmpty()) { - try { - return Integer.parseInt(property.trim()); - } catch (NumberFormatException e) { - LOGGER.error("Error while converting '" + property.trim() + "' as Integer for key '" + key + "'", e); - return null; - } - } - return null; - } - - private void reset() { - config = null; - propertyInformations = null; - } - - @Override - public List getPropertyInformations(String propertyKey) { - init(); - return propertyInformations != null ? propertyInformations.get(propertyKey) : null; - } - - /** - * Load the config model from the given input stream input. - * - * @param input the input stream - * @return he config model from the given input stream input. - * @throws IOException - */ - protected abstract T loadConfig(InputStream input) throws IOException; - - /** - * Load the property informations. - * - * @return the property information. - */ - protected abstract Map> loadPropertyInformations(); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConfigSource.class); + + private static final int DEFAULT_ORDINAL = 100; + + private final String configFileName; + + private final String profile; + + private final int ordinal; + + private final Module javaProject; + private VirtualFile outputConfigFile; + private VirtualFile sourceConfigFile; + private long lastModified = -1L; + private T config; + + private Map> propertyInformations; + + public AbstractConfigSource(String configFileName, int ordinal, Module javaProject) { + this(configFileName, null, ordinal, javaProject); + } + + public AbstractConfigSource(String configFileName, Module javaProject) { + this(configFileName, null, javaProject); + } + + public AbstractConfigSource(String configFileName, String profile, Module javaProject) { + this(configFileName, profile, DEFAULT_ORDINAL, javaProject); + } + + public AbstractConfigSource(String configFileName, String profile, int ordinal, Module javaProject) { + this.configFileName = configFileName; + this.profile = profile; + this.ordinal = ordinal; + this.javaProject = javaProject; + // load config file to udpate some fields like lastModified, config instance + // which must be updated when the config source is created. It's important that + // those fields are initialized here (and not in lazy mode) to prevent from + // multi + // thread + // context. + init(); + } + + private void init() { + T config = getConfig(); + if (config != null && propertyInformations == null) { + propertyInformations = loadPropertyInformations(); + } + } + + /** + * Returns the target/classes/$configFile and null otherwise. + * + *

+ * Using this file instead of using src/main/resources/$configFile gives the + * capability to get the filtered value. + *

+ * + * @return the target/classes/$configFile and null otherwise. + */ + private VirtualFile getOutputConfigFile() { + if (outputConfigFile != null && outputConfigFile.exists()) { + return outputConfigFile; + } + sourceConfigFile = null; + outputConfigFile = null; + if (javaProject.isLoaded()) { + VirtualFile[] sourceRoots = ModuleRootManager.getInstance(javaProject).getSourceRoots(false); + for (VirtualFile sourceRoot : sourceRoots) { + VirtualFile file = sourceRoot.findFileByRelativePath(configFileName); + if (file != null && file.exists()) { + sourceConfigFile = file; + outputConfigFile = file; + } + } + VirtualFile output = CompilerPaths.getModuleOutputDirectory(javaProject, false); + if (output != null) { + output = output.findFileByRelativePath(configFileName); + if (output != null) { + if (sourceConfigFile == null || output.getModificationStamp() >= sourceConfigFile.getModificationStamp()) { + outputConfigFile = output; + } + } + } + return outputConfigFile; + } + return null; + } + + @Override + public String getConfigFileName() { + return configFileName; + } + + @Override + public String getProfile() { + return profile; + } + + @Override + public int getOrdinal() { + return ordinal; + } + + @Override + public String getSourceConfigFileURI() { + getOutputConfigFile(); + if (sourceConfigFile != null) { + String uri = sourceConfigFile.getUrl(); + return fixURI(uri); + } + return null; + } + + @Override + public boolean isSourceConfigFile(VirtualFile file) { + return file.equals(sourceConfigFile); + } + + private static String fixURI(String uri) { + return VfsUtil.toUri(uri).toString(); + } + + /** + * Returns the loaded config and null otherwise. + * + * @return the loaded config and null otherwise + */ + protected final T getConfig() { + VirtualFile configFile = getOutputConfigFile(); + if (configFile == null) { + reset(); + return null; + } + try { + long currentLastModified = configFile.getModificationStamp(); + if (currentLastModified > lastModified) { + reset(); + try (InputStream input = configFile.getInputStream()) { + config = loadConfig(input); + lastModified = configFile.getModificationStamp(); + } catch (IOException e) { + reset(); + LOGGER.error("Error while loading properties from '" + configFile + "'.", e); + } + } + } catch (RuntimeException e1) { + LOGGER.error("Error while getting last modified time for '" + configFile + "'.", e1); + } + return config; + } + + @Override + public void reload(PsiFile file) { + reset(); + String content = file.getText(); + try (InputStream input = IOUtils.toInputStream(content)) { + config = loadConfig(input); + lastModified = System.currentTimeMillis(); + } catch (IOException e) { + reset(); + LOGGER.error("Error while loading properties from '" + sourceConfigFile + "'.", e); + } + } + + @Override + public Integer getPropertyAsInt(String key) { + String property = getProperty(key); + if (property != null && !property.trim().isEmpty()) { + try { + return Integer.parseInt(property.trim()); + } catch (NumberFormatException e) { + LOGGER.error("Error while converting '" + property.trim() + "' as Integer for key '" + key + "'", e); + return null; + } + } + return null; + } + + private void reset() { + config = null; + propertyInformations = null; + } + + @Override + public List getPropertyInformations(String propertyKey) { + init(); + return propertyInformations != null ? propertyInformations.get(propertyKey) : null; + } + + /** + * Load the config model from the given input stream input. + * + * @param input the input stream + * @return he config model from the given input stream input. + * @throws IOException + */ + protected abstract T loadConfig(InputStream input) throws IOException; + + /** + * Load the property informations. + * + * @return the property information. + */ + protected abstract Map> loadPropertyInformations(); } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/IConfigSource.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/IConfigSource.java index c09c70497..96cd5edd1 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/IConfigSource.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/IConfigSource.java @@ -10,6 +10,7 @@ package com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; import java.util.List; import java.util.Map; @@ -60,6 +61,8 @@ public interface IConfigSource { */ String getSourceConfigFileURI(); + boolean isSourceConfigFile(VirtualFile file); + /** * Returns a list of all values for properties and different profiles that are * defined in this config source. @@ -100,4 +103,5 @@ public interface IConfigSource { */ Set getAllKeys(); + void reload(PsiFile file); } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProject.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProject.java index de88dd155..e0fe63bfc 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProject.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProject.java @@ -1,272 +1,291 @@ /******************************************************************************* -* Copyright (c) 2020 Red Hat Inc. and others. -* All rights reserved. This program and the accompanying materials -* which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* Contributors: -* Red Hat Inc. - initial API and implementation -*******************************************************************************/ + * Copyright (c) 2020 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ package com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.compiler.CompilerPaths; import com.intellij.openapi.module.Module; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.core.project.ConfigSourcePropertiesProvider; import org.eclipse.lsp4mp.commons.utils.ConfigSourcePropertiesProviderUtils; import org.eclipse.lsp4mp.commons.utils.IConfigSourcePropertiesProvider; import org.eclipse.lsp4mp.commons.utils.PropertyValueExpander; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** * JDT MicroProfile project. - * + * * @author Angelo ZERR * @see https://github.com/redhat-developer/quarkus-ls/blob/master/microprofile.jdt/com.redhat.microprofile.jdt.core/src/main/java/com/redhat/microprofile/jdt/core/project/JDTMicroProfileProject.java - * */ public class PsiMicroProfileProject { - private final Module javaProject; + private final Module javaProject; - private List configSources; + private List configSources; - private transient IConfigSourcePropertiesProvider aggregatedPropertiesProvider = null; - private transient PropertyValueExpander propertyValueExpander = null; + private transient IConfigSourcePropertiesProvider aggregatedPropertiesProvider = null; + private transient PropertyValueExpander propertyValueExpander = null; - public PsiMicroProfileProject(Module javaProject) { - this.javaProject = javaProject; - } + public PsiMicroProfileProject(Module javaProject) { + this.javaProject = javaProject; + } - /** - * Returns the value of this property or defaultValue if it is not - * defined in this project. - * - * Expands property expressions when there are no cyclical references between - * property values. - * - * @param propertyKey the property to get with the profile included, in the - * format used by microprofile-config.properties - * @param defaultValue the value to return if the value for the property is not - * defined in this project - * @return the value of this property or defaultValue if it is not - * defined in this project - */ - public String getProperty(String propertyKey, String defaultValue) { + /** + * Returns the value of this property or defaultValue if it is not + * defined in this project. + *

+ * Expands property expressions when there are no cyclical references between + * property values. + * + * @param propertyKey the property to get with the profile included, in the + * format used by microprofile-config.properties + * @param defaultValue the value to return if the value for the property is not + * defined in this project + * @return the value of this property or defaultValue if it is not + * defined in this project + */ + public String getProperty(String propertyKey, String defaultValue) { - if (aggregatedPropertiesProvider == null) { - aggregatedPropertiesProvider = getAggregatedPropertiesProvider(); - } + if (aggregatedPropertiesProvider == null) { + aggregatedPropertiesProvider = getAggregatedPropertiesProvider(); + } - String unresolved = aggregatedPropertiesProvider.getValue(propertyKey); - if (unresolved == null) { - return defaultValue; - } else if (unresolved.contains("${")) { - if (propertyValueExpander == null) { - propertyValueExpander = new PropertyValueExpander(aggregatedPropertiesProvider); - } - String expandedValue = propertyValueExpander.getValue(propertyKey); - if (expandedValue == null) { - return defaultValue; - } - return expandedValue; - } else { - return unresolved; - } - } + String unresolved = aggregatedPropertiesProvider.getValue(propertyKey); + if (unresolved == null) { + return defaultValue; + } else if (unresolved.contains("${")) { + if (propertyValueExpander == null) { + propertyValueExpander = new PropertyValueExpander(aggregatedPropertiesProvider); + } + String expandedValue = propertyValueExpander.getValue(propertyKey); + if (expandedValue == null) { + return defaultValue; + } + return expandedValue; + } else { + return unresolved; + } + } - /** - * Returns the value of this property or null if it is not defined in this - * project. - * - * Expands property expressions when there are no cyclical references between - * property values. - * - * @param propertyKey the property to get with the profile included, in the - * format used by microprofile-config.properties - * @return the value of this property or null if it is not defined in this - * project - */ - public String getProperty(String propertyKey) { - return getProperty(propertyKey, null); - } + /** + * Returns the value of this property or null if it is not defined in this + * project. + *

+ * Expands property expressions when there are no cyclical references between + * property values. + * + * @param propertyKey the property to get with the profile included, in the + * format used by microprofile-config.properties + * @return the value of this property or null if it is not defined in this + * project + */ + public String getProperty(String propertyKey) { + return getProperty(propertyKey, null); + } - /** - * Returns the value of this property as an int, or defaultValue - * when there is no value or the value cannot be parsed as a String. - * - * Expands property expressions when there are no cyclical references between - * property values. - * - * @param key the property to get with the profile included, in the - * format used by microprofile-config.properties - * @param defaultValue the value to return if the value for the property is not - * defined in this project - * @return the value of this property as an int, or defaultValue - * when there is no value or the value cannot be parsed as a String - */ - public Integer getPropertyAsInteger(String key, Integer defaultValue) { - String value = getProperty(key, null); - if (value == null) { - return defaultValue; - } - try { - int intValue = Integer.parseInt(value); - return intValue; - } catch (NumberFormatException nfe) { - return defaultValue; - } - } + /** + * Returns the value of this property as an int, or defaultValue + * when there is no value or the value cannot be parsed as a String. + *

+ * Expands property expressions when there are no cyclical references between + * property values. + * + * @param key the property to get with the profile included, in the + * format used by microprofile-config.properties + * @param defaultValue the value to return if the value for the property is not + * defined in this project + * @return the value of this property as an int, or defaultValue + * when there is no value or the value cannot be parsed as a String + */ + public Integer getPropertyAsInteger(String key, Integer defaultValue) { + String value = getProperty(key, null); + if (value == null) { + return defaultValue; + } + try { + int intValue = Integer.parseInt(value); + return intValue; + } catch (NumberFormatException nfe) { + return defaultValue; + } + } - /** - * Returns a list of all values for properties and different profiles that are - * defined in this project. - * - *

- * This list contains information for the property (ex : greeting.message) and - * profile property (ex : %dev.greeting.message). - *

- * - *

- * When several properties file (ex : microprofile-config.properties, - * application.properties, etc) define the same property, it's the file which - * have the bigger ordinal (see {@link IConfigSource#getOrdinal()} which is - * returned. - *

- * - *

- * Expands property expressions for each of the values of the key when there are - * no cyclical references between property values. - *

- * - * @param propertyKey the name of the property to collect the values for - * @return a list of all values for properties and different profiles that are - * defined in this project. - */ - public List getPropertyInformations(String propertyKey) { - // Use a map to override property values - // eg. if application.yaml defines a value for a property it should override the - // value defined in application.properties - Map propertyToInfoMap = new HashMap<>(); - // Go backwards so that application.properties replaces - // microprofile-config.properties, etc. - List configSources = getConfigSources(); - for (int i = configSources.size() - 1; i >= 0; i--) { - IConfigSource configSource = configSources.get(i); - List propertyInformations = configSource - .getPropertyInformations(propertyKey); - if (propertyInformations != null) { - for (MicroProfileConfigPropertyInformation propertyInformation : propertyInformations) { - propertyToInfoMap.put(propertyInformation.getPropertyNameWithProfile(), propertyInformation); - } - } - } - return propertyToInfoMap.values().stream() // - .sorted((a, b) -> { - return a.getPropertyNameWithProfile().compareTo(b.getPropertyNameWithProfile()); - }) // - .map(info -> { - String resolved = this.getProperty(info.getPropertyNameWithProfile()); - return new MicroProfileConfigPropertyInformation(info.getPropertyNameWithProfile(), resolved, - info.getSourceConfigFileURI(), info.getConfigFileName()); - }).collect(Collectors.toList()); - } + /** + * Returns a list of all values for properties and different profiles that are + * defined in this project. + * + *

+ * This list contains information for the property (ex : greeting.message) and + * profile property (ex : %dev.greeting.message). + *

+ * + *

+ * When several properties file (ex : microprofile-config.properties, + * application.properties, etc) define the same property, it's the file which + * have the bigger ordinal (see {@link IConfigSource#getOrdinal()} which is + * returned. + *

+ * + *

+ * Expands property expressions for each of the values of the key when there are + * no cyclical references between property values. + *

+ * + * @param propertyKey the name of the property to collect the values for + * @return a list of all values for properties and different profiles that are + * defined in this project. + */ + public List getPropertyInformations(String propertyKey) { + // Use a map to override property values + // eg. if application.yaml defines a value for a property it should override the + // value defined in application.properties + Map propertyToInfoMap = new HashMap<>(); + // Go backwards so that application.properties replaces + // microprofile-config.properties, etc. + List configSources = getConfigSources(); + for (int i = configSources.size() - 1; i >= 0; i--) { + IConfigSource configSource = configSources.get(i); + List propertyInformations = configSource + .getPropertyInformations(propertyKey); + if (propertyInformations != null) { + for (MicroProfileConfigPropertyInformation propertyInformation : propertyInformations) { + propertyToInfoMap.put(propertyInformation.getPropertyNameWithProfile(), propertyInformation); + } + } + } + return propertyToInfoMap.values().stream() // + .sorted((a, b) -> { + return a.getPropertyNameWithProfile().compareTo(b.getPropertyNameWithProfile()); + }) // + .map(info -> { + String resolved = this.getProperty(info.getPropertyNameWithProfile()); + return new MicroProfileConfigPropertyInformation(info.getPropertyNameWithProfile(), resolved, + info.getSourceConfigFileURI(), info.getConfigFileName()); + }).collect(Collectors.toList()); + } - public List getConfigSources() { - if (configSources == null) { - configSources = loadConfigSources(javaProject); - } - return configSources; - } + public List getConfigSources() { + if (configSources == null) { + configSources = loadConfigSources(javaProject); + } + return configSources; + } - /** - * Evict the config sources cache and related cached information as soon as one - * of properties, yaml file is saved. - */ - public void evictConfigSourcesCache() { - configSources = null; - propertyValueExpander = null; - aggregatedPropertiesProvider = null; - } + /** + * Evict the config sources cache and related cached information as soon as one + * of properties, yaml file is saved. + */ + public void evictConfigSourcesCache(VirtualFile file) { + final List configSources = getConfigSources(); + List configSourcesToDelete = new ArrayList<>(); + for (IConfigSource configSource : configSources) { + if (configSource.isSourceConfigFile(file)) { + ReadAction.compute(() -> { + PsiFile psiFile = PsiManager.getInstance(javaProject.getProject()).findFile(file); + if (psiFile != null) { + // The config source file has been updated, reload it + configSource.reload(psiFile); + } else { + // The config source file has been deleted, remove it + configSourcesToDelete.add(configSource); + } + return null; + }); + } + } + if (!configSourcesToDelete.isEmpty()) { + // Remove from config sources cache, the config source file which has been deleted + for (IConfigSource configSourceToDelete : configSourcesToDelete) { + configSources.remove(configSourceToDelete); + } + } + propertyValueExpander = null; + aggregatedPropertiesProvider = null; + } - /** - * Load config sources from the given project and sort it by using - * {@link IConfigSource#getOrdinal()} - * - * @param javaProject the Java project - * @return the loaded config sources. - */ - private synchronized List loadConfigSources(Module javaProject) { - if (configSources != null) { - // Case when there are several Threads which load config sources, the second - // Thread should not reload the config sources again. - return configSources; - } - List configSources = new ArrayList<>(); - VirtualFile outputFile = CompilerPaths.getModuleOutputDirectory(javaProject, false); - for (IConfigSourceProvider provider : IConfigSourceProvider.EP_NAME.getExtensions()) { - configSources.addAll(provider.getConfigSources(javaProject, outputFile)); - } - Collections.sort(configSources, (a, b) -> b.getOrdinal() - a.getOrdinal()); - return configSources; - } + /** + * Load config sources from the given project and sort it by using + * {@link IConfigSource#getOrdinal()} + * + * @param javaProject the Java project + * @return the loaded config sources. + */ + private synchronized List loadConfigSources(Module javaProject) { + if (configSources != null) { + // Case when there are several Threads which load config sources, the second + // Thread should not reload the config sources again. + return configSources; + } + List configSources = new ArrayList<>(); + VirtualFile outputFile = CompilerPaths.getModuleOutputDirectory(javaProject, false); + for (IConfigSourceProvider provider : IConfigSourceProvider.EP_NAME.getExtensions()) { + configSources.addAll(provider.getConfigSources(javaProject, outputFile)); + } + Collections.sort(configSources, (a, b) -> b.getOrdinal() - a.getOrdinal()); + return configSources; + } - /** - * Returns true if the given property has a value declared for any profile, and - * false otherwise. - * - * @param property the property to check if there is a value for - * @return true if the given property has a value declared for any profile, and - * false otherwise - */ - public boolean hasProperty(String property) { - List configSources = getConfigSources(); - for (IConfigSource configSource : configSources) { - if (configSource.getPropertyInformations(property) != null) { - return true; - } - } - return false; - } + /** + * Returns true if the given property has a value declared for any profile, and + * false otherwise. + * + * @param property the property to check if there is a value for + * @return true if the given property has a value declared for any profile, and + * false otherwise + */ + public boolean hasProperty(String property) { + List configSources = getConfigSources(); + for (IConfigSource configSource : configSources) { + if (configSource.getPropertyInformations(property) != null) { + return true; + } + } + return false; + } - private IConfigSourcePropertiesProvider getAggregatedPropertiesProvider() { - List configSources = getConfigSources(); - if (configSources.size() == 0) { - // Return an empty IConfigSourcePropertiesProvider - return new IConfigSourcePropertiesProvider() { + private IConfigSourcePropertiesProvider getAggregatedPropertiesProvider() { + List configSources = getConfigSources(); + if (configSources.size() == 0) { + // Return an empty IConfigSourcePropertiesProvider + return new IConfigSourcePropertiesProvider() { - @Override - public Set keys() { - return Collections.emptySet(); - } + @Override + public Set keys() { + return Collections.emptySet(); + } - @Override - public boolean hasKey(String key) { - return false; - } + @Override + public boolean hasKey(String key) { + return false; + } - @Override - public String getValue(String key) { - return null; - } + @Override + public String getValue(String key) { + return null; + } - }; - } - IConfigSourcePropertiesProvider provider = new ConfigSourcePropertiesProvider( - configSources.get(configSources.size() - 1)); - for (int i = configSources.size() - 2; i >= 0; i--) { - provider = ConfigSourcePropertiesProviderUtils - .layer(new ConfigSourcePropertiesProvider(configSources.get(i)), provider); - } - return provider; - } + }; + } + IConfigSourcePropertiesProvider provider = new ConfigSourcePropertiesProvider( + configSources.get(configSources.size() - 1)); + for (int i = configSources.size() - 2; i >= 0; i--) { + provider = ConfigSourcePropertiesProviderUtils + .layer(new ConfigSourcePropertiesProvider(configSources.get(i)), provider); + } + return provider; + } } \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java index 117a82920..86566d2f6 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java @@ -61,9 +61,9 @@ public void sourceFilesChanged(Set> sources) { if (isConfigSource(file)) { // A microprofile config file properties file source has been updated, evict the cache of the properties Module javaProject = pair.getSecond(); - PsiMicroProfileProject mpProject = getJDTMicroProfileProject(javaProject); + PsiMicroProfileProject mpProject = getMicroProfileProject(javaProject); if (mpProject != null) { - mpProject.evictConfigSourcesCache(); + mpProject.evictConfigSourcesCache(file); } } } @@ -90,11 +90,11 @@ private PsiMicroProfileProjectManager(Project project) { connection.subscribe(ProjectTopics.MODULES, microprofileProjectListener); } - public PsiMicroProfileProject getJDTMicroProfileProject(Module project) { - return getJDTMicroProfileProject(project, true); + public PsiMicroProfileProject getMicroProfileProject(Module project) { + return getMicroProfileProject(project, true); } - private PsiMicroProfileProject getJDTMicroProfileProject(Module javaProject, boolean create) { + private PsiMicroProfileProject getMicroProfileProject(Module javaProject, boolean create) { PsiMicroProfileProject mpProject = javaProject.getUserData(KEY); if (mpProject == null) { if (!create) { diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/config/java/MicroProfileConfigASTValidator.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/config/java/MicroProfileConfigASTValidator.java index 37b562061..f770399c8 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/config/java/MicroProfileConfigASTValidator.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/config/java/MicroProfileConfigASTValidator.java @@ -291,7 +291,7 @@ private static boolean isAssignable(String typeFqn, String value, Module javaPro private static boolean doesPropertyHaveValue(String property, JavaDiagnosticsContext context) { Module javaProject = context.getJavaProject(); PsiMicroProfileProject mpProject = PsiMicroProfileProjectManager.getInstance(javaProject.getProject()) - .getJDTMicroProfileProject(javaProject); + .getMicroProfileProject(javaProject); return mpProject.hasProperty(property); } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/config/java/NoValueAssignedToPropertyQuickFix.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/config/java/NoValueAssignedToPropertyQuickFix.java index 957ba8099..c77ae872a 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/config/java/NoValueAssignedToPropertyQuickFix.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/config/java/NoValueAssignedToPropertyQuickFix.java @@ -92,7 +92,7 @@ public List getCodeActions(JavaCodeActionContext context, String insertText = propertyName + "=" + lineSeparator; PsiMicroProfileProject mpProject = PsiMicroProfileProjectManager.getInstance(javaProject.getProject()). - getJDTMicroProfileProject(javaProject); + getMicroProfileProject(javaProject); List configSources = mpProject.getConfigSources(); for (IConfigSource configSource : configSources) { String uri = configSource.getSourceConfigFileURI(); diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/restclient/java/MicroProfileRestClientCodeLensParticipant.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/restclient/java/MicroProfileRestClientCodeLensParticipant.java index 287995479..6d4421d12 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/restclient/java/MicroProfileRestClientCodeLensParticipant.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/restclient/java/MicroProfileRestClientCodeLensParticipant.java @@ -73,7 +73,7 @@ public List collectCodeLens(JavaCodeLensContext context, ProgressIndic MicroProfileJavaCodeLensParams params = context.getParams(); List lenses = new ArrayList<>(); PsiMicroProfileProject mpProject = PsiMicroProfileProjectManager.getInstance(context.getJavaProject().getProject()) - .getJDTMicroProfileProject(context.getJavaProject()); + .getMicroProfileProject(context.getJavaProject()); collectURLCodeLenses(elements, null, null, mpProject, lenses, params, utils); return lenses; } diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java index 58997c435..e4719bace 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java @@ -14,12 +14,20 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupActivity; import com.redhat.devtools.intellij.lsp4mp4ij.classpath.ClasspathResourceChangedManager; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project.PsiMicroProfileProjectManager; import org.jetbrains.annotations.NotNull; public class QuarkusPostStartupActivity implements StartupActivity, DumbAware { @Override public void runActivity(@NotNull Project project) { ClasspathResourceChangedManager.getInstance(project); + // Force the instantiation of the manager to be sure that classpath listener + // are registered before QuarkusLanguageClient classpath listener + // When an application.properties changed + // - the manager need to update the properties cache + // - and after the QuarkusLanguageClient throws an event to trigger Java validation. + // As java validation requires the properties cache, it needs that cache must be updated before. + PsiMicroProfileProjectManager.getInstance(project); QuarkusProjectService.getInstance(project); } } diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunContext.java b/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunContext.java index 966ebd54b..af87f28cf 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunContext.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunContext.java @@ -26,7 +26,7 @@ public class QuarkusRunContext { private final PsiMicroProfileProject project; public QuarkusRunContext(Module module) { - this.project = PsiMicroProfileProjectManager.getInstance(module.getProject()).getJDTMicroProfileProject(module); + this.project = PsiMicroProfileProjectManager.getInstance(module.getProject()).getMicroProfileProject(module); } protected static QuarkusRunContext getContext(AnActionEvent e) { diff --git a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java index 57063c7b2..9831e89cd 100644 --- a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java +++ b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java @@ -98,7 +98,7 @@ public boolean isSupportNamingStrategy() { public String getDefaultNamingStrategy() { if (defaultNamingStrategy == null) { defaultNamingStrategy = PsiMicroProfileProjectManager.getInstance(javaProject.getProject()) - .getJDTMicroProfileProject(javaProject) + .getMicroProfileProject(javaProject) .getProperty(QuarkusConstants.QUARKUS_ARC_CONFIG_PROPERTIES_DEFAULT_NAMING_STRATEGY, null); if (defaultNamingStrategy != null) { defaultNamingStrategy = QuarkusConstants.NAMING_STRATEGY_PREFIX diff --git a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/jaxrs/java/QuarkusJaxRsCodeLensParticipant.java b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/jaxrs/java/QuarkusJaxRsCodeLensParticipant.java index 39432abb6..97d4fa6e8 100644 --- a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/jaxrs/java/QuarkusJaxRsCodeLensParticipant.java +++ b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/jaxrs/java/QuarkusJaxRsCodeLensParticipant.java @@ -43,7 +43,7 @@ public void beginCodeLens(JavaCodeLensContext context, ProgressIndicator monitor // "quarkus.http.port" Module javaProject = context.getJavaProject(); PsiMicroProfileProject mpProject = PsiMicroProfileProjectManager.getInstance(javaProject.getProject()) - .getJDTMicroProfileProject(javaProject); + .getMicroProfileProject(javaProject); int serverPort = mpProject.getPropertyAsInteger(QUARKUS_HTTP_PORT, JaxRsContext.DEFAULT_PORT); int devServerPort = mpProject.getPropertyAsInteger(QUARKUS_DEV_HTTP_PORT, serverPort); JaxRsContext.getJaxRsContext(context).setServerPort(devServerPort); diff --git a/src/test/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/MicroProfileAssert.java b/src/test/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/MicroProfileAssert.java index e7ec5146b..688595180 100644 --- a/src/test/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/MicroProfileAssert.java +++ b/src/test/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/MicroProfileAssert.java @@ -258,7 +258,7 @@ public static void assertHintsDuplicate(MicroProfileProjectInfo info) { } public static void saveFile(String name, String content, Module javaProject) throws IOException { - saveFile(name, content, javaProject, false); + saveFile(name, content, javaProject, true); } public static void saveFile(String name, String content, Module javaProject, boolean inSource) throws IOException {