This is an automated email from the ASF dual-hosted git repository.

dbalek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new f7f6f74f2e Micronaut Expression Language: Goto Declaration and Hover 
implementation.
     new 45b50b30e3 Merge pull request #6732 from 
dbalek/dbalek/micronaut-expression-language-hyperinks
f7f6f74f2e is described below

commit f7f6f74f2ef9edf4bb79d6bc443a8666b643b93f
Author: Dusan Balek <dusan.ba...@oracle.com>
AuthorDate: Mon Nov 20 18:17:00 2023 +0100

    Micronaut Expression Language: Goto Declaration and Hover implementation.
---
 .../micronaut/MicronautConfigUtilities.java        |  62 +++++++-
 .../completion/MicronautConfigDocumentation.java   |   2 +-
 .../MicronautDataCompletionCollector.java          |   4 +-
 .../MicronautDataCompletionProvider.java           | 116 +++++++++-----
 .../completion/MicronautDataCompletionTask.java    |   4 +-
 .../MicronautExpressionLanguageCompletion.java     |  22 +--
 .../MicronautExpressionLanguageHoverProvider.java  |  42 +++++
 .../micronaut/expression/ExpressionTree.java       | 170 ++++++++++++---------
 .../MicronautExpressionLanguageParser.java         |   3 +
 .../MicronautExpressionLanguageUtilities.java      | 161 +++++++++++++++++++
 .../MicronautConfigHyperlinkProvider.java          |  61 +-------
 .../MicronautExpressionHyperlinkProvider.java      | 161 +++++++++++++++++++
 .../micronaut/resources/mexp.tmLanguage.json       |   4 +-
 13 files changed, 623 insertions(+), 189 deletions(-)

diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/MicronautConfigUtilities.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/MicronautConfigUtilities.java
index 75887a4645..7617172bbb 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/MicronautConfigUtilities.java
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/MicronautConfigUtilities.java
@@ -24,14 +24,25 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.ElementFilter;
 import javax.swing.text.Document;
 import org.netbeans.api.editor.document.EditorDocumentUtils;
 import org.netbeans.api.editor.document.LineDocument;
 import org.netbeans.api.editor.document.LineDocumentUtils;
+import org.netbeans.api.java.source.ClasspathInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.api.java.source.JavaSource;
 import org.netbeans.api.project.FileOwnerQuery;
 import org.netbeans.api.project.Project;
 import org.netbeans.lib.editor.util.swing.DocumentUtilities;
@@ -223,8 +234,8 @@ public class MicronautConfigUtilities {
         }
         return spans;
     }
-    
-    private static ConfigurationMetadataProperty getProperty(Map<String, 
ConfigurationMetadataGroup> groups, String propertyName, 
List<ConfigurationMetadataSource> sources) {
+
+    public static ConfigurationMetadataProperty getProperty(Map<String, 
ConfigurationMetadataGroup> groups, String propertyName, 
List<ConfigurationMetadataSource> sources) {
         for (Map.Entry<String, ConfigurationMetadataGroup> groupEntry : 
groups.entrySet()) {
             String groupKey = groupEntry.getKey();
             if (groupKey.endsWith(".*")) {
@@ -246,6 +257,53 @@ public class MicronautConfigUtilities {
         return null;
     }
 
+    public static ElementHandle getElementHandle(ClasspathInfo cpInfo, String 
typeName, String propertyName, AtomicBoolean cancel) {
+        ElementHandle[] handle = new ElementHandle[1];
+        if (typeName != null) {
+            handle[0] = 
ElementHandle.createTypeElementHandle(ElementKind.CLASS, typeName);
+            if (cpInfo != null && propertyName != null) {
+                try {
+                    JavaSource.create(cpInfo).runUserActionTask(controller -> {
+                        if (cancel != null && cancel.get()) {
+                            return;
+                        }
+                        controller.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+                        TypeElement te = (TypeElement) 
handle[0].resolve(controller);
+                        if (te != null) {
+                            ElementHandle found = null;
+                            String name = "set" + propertyName.replace("-", 
"");
+                            for (ExecutableElement executableElement : 
ElementFilter.methodsIn(te.getEnclosedElements())) {
+                                if 
(name.equalsIgnoreCase(executableElement.getSimpleName().toString())) {
+                                    found = 
ElementHandle.create(executableElement);
+                                    break;
+                                }
+                            }
+                            if (found == null) {
+                                TypeElement typeElement = 
controller.getElements().getTypeElement("io.micronaut.context.annotation.Property");
+                                for (VariableElement variableElement : 
ElementFilter.fieldsIn(te.getEnclosedElements())) {
+                                    for (AnnotationMirror annotationMirror : 
variableElement.getAnnotationMirrors()) {
+                                        if (typeElement == 
annotationMirror.getAnnotationType().asElement()) {
+                                            for (Map.Entry<? extends 
ExecutableElement, ? extends AnnotationValue> entry : 
annotationMirror.getElementValues().entrySet()) {
+                                                if 
("name".contentEquals(entry.getKey().getSimpleName()) && 
propertyName.equals(entry.getValue().getValue())) {
+                                                    found = 
ElementHandle.create(variableElement);
+                                                    break;
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                            if (found != null) {
+                                handle[0] = found;
+                            }
+                        }
+                    }, true);
+                } catch (IOException ex) {}
+            }
+        }
+        return handle[0];
+    }
+
     private static void scan(List<? extends StructureItem> structures, 
Stack<StructureItem> context, Function<Stack<StructureItem>, Boolean> visitor) {
         for (StructureItem structure : structures) {
             if (structure != null) {
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigDocumentation.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigDocumentation.java
index d003cce58f..4dfa388ffc 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigDocumentation.java
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautConfigDocumentation.java
@@ -32,7 +32,7 @@ public class MicronautConfigDocumentation implements 
CompletionDocumentation {
 
     private final ConfigurationMetadataProperty element;
 
-    MicronautConfigDocumentation(ConfigurationMetadataProperty element) {
+    public MicronautConfigDocumentation(ConfigurationMetadataProperty element) 
{
         this.element = element;
     }
 
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionCollector.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionCollector.java
index 7fe3c331f6..04456f1c05 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionCollector.java
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionCollector.java
@@ -31,6 +31,7 @@ import org.netbeans.api.editor.mimelookup.MimeRegistration;
 import org.netbeans.api.java.source.CompilationInfo;
 import org.netbeans.api.lsp.Completion;
 import org.netbeans.api.lsp.TextEdit;
+import 
org.netbeans.modules.micronaut.expression.MicronautExpressionLanguageUtilities;
 import org.netbeans.spi.editor.completion.CompletionItem;
 import org.netbeans.spi.lsp.CompletionCollector;
 
@@ -147,6 +148,7 @@ public class MicronautDataCompletionCollector implements 
CompletionCollector {
                             .sortText(String.format("%04d%s#%02d%s", 100, 
simpleName, cnt, sortParams.toString()))
                             .insertText(insertText.toString())
                             .insertTextFormat(asTemplate ? 
Completion.TextFormat.Snippet : Completion.TextFormat.PlainText)
+                            .documentation(() -> 
MicronautExpressionLanguageUtilities.getJavadocText(info, element, false, 3))
                             .build();
                 }
                 Builder builder = CompletionCollector.newBuilder(simpleName);
@@ -167,7 +169,7 @@ public class MicronautDataCompletionCollector implements 
CompletionCollector {
                     default:
                         throw new IllegalStateException("Unexpected Java 
element kind: " + element.getKind());
                 }
-                return builder.build();
+                return builder.documentation(() -> 
MicronautExpressionLanguageUtilities.getJavadocText(info, element, false, 
3)).build();
             }
         }).stream().forEach(consumer);
         return true;
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionProvider.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionProvider.java
index a57832f97a..09a03fec10 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionProvider.java
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionProvider.java
@@ -22,6 +22,8 @@ import java.awt.Color;
 import java.io.CharConversionException;
 import java.net.URL;
 import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
@@ -35,6 +37,7 @@ import javax.swing.text.Document;
 import javax.swing.text.JTextComponent;
 import org.netbeans.api.editor.mimelookup.MimeRegistration;
 import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ui.ElementJavadoc;
 import org.netbeans.lib.editor.codetemplates.api.CodeTemplateManager;
 import org.netbeans.spi.editor.completion.CompletionDocumentation;
 import org.netbeans.spi.editor.completion.CompletionItem;
@@ -187,46 +190,30 @@ public final class MicronautDataCompletionProvider 
implements CompletionProvider
 
                 @Override
                 public CompletionItem createEnvPropertyItem(String name, 
String documentation, int anchorOffset, int offset) {
+                    CompletionDocumentation cd = new CompletionDocumentation() 
{
+                        @Override
+                        public String getText() {
+                            return documentation;
+                        }
+                        @Override
+                        public URL getURL() {
+                            return null;
+                        }
+                        @Override
+                        public CompletionDocumentation resolveLink(String 
link) {
+                            return null;
+                        }
+                        @Override
+                        public Action getGotoSourceAction() {
+                            return null;
+                        }
+                    };
                     return CompletionUtilities.newCompletionItemBuilder(name)
                             .iconResource(ATTRIBUTE_VALUE)
                             .leftHtmlText(ATTRIBUTE_VALUE_COLOR + name + 
COLOR_END)
                             .sortPriority(30)
                             .startOffset(anchorOffset)
-                            .documentationTask(() -> {
-                                return documentation == null ? null : new 
CompletionTask() {
-                                    private CompletionDocumentation cd = new 
CompletionDocumentation() {
-                                        @Override
-                                        public String getText() {
-                                            return documentation;
-                                        }
-                                        @Override
-                                        public URL getURL() {
-                                            return null;
-                                        }
-                                        @Override
-                                        public CompletionDocumentation 
resolveLink(String link) {
-                                            return null;
-                                        }
-                                        @Override
-                                        public Action getGotoSourceAction() {
-                                            return null;
-                                        }
-                                    };
-                                    @Override
-                                    public void query(CompletionResultSet 
resultSet) {
-                                        resultSet.setDocumentation(cd);
-                                        resultSet.finish();
-                                    }
-                                    @Override
-                                    public void refresh(CompletionResultSet 
resultSet) {
-                                        resultSet.setDocumentation(cd);
-                                        resultSet.finish();
-                                    }
-                                    @Override
-                                    public void cancel() {
-                                    }
-                                };
-                            })
+                            .documentationTask(getDocTask(cd, null))
                             .build();
                 }
 
@@ -286,7 +273,8 @@ public final class MicronautDataCompletionProvider 
implements CompletionProvider
                         } else {
                             builder.insertText(insertText.toString());
                         }
-                        return builder.build();
+                        AtomicBoolean cancel = new AtomicBoolean();
+                        return builder.documentationTask(getDocTask(new 
JavaCompletionDoc(ElementJavadoc.create(info, element, () -> cancel.get())), 
cancel)).build();
                     }
                     CompletionUtilities.CompletionItemBuilder builder = 
CompletionUtilities.newCompletionItemBuilder(simpleName).startOffset(offset);
                     switch (element.getKind()) {
@@ -310,7 +298,8 @@ public final class MicronautDataCompletionProvider 
implements CompletionProvider
                         default:
                             throw new IllegalStateException("Unexpected Java 
element kind: " + element.getKind());
                     }
-                    return builder.build();
+                    AtomicBoolean cancel = new AtomicBoolean();
+                    return builder.documentationTask(getDocTask(new 
JavaCompletionDoc(ElementJavadoc.create(info, element, () -> cancel.get())), 
cancel)).build();
                 }
             }));
             resultSet.setAnchorOffset(task.getAnchorOffset());
@@ -318,6 +307,29 @@ public final class MicronautDataCompletionProvider 
implements CompletionProvider
         }
     }
 
+    private static Supplier<CompletionTask> getDocTask(CompletionDocumentation 
doc, AtomicBoolean cancel) {
+        return () -> {
+            return new CompletionTask() {
+                @Override
+                public void query(CompletionResultSet resultSet) {
+                    resultSet.setDocumentation(doc);
+                    resultSet.finish();
+                }
+                @Override
+                public void refresh(CompletionResultSet resultSet) {
+                    resultSet.setDocumentation(doc);
+                    resultSet.finish();
+                }
+                @Override
+                public void cancel() {
+                    if (cancel != null) {
+                        cancel.set(true);
+                    }
+                }
+            };
+        };
+    }
+
     private static String getHTMLColor(int r, int g, int b) {
         Color c = LFCustoms.shiftColor(new Color(r, g, b));
         return "<font color=#" //NOI18N
@@ -335,4 +347,34 @@ public final class MicronautDataCompletionProvider 
implements CompletionProvider
         }
         return s;
     }
+
+    private static class JavaCompletionDoc implements CompletionDocumentation {
+
+        private final ElementJavadoc elementJavadoc;
+
+        private JavaCompletionDoc(ElementJavadoc elementJavadoc) {
+            this.elementJavadoc = elementJavadoc;
+        }
+
+        @Override
+        public JavaCompletionDoc resolveLink(String link) {
+            ElementJavadoc doc = elementJavadoc.resolveLink(link);
+            return doc != null ? new JavaCompletionDoc(doc) : null;
+        }
+
+        @Override
+        public URL getURL() {
+            return elementJavadoc.getURL();
+        }
+
+        @Override
+        public String getText() {
+            return elementJavadoc.getText();
+        }
+
+        @Override
+        public Action getGotoSourceAction() {
+            return elementJavadoc.getGotoSourceAction();
+        }
+    }
 }
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionTask.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionTask.java
index 4fde2b0d92..317a1f50d8 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionTask.java
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautDataCompletionTask.java
@@ -67,6 +67,7 @@ import 
org.netbeans.modules.db.sql.editor.api.completion.SQLCompletion;
 import org.netbeans.modules.db.sql.editor.api.completion.SQLCompletionContext;
 import 
org.netbeans.modules.db.sql.editor.api.completion.SQLCompletionResultSet;
 import org.netbeans.modules.micronaut.expression.EvaluationContext;
+import 
org.netbeans.modules.micronaut.expression.MicronautExpressionLanguageParser;
 import org.netbeans.modules.parsing.api.ParserManager;
 import org.netbeans.modules.parsing.api.ResultIterator;
 import org.netbeans.modules.parsing.api.Source;
@@ -104,7 +105,6 @@ public class MicronautDataCompletionTask {
     private static final boolean COMPLETION_CASE_SENSITIVE_DEFAULT = true;
     private static final String JAVA_COMPLETION_SUBWORDS = 
"javaCompletionSubwords"; //NOI18N
     private static final boolean JAVA_COMPLETION_SUBWORDS_DEFAULT = false;
-    private static final Pattern MEXP_PATTERN = Pattern.compile("#\\{(.*?)}");
     private static final PreferenceChangeListener preferencesTracker = new 
PreferenceChangeListener() {
         @Override
         public void preferenceChange(PreferenceChangeEvent evt) {
@@ -212,7 +212,7 @@ public class MicronautDataCompletionTask {
     }
 
     private <T> List<T> resolveExpressionLanguage(CompilationInfo info, 
TreePath path, String prefix, int off, 
MicronautExpressionLanguageCompletion.ItemFactory<T> factory) {
-        Matcher matcher = MEXP_PATTERN.matcher(prefix);
+        Matcher matcher = 
MicronautExpressionLanguageParser.MEXP_PATTERN.matcher(prefix);
         while (matcher.find() && matcher.groupCount() == 1) {
             if (off >= matcher.start(1) && off <= matcher.end(1)) {
                 EvaluationContext ctx = EvaluationContext.get(info, path);
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautExpressionLanguageCompletion.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautExpressionLanguageCompletion.java
index e388351069..56ae4a01fc 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautExpressionLanguageCompletion.java
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautExpressionLanguageCompletion.java
@@ -114,11 +114,11 @@ public class MicronautExpressionLanguageCompletion {
                                 }
                             }
                         }
-                        TypeMirror treeType = lastTree.getType(ctx);
+                        TypeMirror treeType = lastTree.getTypeMirror(ctx);
                         switch (treeType.getKind()) {
                             case BOOLEAN:
                                 TypeMirror rtm = lastTree instanceof 
ExpressionTree.BinaryExpression
-                                        ? ((ExpressionTree.BinaryExpression) 
lastTree).getRightOperand().getType(ctx)
+                                        ? ((ExpressionTree.BinaryExpression) 
lastTree).getRightOperand().getTypeMirror(ctx)
                                         : 
info.getTypes().getNoType(TypeKind.NONE);
                                 switch (rtm.getKind()) {
                                     case INT:
@@ -207,7 +207,7 @@ public class MicronautExpressionLanguageCompletion {
                         case MATCHES:
                             ExpressionTree.BinaryExpression binary = 
(ExpressionTree.BinaryExpression) path.getLeaf();
                             if (nextNonWSTokenCategory(prefix, 
binary.getRightOperand().getStartPosition()).startsWith("keyword.operator")) {
-                                lastTreeType = 
binary.getLeftOperand().getType(ctx);
+                                lastTreeType = 
binary.getLeftOperand().getTypeMirror(ctx);
                             } else {
                                 if (ctx.getScope().getEnclosingMethod() != 
null) {
                                     kws = Arrays.asList("this");
@@ -220,7 +220,7 @@ public class MicronautExpressionLanguageCompletion {
                         case NOT_EQUAL_TO:
                             binary = (ExpressionTree.BinaryExpression) 
path.getLeaf();
                             if (nextNonWSTokenCategory(prefix, 
binary.getRightOperand().getStartPosition()).startsWith("keyword.operator")) {
-                                lastTreeType = 
binary.getLeftOperand().getType(ctx);
+                                lastTreeType = 
binary.getLeftOperand().getTypeMirror(ctx);
                             } else {
                                 kws = ctx.getScope().getEnclosingMethod() != 
null ? Arrays.asList("null", "this"): Arrays.asList("null");
                                 builtins = Arrays.asList("T", "()", "ctx", 
"[]", "env", "[]");
@@ -231,7 +231,7 @@ public class MicronautExpressionLanguageCompletion {
                         case OR:
                             binary = (ExpressionTree.BinaryExpression) 
path.getLeaf();
                             if (nextNonWSTokenCategory(prefix, 
binary.getRightOperand().getStartPosition()).startsWith("keyword.operator")) {
-                                lastTreeType = 
binary.getLeftOperand().getType(ctx);
+                                lastTreeType = 
binary.getLeftOperand().getTypeMirror(ctx);
                                 break;
                             }
                         case NOT:
@@ -246,7 +246,7 @@ public class MicronautExpressionLanguageCompletion {
                         case INSTANCE_OF:
                             ExpressionTree.InstanceOf instanceOf = 
(ExpressionTree.InstanceOf) path.getLeaf();
                             if (nextNonWSTokenCategory(prefix, 
instanceOf.getType().getStartPosition()).startsWith("keyword.operator")) {
-                                lastTreeType = 
instanceOf.getExpression().getType(ctx);
+                                lastTreeType = 
instanceOf.getExpression().getTypeMirror(ctx);
                             } else {
                                 builtins = Arrays.asList("T", "()");
                             }
@@ -255,7 +255,7 @@ public class MicronautExpressionLanguageCompletion {
                             ExpressionTree.TernaryExpression ternary = 
(ExpressionTree.TernaryExpression) path.getLeaf();
                             String next = nextNonWSTokenCategory(prefix, 
ternary.getTrueExpression().getStartPosition());
                             if 
("keyword.control.ternary.qmark.mexp".equals(next)) {
-                                lastTreeType = 
ternary.getCondition().getType(ctx);
+                                lastTreeType = 
ternary.getCondition().getTypeMirror(ctx);
                             } else {
                                 String prev = prevNonWSTokenText(prefix);
                                 if ("?". equals(prev) || ":".equals(prev)) {
@@ -271,7 +271,7 @@ public class MicronautExpressionLanguageCompletion {
                                 ExpressionTree.PropertyAccess pa = 
(ExpressionTree.PropertyAccess) path.getLeaf();
                                 ExpressionTree callee = pa.getCallee();
                                 if (callee != null) {
-                                    TypeMirror pacTM = callee.getType(ctx);
+                                    TypeMirror pacTM = 
callee.getTypeMirror(ctx);
                                     if (pacTM.getKind() == TypeKind.DECLARED) {
                                         elements = 
ElementFilter.methodsIn(((DeclaredType) 
pacTM).asElement().getEnclosedElements()).stream()
                                                 .filter(ee -> callee.getKind() 
!= ExpressionTree.Kind.TYPE_REFERENCE || 
ee.getModifiers().contains(Modifier.STATIC))
@@ -288,7 +288,7 @@ public class MicronautExpressionLanguageCompletion {
                                 ExpressionTree.MethodCall methCall = 
(ExpressionTree.MethodCall) path.getLeaf();
                                 ExpressionTree callee = methCall.getCallee();
                                 if (callee != null) {
-                                    TypeMirror methTM = callee.getType(ctx);
+                                    TypeMirror methTM = 
callee.getTypeMirror(ctx);
                                     if (methTM.getKind() == TypeKind.DECLARED) 
{
                                         elements = 
ElementFilter.methodsIn(((DeclaredType) 
methTM).asElement().getEnclosedElements()).stream()
                                                 .filter(ee -> callee.getKind() 
!= ExpressionTree.Kind.TYPE_REFERENCE || 
ee.getModifiers().contains(Modifier.STATIC))
@@ -311,7 +311,7 @@ public class MicronautExpressionLanguageCompletion {
                                 if (path.getLeaf().getKind() == 
ExpressionTree.Kind.TERNARY) {
                                     ExpressionTree.TernaryExpression ternary = 
(ExpressionTree.TernaryExpression) path.getLeaf();
                                     if (ternary.getCondition() instanceof 
ExpressionTree.BinaryExpression) {
-                                        rtm = 
((ExpressionTree.BinaryExpression) 
ternary.getCondition()).getRightOperand().getType(ctx);
+                                        rtm = 
((ExpressionTree.BinaryExpression) 
ternary.getCondition()).getRightOperand().getTypeMirror(ctx);
                                     }
                                 }
                                 switch (rtm.getKind()) {
@@ -331,7 +331,7 @@ public class MicronautExpressionLanguageCompletion {
                             case DOUBLE:
                                 ExpressionTree.Path parentPath = 
path.getParentPath();
                                 TypeMirror ptm = parentPath != null && 
parentPath.getLeaf() instanceof ExpressionTree.BinaryExpression
-                                        ? parentPath.getLeaf().getType(ctx)
+                                        ? 
parentPath.getLeaf().getTypeMirror(ctx)
                                         : 
info.getTypes().getNoType(TypeKind.NONE);
                                 if (ptm.getKind() == TypeKind.BOOLEAN) {
                                     kws = Arrays.asList("and", "or", "div", 
"mod", "instanceof");
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautExpressionLanguageHoverProvider.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautExpressionLanguageHoverProvider.java
new file mode 100644
index 0000000000..98e7beb9b4
--- /dev/null
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/completion/MicronautExpressionLanguageHoverProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.micronaut.completion;
+
+import java.util.concurrent.CompletableFuture;
+import javax.swing.text.Document;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import 
org.netbeans.modules.micronaut.expression.MicronautExpressionLanguageUtilities;
+import org.netbeans.spi.lsp.HoverProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@MimeRegistration(mimeType = "text/x-java", service = HoverProvider.class)
+public class MicronautExpressionLanguageHoverProvider implements HoverProvider 
{
+
+    @Override
+    public CompletableFuture<String> getHoverContent(Document doc, int offset) 
{
+        return 
CompletableFuture.completedFuture(MicronautExpressionLanguageUtilities.resolve(doc,
 offset, (info, element) -> {
+            return MicronautExpressionLanguageUtilities.getJavadocText(info, 
element, false, 1);
+        }, (property, source) -> {
+            return new MicronautConfigDocumentation(property).getText();
+        }));
+    }
+}
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/ExpressionTree.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/ExpressionTree.java
index cbd9ef001d..6e62b91f21 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/ExpressionTree.java
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/ExpressionTree.java
@@ -22,6 +22,7 @@ import java.beans.Introspector;
 import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
+import javax.lang.model.element.Element;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
@@ -39,7 +40,8 @@ import javax.lang.model.util.ElementFilter;
 public abstract class ExpressionTree {
 
     private final Kind kind;
-    private TypeMirror type;
+    protected Element element;
+    protected TypeMirror typeMirror;
 
     private ExpressionTree(Kind kind) {
         this.kind = kind;
@@ -49,19 +51,26 @@ public abstract class ExpressionTree {
         return kind;
     }
 
-    public TypeMirror getType(EvaluationContext ctx) {
-        if (type == null) {
-            type = resolve(ctx);
+    public Element getElement(EvaluationContext ctx) {
+        if (typeMirror == null && element == null) {
+            resolve(ctx);
         }
-        return type;
+        return element;
+    }
+
+    public TypeMirror getTypeMirror(EvaluationContext ctx) {
+        if (typeMirror == null && element == null) {
+            resolve(ctx);
+        }
+        return typeMirror;
     }
 
     public abstract int getStartPosition();
     public abstract int getEndPosition();
     public abstract <R,D> R accept(Scanner<R,D> scanner, D data);
 
-    protected TypeMirror resolve(EvaluationContext ctx) {
-        return ctx.getTypes().getNoType(TypeKind.NONE);
+    protected void resolve(EvaluationContext ctx) {
+        typeMirror = ctx.getTypes().getNoType(TypeKind.NONE);
     }
 
     private static TypeMirror unbox(EvaluationContext ctx, TypeMirror tm) {
@@ -120,22 +129,29 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
+        protected void resolve(EvaluationContext ctx) {
             switch (getKind()) {
                 case NULL_LITERAL:
-                    return ctx.getTypes().getNullType();
+                    typeMirror = ctx.getTypes().getNullType();
+                    break;
                 case BOOLEAN_LITERAL:
-                    return ctx.getTypes().getPrimitiveType(TypeKind.BOOLEAN);
+                    typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.BOOLEAN);
+                    break;
                 case STRING_LITERAL:
-                    return 
ctx.getElements().getTypeElement("java.lang.String").asType();
+                    typeMirror = 
ctx.getElements().getTypeElement("java.lang.String").asType();
+                    break;
                 case INT_LITERAL:
-                    return ctx.getTypes().getPrimitiveType(TypeKind.INT);
+                    typeMirror = ctx.getTypes().getPrimitiveType(TypeKind.INT);
+                    break;
                 case LONG_LITERAL:
-                    return ctx.getTypes().getPrimitiveType(TypeKind.LONG);
+                    typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.LONG);
+                    break;
                 case FLOAT_LITERAL:
-                    return ctx.getTypes().getPrimitiveType(TypeKind.FLOAT);
+                    typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.FLOAT);
+                    break;
                 case DOUBLE_LITERAL:
-                    return ctx.getTypes().getPrimitiveType(TypeKind.DOUBLE);
+                    typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.DOUBLE);
+                    break;
                 default:
                     throw new AssertionError("Unexpected kind: " + getKind());
             }
@@ -173,12 +189,13 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
-            TypeMirror tm = unbox(ctx, expression.getType(ctx));
+        protected void resolve(EvaluationContext ctx) {
+            TypeMirror tm = unbox(ctx, expression.getTypeMirror(ctx));
             switch (getKind()) {
                 case NOT:
                 case EMPTY:
-                    return ctx.getTypes().getPrimitiveType(TypeKind.BOOLEAN);
+                    typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.BOOLEAN);
+                    break;
                 case PLUS:
                 case MINUS:
                     switch (tm.getKind()) {
@@ -186,10 +203,12 @@ public abstract class ExpressionTree {
                         case LONG:
                         case FLOAT:
                         case DOUBLE:
-                            return tm;
+                            typeMirror = tm;
+                            break;
                         default:
-                            return ctx.getTypes().getNoType(TypeKind.INT);
+                            typeMirror = 
ctx.getTypes().getNoType(TypeKind.INT);
                     }
+                    break;
                 default:
                     throw new AssertionError("Unexpected kind: " + getKind());
             }
@@ -231,9 +250,9 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
-            TypeMirror leftTM = unbox(ctx, left.getType(ctx));
-            TypeMirror rightTM = unbox(ctx, right.getType(ctx));
+        protected void resolve(EvaluationContext ctx) {
+            TypeMirror leftTM = unbox(ctx, left.getTypeMirror(ctx));
+            TypeMirror rightTM = unbox(ctx, right.getTypeMirror(ctx));
             switch (getKind()) {
                 case EQUAL_TO:
                 case NOT_EQUAL_TO:
@@ -245,10 +264,12 @@ public abstract class ExpressionTree {
                 case AND:
                 case OR:
                 case ELVIS:
-                    return ctx.getTypes().getPrimitiveType(TypeKind.BOOLEAN);
+                    typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.BOOLEAN);
+                    break;
                 case PLUS:
                     if (leftTM.getKind() == TypeKind.DECLARED && 
"java.lang.String".contentEquals(((TypeElement) ((DeclaredType) 
leftTM).asElement()).getQualifiedName())) {
-                        return leftTM;
+                        typeMirror = leftTM;
+                        break;
                     }
                 case MINUS:
                 case MULTIPLY:
@@ -256,16 +277,15 @@ public abstract class ExpressionTree {
                 case REMAINDER:
                 case POWER:
                     if (leftTM.getKind() == TypeKind.DOUBLE || 
rightTM.getKind() == TypeKind.DOUBLE) {
-                        return 
ctx.getTypes().getPrimitiveType(TypeKind.DOUBLE);
-                    }
-                    if (leftTM.getKind() == TypeKind.FLOAT || 
rightTM.getKind() == TypeKind.FLOAT) {
-                        return ctx.getTypes().getPrimitiveType(TypeKind.FLOAT);
-                    }
-                    if (leftTM.getKind() == TypeKind.LONG || rightTM.getKind() 
== TypeKind.LONG) {
-                        return ctx.getTypes().getPrimitiveType(TypeKind.LONG);
+                        typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.DOUBLE);
+                    } else if (leftTM.getKind() == TypeKind.FLOAT || 
rightTM.getKind() == TypeKind.FLOAT) {
+                        typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.FLOAT);
+                    } else if (leftTM.getKind() == TypeKind.LONG || 
rightTM.getKind() == TypeKind.LONG) {
+                        typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.LONG);
                     } else {
-                        return ctx.getTypes().getPrimitiveType(TypeKind.INT);
+                        typeMirror = 
ctx.getTypes().getPrimitiveType(TypeKind.INT);
                     }
+                    break;
                 default:
                     throw new AssertionError("Unexpected kind: " + getKind());
             }
@@ -307,8 +327,8 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
-            return ctx.getTypes().getPrimitiveType(TypeKind.BOOLEAN);
+        protected void resolve(EvaluationContext ctx) {
+            typeMirror = ctx.getTypes().getPrimitiveType(TypeKind.BOOLEAN);
         }
     }
 
@@ -353,22 +373,20 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
-            TypeMirror trueTM = unbox(ctx, trueExpression.getType(ctx));
-            TypeMirror falseTM = unbox(ctx, falseExpression.getType(ctx));
+        protected void resolve(EvaluationContext ctx) {
+            TypeMirror trueTM = unbox(ctx, trueExpression.getTypeMirror(ctx));
+            TypeMirror falseTM = unbox(ctx, 
falseExpression.getTypeMirror(ctx));
             if (trueTM.getKind() == TypeKind.DOUBLE || falseTM.getKind() == 
TypeKind.DOUBLE) {
-                return ctx.getTypes().getPrimitiveType(TypeKind.DOUBLE);
-            }
-            if (trueTM.getKind() == TypeKind.FLOAT || falseTM.getKind() == 
TypeKind.FLOAT) {
-                return ctx.getTypes().getPrimitiveType(TypeKind.FLOAT);
-            }
-            if (trueTM.getKind() == TypeKind.LONG || falseTM.getKind() == 
TypeKind.LONG) {
-                return ctx.getTypes().getPrimitiveType(TypeKind.LONG);
-            }
-            if (trueTM.getKind() == TypeKind.INT || falseTM.getKind() == 
TypeKind.INT) {
-                return ctx.getTypes().getPrimitiveType(TypeKind.INT);
+                typeMirror = ctx.getTypes().getPrimitiveType(TypeKind.DOUBLE);
+            } else if (trueTM.getKind() == TypeKind.FLOAT || falseTM.getKind() 
== TypeKind.FLOAT) {
+                typeMirror = ctx.getTypes().getPrimitiveType(TypeKind.FLOAT);
+            } else if (trueTM.getKind() == TypeKind.LONG || falseTM.getKind() 
== TypeKind.LONG) {
+                typeMirror = ctx.getTypes().getPrimitiveType(TypeKind.LONG);
+            } else if (trueTM.getKind() == TypeKind.INT || falseTM.getKind() 
== TypeKind.INT) {
+                typeMirror = ctx.getTypes().getPrimitiveType(TypeKind.INT);
+            } else {
+                typeMirror = trueTM;
             }
-            return trueTM;
         }
     }
 
@@ -405,8 +423,8 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
-            return expression.resolve(ctx);
+        protected void resolve(EvaluationContext ctx) {
+            typeMirror = expression.getTypeMirror(ctx);
         }
     }
 
@@ -449,12 +467,12 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
-            TypeElement te = ctx.getElements().getTypeElement(typeName);
-            if (te == null) {
-                te = ctx.getElements().getTypeElement("java.lang." + typeName);
+        protected void resolve(EvaluationContext ctx) {
+            element = ctx.getElements().getTypeElement(typeName);
+            if (element == null) {
+                element = ctx.getElements().getTypeElement("java.lang." + 
typeName);
             }
-            return te != null ? te.asType() : 
ctx.getTypes().getNoType(TypeKind.NONE);
+            typeMirror = element != null ? element.asType() : 
ctx.getTypes().getNoType(TypeKind.NONE);
         }
     }
 
@@ -485,9 +503,9 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
+        protected void resolve(EvaluationContext ctx) {
             TypeElement te = ctx.getScope().getEnclosingClass();
-            return te != null ? te.asType() : 
ctx.getTypes().getNoType(TypeKind.NONE);
+            typeMirror = te != null ? te.asType() : 
ctx.getTypes().getNoType(TypeKind.NONE);
         }
     }
 
@@ -545,20 +563,20 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
+        protected void resolve(EvaluationContext ctx) {
             List<ExecutableElement> methods = null;
             DeclaredType dt = null;
             if (callee == null) {
                 methods = ctx.getContextMethods();
             } else {
-                TypeMirror calleeTM = callee.getType(ctx);
+                TypeMirror calleeTM = callee.getTypeMirror(ctx);
                 if (calleeTM.getKind() == TypeKind.DECLARED) {
                     dt = (DeclaredType) calleeTM;
                     methods = ElementFilter.methodsIn(((TypeElement) 
dt.asElement()).getEnclosedElements());
                 }
             }
             if (methods != null && !methods.isEmpty()) {
-                List<TypeMirror> argTypes = arguments.stream().map(arg -> 
arg.getType(ctx)).collect(Collectors.toList());
+                List<TypeMirror> argTypes = arguments.stream().map(arg -> 
arg.getTypeMirror(ctx)).collect(Collectors.toList());
                 for (ExecutableElement ee : methods) {
                     TypeMirror enclType = dt != null ? dt : 
ee.getEnclosingElement().asType();
                     if (enclType.getKind() == TypeKind.DECLARED && 
identifier.contentEquals(ee.getSimpleName()) && 
ctx.getTrees().isAccessible(ctx.getScope(), ee, (DeclaredType) enclType)) {
@@ -578,13 +596,15 @@ public abstract class ExpressionTree {
                                 }
                             }
                             if (match) {
-                                return et.getReturnType();
+                                element = ee;
+                                typeMirror = et.getReturnType();
+                                return;
                             }
                         }
                     }
                 }
             }
-            return ctx.getTypes().getNoType(TypeKind.NONE);
+            typeMirror = ctx.getTypes().getNoType(TypeKind.NONE);
         }
     }
 
@@ -635,13 +655,13 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
+        protected void resolve(EvaluationContext ctx) {
             List<ExecutableElement> methods = null;
             DeclaredType dt = null;
             if (callee == null) {
                 methods = ctx.getContextMethods();
             } else {
-                TypeMirror calleeTM = callee.getType(ctx);
+                TypeMirror calleeTM = callee.getTypeMirror(ctx);
                 if (calleeTM.getKind() == TypeKind.DECLARED) {
                     dt = (DeclaredType) calleeTM;
                     methods = ElementFilter.methodsIn(((TypeElement) 
dt.asElement()).getEnclosedElements());
@@ -652,11 +672,13 @@ public abstract class ExpressionTree {
                     TypeMirror enclType = dt != null ? dt : 
ee.getEnclosingElement().asType();
                     if (enclType.getKind() == TypeKind.DECLARED && 
identifier.equals(getPropertyName(ee)) && 
ctx.getTrees().isAccessible(ctx.getScope(), ee, (DeclaredType) enclType)) {
                         ExecutableType et = (ExecutableType) 
ctx.getTypes().asMemberOf((DeclaredType) enclType, ee);
-                        return et.getReturnType();
+                        element = ee;
+                        typeMirror = et.getReturnType();
+                        return;
                     }
                 }
             }
-            return ctx.getTypes().getNoType(TypeKind.NONE);
+            typeMirror = ctx.getTypes().getNoType(TypeKind.NONE);
         }
     }
 
@@ -697,9 +719,9 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
-            TypeMirror calleeTM = callee.getType(ctx);
-            return calleeTM.getKind() == TypeKind.ARRAY ? ((ArrayType) 
calleeTM).getComponentType() : ctx.getTypes().getNoType(TypeKind.NONE);
+        protected void resolve(EvaluationContext ctx) {
+            TypeMirror calleeTM = callee.getTypeMirror(ctx);
+            typeMirror = calleeTM.getKind() == TypeKind.ARRAY ? ((ArrayType) 
calleeTM).getComponentType() : ctx.getTypes().getNoType(TypeKind.NONE);
         }
     }
 
@@ -736,8 +758,8 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
-            return getTypeReference().getType(ctx);
+        protected void resolve(EvaluationContext ctx) {
+            typeMirror = typeReference.getTypeMirror(ctx);
         }
     }
 
@@ -774,8 +796,8 @@ public abstract class ExpressionTree {
         }
 
         @Override
-        protected TypeMirror resolve(EvaluationContext ctx) {
-            return 
ctx.getElements().getTypeElement("java.lang.String").asType();
+        protected void resolve(EvaluationContext ctx) {
+            typeMirror = 
ctx.getElements().getTypeElement("java.lang.String").asType();
         }
     }
 
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/MicronautExpressionLanguageParser.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/MicronautExpressionLanguageParser.java
index 08c15d3267..85cd2df304 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/MicronautExpressionLanguageParser.java
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/MicronautExpressionLanguageParser.java
@@ -21,6 +21,7 @@ package org.netbeans.modules.micronaut.expression;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.regex.Pattern;
 import org.netbeans.api.lexer.Language;
 import org.netbeans.api.lexer.TokenHierarchy;
 import org.netbeans.api.lexer.TokenSequence;
@@ -32,6 +33,8 @@ import static 
org.netbeans.modules.micronaut.expression.ExpressionTree.*;
  */
 public class MicronautExpressionLanguageParser {
 
+    public static final Pattern MEXP_PATTERN = Pattern.compile("#\\{(.*?)}");
+
     private final TokenSequence<?> ts;
     private String tokenType;
     private int lastPos;
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/MicronautExpressionLanguageUtilities.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/MicronautExpressionLanguageUtilities.java
new file mode 100644
index 0000000000..5e332c9523
--- /dev/null
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/expression/MicronautExpressionLanguageUtilities.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.micronaut.expression;
+
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.lang.model.element.Element;
+import javax.swing.text.Document;
+import org.netbeans.api.java.source.CompilationController;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.ui.ElementJavadoc;
+import org.netbeans.api.project.FileOwnerQuery;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.micronaut.MicronautConfigProperties;
+import org.netbeans.modules.micronaut.MicronautConfigUtilities;
+import org.netbeans.modules.parsing.api.ParserManager;
+import org.netbeans.modules.parsing.api.ResultIterator;
+import org.netbeans.modules.parsing.api.Source;
+import org.netbeans.modules.parsing.api.UserTask;
+import org.netbeans.modules.parsing.spi.ParseException;
+import org.openide.util.Exceptions;
+import org.openide.util.NbBundle;
+import 
org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
+import 
org.springframework.boot.configurationmetadata.ConfigurationMetadataSource;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public class MicronautExpressionLanguageUtilities {
+
+    private static final Pattern LINK_PATTERN = Pattern.compile("<a 
href='(\\*\\d+)'>(.*?)<\\/a>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
+
+    public static <T> T resolve(Document doc, int offset, 
BiFunction<CompilationInfo, Element, T> withElement, 
BiFunction<ConfigurationMetadataProperty, ConfigurationMetadataSource, T> 
withProperty) {
+        AtomicReference<T> ret = new AtomicReference<>();
+        try {
+            ParserManager.parse(Collections.singleton(Source.create(doc)), new 
UserTask() {
+                @Override
+                public void run(ResultIterator resultIterator) throws 
Exception {
+                    CompilationController cc = 
CompilationController.get(resultIterator.getParserResult(offset));
+                    if (cc != null) {
+                        cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+                        TreePath treePath = 
cc.getTreeUtilities().pathFor(offset);
+                        if (treePath.getLeaf().getKind() == 
Tree.Kind.STRING_LITERAL) {
+                            int off = offset - (int) 
cc.getTrees().getSourcePositions().getStartPosition(treePath.getCompilationUnit(),
 treePath.getLeaf()) - 1;
+                            Matcher matcher = 
MicronautExpressionLanguageParser.MEXP_PATTERN.matcher((String) ((LiteralTree) 
treePath.getLeaf()).getValue());
+                            while (matcher.find() && matcher.groupCount() == 
1) {
+                                if (off >= matcher.start(1) && off <= 
matcher.end(1)) {
+                                    MicronautExpressionLanguageParser parser = 
new MicronautExpressionLanguageParser(matcher.group(1));
+                                    ExpressionTree tree = parser.parse();
+                                    ExpressionTree.Path path = 
ExpressionTree.Path.get(tree, off - matcher.start(1));
+                                    if (path != null) {
+                                        Element el = 
path.getLeaf().getElement(EvaluationContext.get(cc, treePath));
+                                        if (el != null) {
+                                            ret.set(withElement.apply(cc, el));
+                                        } else if (path.getLeaf().getKind() == 
ExpressionTree.Kind.STRING_LITERAL && path.getParentPath() != null
+                                                && 
path.getParentPath().getLeaf().getKind() == 
ExpressionTree.Kind.ENVIRONMENT_ACCESS) {
+                                            Project project = 
FileOwnerQuery.getOwner(cc.getFileObject());
+                                            if (project != null) {
+                                                String propertyName = (String) 
((ExpressionTree.Literal) path.getLeaf()).getValue();
+                                                
List<ConfigurationMetadataSource> sources = new ArrayList<>();
+                                                ConfigurationMetadataProperty 
property = 
MicronautConfigUtilities.getProperty(MicronautConfigProperties.getGroups(project),
 propertyName, sources);
+                                                if (property != null) {
+                                                    
Optional<ConfigurationMetadataSource> source = sources.stream().filter(s -> 
s.getProperties().get(property.getId()) == property).findFirst();
+                                                    
ret.set(withProperty.apply(property, source.orElse(null)));
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            });
+        } catch (ParseException pe) {
+            Exceptions.printStackTrace(pe);
+        }
+        return ret.get();
+    }
+
+    @NbBundle.Messages({
+        "LBL_More=..."
+    })
+    public static String getJavadocText(CompilationInfo info, Element e, 
boolean shorten, int timeoutInSeconds) {
+        String text = null;
+        try {
+            AtomicBoolean cancel = new AtomicBoolean();
+            ElementJavadoc javadoc = ElementJavadoc.create(info, e, () -> 
cancel.get());
+            javadoc.getTextAsync();
+            text = javadoc.getTextAsync() != null ? 
javadoc.getTextAsync().get(timeoutInSeconds, TimeUnit.SECONDS) : null;
+            if (text != null) {
+                text = resolveLinks(text, javadoc);
+                if (shorten) {
+                    int idx = 0;
+                    for (int i = 0; i < 3 && idx >= 0; i++) {
+                        idx = text.indexOf("<p>", idx + 1); //NOI18N
+                    }
+                    if (idx >= 0) {
+                        text = text.substring(0, idx + 3);
+                        text += Bundle.LBL_More();
+                    }
+                }
+                int idx = text.indexOf("<p id=\"not-found\">"); //NOI18N
+                if (idx >= 0) {
+                    text = text.substring(0, idx);
+                }
+            }
+            cancel.set(true);
+        } catch (Exception ex) {}
+        return text;
+    }
+
+    private static String resolveLinks(String content, ElementJavadoc doc) {
+        Matcher matcher = LINK_PATTERN.matcher(content);
+        String updatedContent = matcher.replaceAll(result -> {
+            if (result.groupCount() == 2) {
+                try {
+                    ElementJavadoc link = doc.resolveLink(result.group(1));
+                    URL url = link != null ? link.getURL() : null;
+                    if (url != null) {
+                        return "<a href='" + url.toString() + "'>" + 
result.group(2) + "</a>";
+                    }
+                } catch (Exception ex) {}
+                return result.group(2);
+            }
+            return result.group();
+        });
+        return updatedContent;
+    }
+
+}
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/hyperlink/MicronautConfigHyperlinkProvider.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/hyperlink/MicronautConfigHyperlinkProvider.java
index dc2e66ee3b..2641c8da4a 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/hyperlink/MicronautConfigHyperlinkProvider.java
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/hyperlink/MicronautConfigHyperlinkProvider.java
@@ -19,28 +19,18 @@
 package org.netbeans.modules.micronaut.hyperlink;
 
 import java.awt.Toolkit;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicBoolean;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.util.ElementFilter;
 import javax.swing.text.Document;
 import org.netbeans.api.editor.mimelookup.MimeRegistration;
 import org.netbeans.api.java.source.ClasspathInfo;
 import org.netbeans.api.java.source.ElementHandle;
-import org.netbeans.api.java.source.JavaSource;
 import org.netbeans.api.java.source.ui.ElementOpen;
 import org.netbeans.api.lsp.HyperlinkLocation;
 import org.netbeans.api.progress.BaseProgressUtils;
@@ -128,7 +118,7 @@ public class MicronautConfigHyperlinkProvider implements 
HyperlinkProviderExt {
                 ClasspathInfo cpInfo = ClasspathInfo.create(doc);
                 for (ConfigurationMetadataSource source : sources) {
                     if (property == null || 
source.getProperties().get(property.getId()) == property) {
-                        ElementHandle handle = getElementHandle(cpInfo, 
source.getType(), property != null ? property.getName() : null, cancel);
+                        ElementHandle handle = 
MicronautConfigUtilities.getElementHandle(cpInfo, source.getType(), property != 
null ? property.getName() : null, cancel);
                         if (handle != null && ElementOpen.open(cpInfo, 
handle)) {
                             return;
                         }
@@ -154,53 +144,6 @@ public class MicronautConfigHyperlinkProvider implements 
HyperlinkProviderExt {
         return null;
     }
 
-    private static ElementHandle getElementHandle(ClasspathInfo cpInfo, String 
typeName, String propertyName, AtomicBoolean cancel) {
-        ElementHandle[] handle = new ElementHandle[1];
-        if (typeName != null) {
-            handle[0] = 
ElementHandle.createTypeElementHandle(ElementKind.CLASS, typeName);
-            if (cpInfo != null && propertyName != null) {
-                try {
-                    JavaSource.create(cpInfo).runUserActionTask(controller -> {
-                        if (cancel != null && cancel.get()) {
-                            return;
-                        }
-                        controller.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
-                        TypeElement te = (TypeElement) 
handle[0].resolve(controller);
-                        if (te != null) {
-                            ElementHandle found = null;
-                            String name = "set" + propertyName.replace("-", 
"");
-                            for (ExecutableElement executableElement : 
ElementFilter.methodsIn(te.getEnclosedElements())) {
-                                if 
(name.equalsIgnoreCase(executableElement.getSimpleName().toString())) {
-                                    found = 
ElementHandle.create(executableElement);
-                                    break;
-                                }
-                            }
-                            if (found == null) {
-                                TypeElement typeElement = 
controller.getElements().getTypeElement("io.micronaut.context.annotation.Property");
-                                for (VariableElement variableElement : 
ElementFilter.fieldsIn(te.getEnclosedElements())) {
-                                    for (AnnotationMirror annotationMirror : 
variableElement.getAnnotationMirrors()) {
-                                        if (typeElement == 
annotationMirror.getAnnotationType().asElement()) {
-                                            for (Map.Entry<? extends 
ExecutableElement, ? extends AnnotationValue> entry : 
annotationMirror.getElementValues().entrySet()) {
-                                                if 
("name".contentEquals(entry.getKey().getSimpleName()) && 
propertyName.equals(entry.getValue().getValue())) {
-                                                    found = 
ElementHandle.create(variableElement);
-                                                    break;
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                            if (found != null) {
-                                handle[0] = found;
-                            }
-                        }
-                    }, true);
-                } catch (IOException ex) {}
-            }
-        }
-        return handle[0];
-    }
-
     public static final class Task extends 
IndexingAwareParserResultTask<Parser.Result> {
     
         private final AtomicBoolean cancel = new AtomicBoolean();
@@ -279,7 +222,7 @@ public class MicronautConfigHyperlinkProvider implements 
HyperlinkProviderExt {
                 for (ConfigurationMetadataSource source : sources) {
                     if (property == null || 
source.getProperties().get(property.getId()) == property) {
                         String typeName = source.getType();
-                        ElementHandle handle = getElementHandle(cpInfo, 
typeName, property != null ? property.getName() : null, cancel);
+                        ElementHandle handle = 
MicronautConfigUtilities.getElementHandle(cpInfo, typeName, property != null ? 
property.getName() : null, cancel);
                         if (handle != null) {
                             CompletableFuture<ElementOpen.Location> future = 
ElementOpen.getLocation(cpInfo, handle, typeName.replace('.', '/') + ".class");
                             return future.thenApply(location -> {
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/hyperlink/MicronautExpressionHyperlinkProvider.java
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/hyperlink/MicronautExpressionHyperlinkProvider.java
new file mode 100644
index 0000000000..33d95947f5
--- /dev/null
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/hyperlink/MicronautExpressionHyperlinkProvider.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.micronaut.hyperlink;
+
+import java.awt.Toolkit;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import javax.lang.model.element.TypeElement;
+import javax.swing.text.Document;
+import org.netbeans.api.editor.document.EditorDocumentUtils;
+import org.netbeans.api.editor.mimelookup.MimeRegistration;
+import org.netbeans.api.java.lexer.JavaTokenId;
+import org.netbeans.api.java.source.ClasspathInfo;
+import org.netbeans.api.java.source.ElementHandle;
+import org.netbeans.api.java.source.SourceUtils;
+import org.netbeans.api.java.source.ui.ElementOpen;
+import org.netbeans.api.lexer.Language;
+import org.netbeans.api.lexer.TokenHierarchy;
+import org.netbeans.api.lexer.TokenSequence;
+import org.netbeans.api.lsp.HyperlinkLocation;
+import org.netbeans.api.progress.BaseProgressUtils;
+import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt;
+import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType;
+import org.netbeans.modules.micronaut.MicronautConfigUtilities;
+import org.netbeans.modules.micronaut.completion.MicronautConfigDocumentation;
+import 
org.netbeans.modules.micronaut.expression.MicronautExpressionLanguageParser;
+import 
org.netbeans.modules.micronaut.expression.MicronautExpressionLanguageUtilities;
+import org.netbeans.spi.lsp.HyperlinkLocationProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@MimeRegistration(mimeType = "text/x-java", service = 
HyperlinkProviderExt.class, position = 1220)
+public class MicronautExpressionHyperlinkProvider implements 
HyperlinkProviderExt {
+
+    @Override
+    public Set<HyperlinkType> getSupportedHyperlinkTypes() {
+        return EnumSet.of(HyperlinkType.GO_TO_DECLARATION);
+    }
+
+    @Override
+    public boolean isHyperlinkPoint(Document doc, int offset, HyperlinkType 
type) {
+        return getHyperlinkSpan(doc, offset, type) != null;
+    }
+
+    @Override
+    public int[] getHyperlinkSpan(Document doc, int offset, HyperlinkType 
type) {
+        AtomicReference<int[]> ret = new AtomicReference<>();
+        if (EditorDocumentUtils.getFileObject(doc) != null) {
+            doc.render(() -> {
+                TokenSequence<JavaTokenId> javaTS = 
SourceUtils.getJavaTokenSequence(TokenHierarchy.get(doc), offset);
+                if (javaTS != null && javaTS.moveNext() && javaTS.token().id() 
== JavaTokenId.STRING_LITERAL) {
+                    int off = offset - javaTS.offset();
+                    Matcher matcher = 
MicronautExpressionLanguageParser.MEXP_PATTERN.matcher(javaTS.token().text());
+                    while (matcher.find() && matcher.groupCount() == 1) {
+                        if (off >= matcher.start(1) && off <= matcher.end(1)) {
+                            TokenHierarchy<String> th = 
TokenHierarchy.create(matcher.group(1), Language.find("text/x-micronaut-el"));
+                            TokenSequence<?> ts = th != null ? 
th.tokenSequence() : null;
+                            if (ts != null) {
+                                int d = ts.move(off - matcher.start(1));
+                                if (d == 0 ? ts.movePrevious() : 
ts.moveNext()) {
+                                    List<String> categories = (List<String>) 
ts.token().getProperty("categories");
+                                    if (categories != null && 
categories.size() > 1) {
+                                        String category = 
categories.get(categories.size() - 1);
+                                        switch (category) {
+                                            case "string.quoted.single.mexp":
+                                                if (categories.size() <= 2 || 
!"meta.environment-access.argument.mexp".equals(categories.get(categories.size()
 - 2))) {
+                                                    break;
+                                                }
+                                            case "entity.name.function.mexp":
+                                            case 
"variable.other.object.property.mexp":
+                                            case "storage.type.java":
+                                                ret.set(new int [] 
{ts.offset() + matcher.start(1) + javaTS.offset(), ts.offset() + 
ts.token().length() + matcher.start(1) + javaTS.offset()});
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            });
+        }
+        return ret.get();
+    }
+
+    @Override
+    public void performClickAction(Document doc, int offset, HyperlinkType 
type) {
+        AtomicBoolean cancel = new AtomicBoolean();
+        BaseProgressUtils.runOffEventDispatchThread(() -> {
+            MicronautExpressionLanguageUtilities.resolve(doc, offset, (info, 
element) -> {
+                return ElementOpen.open(info.getClasspathInfo(), element);
+            }, (property, source) -> {
+                ClasspathInfo cpInfo = ClasspathInfo.create(doc);
+                ElementHandle handle = source != null ? 
MicronautConfigUtilities.getElementHandle(cpInfo, source.getType(), 
property.getName(), cancel) : null;
+                if (handle == null || !ElementOpen.open(cpInfo, handle)) {
+                    Toolkit.getDefaultToolkit().beep();
+                }
+                return null;
+            });
+        }, Bundle.LBL_GoToDeclaration(), cancel, false);
+    }
+
+    @Override
+    public String getTooltipText(Document doc, int offset, HyperlinkType type) 
{
+        return MicronautExpressionLanguageUtilities.resolve(doc, offset, 
(info, element) -> {
+            return "<html><body>" + 
MicronautExpressionLanguageUtilities.getJavadocText(info, element, true, 1);
+        }, (property, source) -> {
+            return "<html><body>" + new 
MicronautConfigDocumentation(property).getText();
+        });
+    }
+
+    @MimeRegistration(mimeType = "text/x-java", service = 
HyperlinkLocationProvider.class)
+    public static class LocationProvider implements HyperlinkLocationProvider {
+
+        @Override
+        public CompletableFuture<HyperlinkLocation> 
getHyperlinkLocation(Document doc, int offset) {
+            CompletableFuture<ElementOpen.Location> future = 
MicronautExpressionLanguageUtilities.resolve(doc, offset, (info, element) -> {
+                TypeElement typeElement = 
info.getElementUtilities().outermostTypeElement(element);
+                if (typeElement != null) {
+                    return ElementOpen.getLocation(info.getClasspathInfo(), 
ElementHandle.create(element), 
typeElement.getQualifiedName().toString().replace('.', '/') + ".class");
+                }
+                return null;
+            }, (property, source) -> {
+                if (source != null) {
+                    ClasspathInfo cpInfo = ClasspathInfo.create(doc);
+                    String typeName = source.getType();
+                    ElementHandle handle = 
MicronautConfigUtilities.getElementHandle(cpInfo, typeName, property.getName(), 
null);
+                    if (handle != null) {
+                        return ElementOpen.getLocation(cpInfo, handle, 
typeName.replace('.', '/') + ".class");
+                    }
+                }
+                return null;
+            });
+            return future != null ? future.thenApply(location -> {
+                return 
HyperlinkLocationProvider.createHyperlinkLocation(location.getFileObject(), 
location.getStartOffset(), location.getEndOffset());
+            }) : CompletableFuture.completedFuture(null);
+        }
+    }
+}
diff --git 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/resources/mexp.tmLanguage.json
 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/resources/mexp.tmLanguage.json
index 34238298d2..a1a9fde79e 100644
--- 
a/enterprise/micronaut/src/org/netbeans/modules/micronaut/resources/mexp.tmLanguage.json
+++ 
b/enterprise/micronaut/src/org/netbeans/modules/micronaut/resources/mexp.tmLanguage.json
@@ -180,14 +180,14 @@
                                        "name": 
"punctuation.bracket.square.beging.mexp"
                                }
                        },
-                       "contentName": "meta.bean-context-access.argument.mexp",
+                       "contentName": "meta.environment-access.argument.mexp",
                        "end": "\\]",
                        "endCaptures": {
                                "0": {
                                        "name": 
"punctuation.bracket.square.end.mexp"
                                }
                        },
-                       "name": "meta.bean-context-access.mexp",
+                       "name": "meta.environment-access.mexp",
                        "patterns": [
                                { "include": "#code" }
                        ]


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org
For additional commands, e-mail: commits-h...@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to