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