This is an automated email from the ASF dual-hosted git repository. sdedic 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 96d18cd Completion items imported from Java delegate to ElementJavadoc to get the content. new a15d4b6 Merge pull request #2984 from sdedic/groovy/completion-javadoc 96d18cd is described below commit 96d18cdc3a1e9e9a2edb7db1df931a59de56c226 Author: Svata Dedic <svatopluk.de...@oracle.com> AuthorDate: Tue Jun 1 16:38:13 2021 +0200 Completion items imported from Java delegate to ElementJavadoc to get the content. --- groovy/groovy.editor/nbproject/project.xml | 2 +- .../editor/api/completion/CompletionHandler.java | 53 +++- .../editor/api/completion/CompletionItem.java | 113 +++++++- .../groovy/editor/completion/MethodCompletion.java | 25 +- .../groovy/editor/completion/TypesCompletion.java | 27 +- .../completion/provider/CompletionAccessor.java | 64 +++++ .../provider/GroovyElementsProvider.java | 35 ++- .../completion/provider/JavaElementHandler.java | 59 +++- .../groovy/editor/java/JavaElementHandle.java | 304 +++++++++++++++++++++ 9 files changed, 632 insertions(+), 50 deletions(-) diff --git a/groovy/groovy.editor/nbproject/project.xml b/groovy/groovy.editor/nbproject/project.xml index 0cdabe9..d971f65 100644 --- a/groovy/groovy.editor/nbproject/project.xml +++ b/groovy/groovy.editor/nbproject/project.xml @@ -92,7 +92,7 @@ <compile-dependency/> <run-dependency> <release-version>2</release-version> - <specification-version>2.21</specification-version> + <specification-version>2.43</specification-version> </run-dependency> </dependency> <dependency> diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CompletionHandler.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CompletionHandler.java index 79812ed..30c574f 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CompletionHandler.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CompletionHandler.java @@ -22,12 +22,18 @@ import groovy.lang.MetaMethod; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; +import javax.lang.model.element.Element; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import org.codehaus.groovy.ast.ASTNode; @@ -36,6 +42,8 @@ import org.codehaus.groovy.ast.expr.ArgumentListExpression; import org.codehaus.groovy.reflection.CachedClass; import org.netbeans.api.java.platform.JavaPlatform; import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.java.source.ui.ElementJavadoc; import org.netbeans.api.lexer.Token; import org.netbeans.api.lexer.TokenSequence; import org.netbeans.editor.BaseDocument; @@ -53,12 +61,14 @@ import org.netbeans.modules.groovy.editor.api.lexer.GroovyTokenId; import org.netbeans.modules.groovy.editor.api.lexer.LexUtilities; import org.netbeans.modules.groovy.editor.utils.GroovyUtils; import org.netbeans.modules.groovy.editor.api.completion.util.CompletionContext; +import org.netbeans.modules.groovy.editor.java.JavaElementHandle; import org.netbeans.modules.groovy.support.api.GroovySettings; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.WeakListeners; -public class CompletionHandler implements CodeCompletionHandler { +public class CompletionHandler implements CodeCompletionHandler2 { private static final Logger LOG = Logger.getLogger(CompletionHandler.class.getName()); private final PropertyChangeListener docListener; @@ -433,7 +443,7 @@ public class CompletionHandler implements CodeCompletionHandler { String error = NbBundle.getMessage(CompletionHandler.class, "GroovyCompletion_NoJavaDocFound"); String doctext = null; - + if (element instanceof ASTMethod) { ASTMethod ame = (ASTMethod) element; @@ -630,4 +640,43 @@ public class CompletionHandler implements CodeCompletionHandler { } return ParameterInfo.NONE; } + + @Override + public Documentation documentElement(ParserResult info, ElementHandle handle, Callable<Boolean> cancel) { + if (handle instanceof JavaElementHandle) { + // let Java support do the hard work. + ElementJavadoc jdoc; + try { + jdoc = ((JavaElementHandle)handle).extract(info, new JavaElementHandle.ElementFunction<ElementJavadoc>() { + @Override + public ElementJavadoc apply(CompilationInfo info, Element el) { + return ElementJavadoc.create(info, el); + } + }); + } catch (IOException ex) { + // TBR + return null; + } + + if (jdoc != null) { + Boolean b; + Future<String> content = jdoc.getTextAsync(); + try { + while (((b = cancel.call()) == null) || !b.booleanValue()) { + try { + return Documentation.create(content.get(250, TimeUnit.MILLISECONDS), + jdoc.getURL()); + } catch (TimeoutException te) {} + } + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + return null; + } + return null; + } + } + + String s = document(info, handle); + return s == null ? null : Documentation.create(s); + } } diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CompletionItem.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CompletionItem.java index e04b1f3..81b955f 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CompletionItem.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CompletionItem.java @@ -42,6 +42,8 @@ import org.netbeans.modules.groovy.editor.api.elements.ElementHandleSupport; import org.netbeans.modules.groovy.editor.api.elements.GroovyElement; import org.netbeans.modules.groovy.editor.api.elements.KeywordElement; import org.netbeans.modules.groovy.editor.api.elements.common.MethodElement.MethodParameter; +import org.netbeans.modules.groovy.editor.completion.provider.CompletionAccessor; +import org.netbeans.modules.groovy.editor.java.JavaElementHandle; import org.netbeans.modules.groovy.editor.java.Utilities; import org.netbeans.modules.groovy.editor.utils.GroovyUtils; import org.netbeans.modules.groovy.support.api.GroovySources; @@ -61,8 +63,47 @@ public abstract class CompletionItem extends DefaultCompletionProposal { private static volatile ImageIcon groovyIcon; private static volatile ImageIcon javaIcon; private static volatile ImageIcon newConstructorIcon; - + static { + CompletionAccessor.setInstance(new CompletionAccessor() { + @Override + public CompletionItem assignHandle(CompletionItem item, JavaElementHandle jh) { + synchronized (item) { + if (item instanceof ConstructorItem) { + ((ConstructorItem)item).assignHandle(jh); + } else if (item instanceof JavaFieldItem) { + ((JavaFieldItem)item).assignHandle(jh); + } else if (item instanceof JavaMethodItem) { + ((JavaMethodItem)item).assignHandle(jh); + } else if (item instanceof TypeItem) { + ((TypeItem)item).assignHandle(jh); + } else { + throw new IllegalArgumentException(item.getClass() + ":" + item.toString()); + } + } + return item; + } + + @Override + public ConstructorItem createConstructor(JavaElementHandle h, List<MethodParameter> parameters, int anchorOffset, boolean expand) { + ConstructorItem ci = new ConstructorItem(h.getIn(), h.getName(), parameters, anchorOffset, expand); + synchronized (ci) { + ci.handle = h; + } + return ci; + } + + @Override + public TypeItem createType(JavaElementHandle h, String qn, String n, int anchorOffset, javax.lang.model.element.ElementKind ek) { + TypeItem ti = new TypeItem(qn, n, anchorOffset, ek); + synchronized (ti) { + ti.handle = h; + } + return ti; + } + }); + } + private CompletionItem(GroovyElement element, int anchorOffset) { this.element = element; this.anchorOffset = anchorOffset; @@ -145,7 +186,7 @@ public abstract class CompletionItem extends DefaultCompletionProposal { public static CompletionItem forDynamicField(int anchorOffset, String name, String type) { return new DynamicFieldItem(anchorOffset, name, type); } - + private static class JavaMethodItem extends CompletionItem { private final String className; @@ -155,7 +196,8 @@ public abstract class CompletionItem extends DefaultCompletionProposal { private final Set<javax.lang.model.element.Modifier> modifiers; private final boolean emphasise; private final boolean nameOnly; - + private ElementHandle handle; + public JavaMethodItem(String className, String simpleName, List<String> parameters, TypeMirror returnType, Set<javax.lang.model.element.Modifier> modifiers, int anchorOffset, boolean emphasise, boolean nameOnly) { @@ -233,7 +275,12 @@ public abstract class CompletionItem extends DefaultCompletionProposal { @Override public ElementHandle getElement() { - return ElementHandleSupport.createHandle(className, simpleName, ElementKind.METHOD, getModifiers()); + synchronized (this) { + if (handle == null) { + handle = ElementHandleSupport.createHandle(className, simpleName, ElementKind.METHOD, getModifiers()); + } + return handle; + } } @Override @@ -244,6 +291,9 @@ public abstract class CompletionItem extends DefaultCompletionProposal { return super.getCustomInsertTemplate(); } + public void assignHandle(ElementHandle h) { + this.handle = h; + } } public static class DynamicFieldItem extends CompletionItem { @@ -637,6 +687,7 @@ public abstract class CompletionItem extends DefaultCompletionProposal { private final String fqn; private final String name; private final javax.lang.model.element.ElementKind ek; + private ElementHandle handle; public TypeItem(String fqn, String name, int anchorOffset, javax.lang.model.element.ElementKind ek) { super(null, anchorOffset); @@ -668,32 +719,49 @@ public abstract class CompletionItem extends DefaultCompletionProposal { public Set<Modifier> getModifiers() { return Collections.emptySet(); } + + void assignHandle(ElementHandle h) { + this.handle = h; + } @Override public ElementHandle getElement() { - // For completion documentation - // return ElementHandleSupport.createHandle(request.info, new ClassElement(name)); - return null; + synchronized (this) { + if (handle == null) { + handle = ElementHandleSupport.createHandle(fqn, name, elementKind, Collections.emptySet()); + } + return handle; + } } } - + public static class ConstructorItem extends CompletionItem { private static final String NEW_CSTR = "org/netbeans/modules/groovy/editor/resources/new_constructor_16.png"; //NOI18N private final boolean expand; // should this item expand to a constructor body? private final String name; + private final String className; private final String paramListString; private final List<MethodParameter> parameters; + private ElementHandle handle; public ConstructorItem(String name, List<MethodParameter> parameters, int anchorOffset, boolean expand) { + this(null, name, parameters, anchorOffset, expand); + } + + /** + * Package private; use {@link CompletionAccessor} to make instances of this. + */ + ConstructorItem(String clazzName, String name, List<MethodParameter> parameters, int anchorOffset, boolean expand) { super(null, anchorOffset); + this.className = clazzName; this.name = name; this.expand = expand; this.parameters = parameters; this.paramListString = parseParams(); } - + private String parseParams() { StringBuilder sb = new StringBuilder(); if (!parameters.isEmpty()) { @@ -746,9 +814,16 @@ public abstract class CompletionItem extends DefaultCompletionProposal { @Override public ElementHandle getElement() { - // For completion documentation - // return ElementHandleSupport.createHandle(request.info, new ClassElement(name)); - return null; + synchronized (this) { + if (handle == null) { + handle = ElementHandleSupport.createHandle(className, name, ElementKind.CONSTRUCTOR, getModifiers()); + } + return handle; + } + } + + void assignHandle(ElementHandle h) { + this.handle = handle; } // Constructors are smart by definition (have to be place above others) @@ -840,7 +915,7 @@ public abstract class CompletionItem extends DefaultCompletionProposal { return hash; } } - + public static class NamedParameter extends CompletionItem { private final String typeName; @@ -917,6 +992,7 @@ public abstract class CompletionItem extends DefaultCompletionProposal { private final TypeMirror type; private final Set<javax.lang.model.element.Modifier> modifiers; private final boolean emphasise; + private ElementHandle handle; public JavaFieldItem(String className, String name, TypeMirror type, @@ -961,10 +1037,19 @@ public abstract class CompletionItem extends DefaultCompletionProposal { public Set<Modifier> getModifiers() { return Utilities.modelModifiersToGsf(modifiers); } + + void assignHandle(ElementHandle h) { + this.handle = h; + } @Override public ElementHandle getElement() { - return ElementHandleSupport.createHandle(className, name, ElementKind.FIELD, getModifiers()); + synchronized (this) { + if (handle == null) { + handle = ElementHandleSupport.createHandle(className, name, ElementKind.FIELD, getModifiers()); + } + return handle; + } } } diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/MethodCompletion.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/MethodCompletion.java index b95cfe2..71a5274 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/MethodCompletion.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/MethodCompletion.java @@ -31,6 +31,7 @@ import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.source.ClasspathInfo; import org.netbeans.api.java.source.CompilationController; +import org.netbeans.api.java.source.ElementHandle; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.Task; import org.netbeans.modules.csl.api.CompletionProposal; @@ -45,10 +46,12 @@ import org.netbeans.modules.groovy.editor.utils.GroovyUtils; import org.netbeans.modules.groovy.editor.api.completion.util.CompletionContext; import org.netbeans.modules.groovy.editor.api.elements.common.MethodElement.MethodParameter; import org.netbeans.modules.groovy.editor.api.elements.index.IndexedClass; -import org.netbeans.modules.groovy.editor.api.elements.index.IndexedField; import org.netbeans.modules.groovy.editor.api.elements.index.IndexedMethod; +import org.netbeans.modules.groovy.editor.completion.provider.CompletionAccessor; import org.netbeans.modules.groovy.editor.imports.ImportUtils; +import org.netbeans.modules.groovy.editor.java.JavaElementHandle; import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport; +import org.openide.filesystems.FileObject; /** * Complete the methods invokable on a class. @@ -162,11 +165,11 @@ public class MethodCompletion extends BaseCompletion { typelist.addAll(getElementListFor(info.getElements(), importName)); } LOG.log(Level.FINEST, "Number of types found: {0}", typelist.size()); - + if (exactConstructorExists(typelist, context.getPrefix())) { // if we are in situation like "String s = new String|" we want to // show only String constructors (not StringBuffer constructors etc.) - addExactProposals(typelist); + addExactProposals(context.getSourceFile(), typelist); } addConstructorProposalsForDeclaredClasses(); } @@ -280,10 +283,11 @@ public class MethodCompletion extends BaseCompletion { return false; } - private void addExactProposals(List<? extends Element> typelist) { + private void addExactProposals(FileObject source, List<? extends Element> typelist) { for (Element element : typelist) { // only look for classes rather than enums or interfaces if (element.getKind() == ElementKind.CLASS) { + TypeElement tel = (TypeElement)element; for (Element encl : element.getEnclosedElements()) { if (encl.getKind() == ElementKind.CONSTRUCTOR) { // we gotta get the constructors name from the type itself, since @@ -291,7 +295,7 @@ public class MethodCompletion extends BaseCompletion { String constructorName = element.getSimpleName().toString(); if (constructorName.toUpperCase().equals(context.getPrefix().toUpperCase())) { - addConstructorProposal(constructorName, (ExecutableElement) encl); + addConstructorProposal(tel.getQualifiedName().toString(), (ExecutableElement) encl); } } } @@ -299,10 +303,15 @@ public class MethodCompletion extends BaseCompletion { } } - private void addConstructorProposal(String constructorName, ExecutableElement encl) { + private void addConstructorProposal(String classFqn, ExecutableElement encl) { List<MethodParameter> paramList = getParameterList(encl); - - ConstructorItem constructor = new ConstructorItem(constructorName, paramList, anchor, false); + List<String> sig = new ArrayList<>(paramList.size()); + for (MethodParameter p : paramList) { + sig.add(p.getFqnType()); + } + JavaElementHandle h = new JavaElementHandle(encl.getEnclosingElement().getSimpleName().toString(), classFqn, ElementHandle.create(encl), sig, Collections.emptySet()); + CompletionItem constructor = CompletionAccessor.instance(). + createConstructor(h, paramList, anchor, false); if (!proposals.contains(constructor)) { proposals.add(constructor); } diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/TypesCompletion.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/TypesCompletion.java index f8568e9..acc9f98 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/TypesCompletion.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/TypesCompletion.java @@ -47,7 +47,9 @@ import org.netbeans.modules.groovy.editor.api.elements.index.IndexedClass; import org.netbeans.modules.groovy.editor.api.lexer.GroovyTokenId; import org.netbeans.modules.groovy.editor.utils.GroovyUtils; import org.netbeans.modules.groovy.editor.api.completion.util.CompletionContext; +import org.netbeans.modules.groovy.editor.completion.provider.CompletionAccessor; import org.netbeans.modules.groovy.editor.imports.ImportUtils; +import org.netbeans.modules.groovy.editor.java.JavaElementHandle; import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport; import org.openide.filesystems.FileObject; @@ -313,14 +315,18 @@ public class TypesCompletion extends BaseCompletion { } // We are dealing with prefix for some class type + JavaElementHandle jh = null; + if (type.getHandle() != null) { + jh = new JavaElementHandle(fqnTypeName, typeName, type.getHandle(), Collections.emptyList(), Collections.emptySet()); + } if (isPrefixed(request, typeName)) { alreadyPresent.add(type); - proposals.add(new CompletionItem.TypeItem(fqnTypeName, typeName, anchor, type.getKind())); + proposals.add(CompletionAccessor.instance().createType(jh, fqnTypeName, typeName, anchor, type.getKind())); } // We are dealing with CamelCase completion for some class type if (CamelCaseUtil.compareCamelCase(typeName, request.getPrefix())) { - CompletionItem.TypeItem camelCaseProposal = new CompletionItem.TypeItem(fqnTypeName, typeName, anchor, ElementKind.CLASS); + CompletionItem.TypeItem camelCaseProposal = CompletionAccessor.instance().createType(jh, fqnTypeName, typeName, anchor, ElementKind.CLASS); if (!proposals.contains(camelCaseProposal)) { proposals.add(camelCaseProposal); @@ -357,7 +363,7 @@ public class TypesCompletion extends BaseCompletion { || samePackage && (modifiers.contains(Modifier.PROTECTED) || (!modifiers.contains(Modifier.PUBLIC) && !modifiers.contains(Modifier.PRIVATE)))) { - result.add(new TypeHolder(element.toString(), element.getKind())); + result.add(new TypeHolder(element.toString(), org.netbeans.api.java.source.ElementHandle.create(element))); } } } @@ -374,7 +380,7 @@ public class TypesCompletion extends BaseCompletion { || samePackage && (modifiers.contains(Modifier.PROTECTED) || (!modifiers.contains(Modifier.PUBLIC) && !modifiers.contains(Modifier.PRIVATE)))) { - result.add(new TypeHolder(element.toString(), element.getKind())); + result.add(new TypeHolder(element.toString(), org.netbeans.api.java.source.ElementHandle.create(element))); } } } @@ -392,6 +398,7 @@ public class TypesCompletion extends BaseCompletion { private final String name; private final ElementKind kind; + private final org.netbeans.api.java.source.ElementHandle handle; public TypeHolder(IndexedClass indexedClass) { this.name = indexedClass.getFqn(); @@ -401,11 +408,23 @@ public class TypesCompletion extends BaseCompletion { } else { this.kind = ElementKind.INTERFACE; } + this.handle = null; } public TypeHolder(String name, ElementKind kind) { this.name = name; this.kind = kind; + this.handle = null; + } + + public TypeHolder(String name, org.netbeans.api.java.source.ElementHandle handle) { + this.name = name; + this.handle = handle; + this.kind = handle.getKind(); + } + + public org.netbeans.api.java.source.ElementHandle getHandle() { + return handle; } public ElementKind getKind() { diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/CompletionAccessor.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/CompletionAccessor.java new file mode 100644 index 0000000..1e79a57 --- /dev/null +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/CompletionAccessor.java @@ -0,0 +1,64 @@ +/* + * 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.groovy.editor.completion.provider; + +import java.util.List; +import javax.lang.model.element.ElementKind; +import org.netbeans.modules.groovy.editor.api.completion.CompletionItem; +import org.netbeans.modules.groovy.editor.api.completion.CompletionItem.ConstructorItem; +import org.netbeans.modules.groovy.editor.api.completion.CompletionItem.TypeItem; +import org.netbeans.modules.groovy.editor.api.elements.common.MethodElement; +import org.netbeans.modules.groovy.editor.java.JavaElementHandle; + +/** + * Avoids publishing additional API methods; the API needs a thorough review, I am not + * capable of at the moment. + * + * @author sdedic + */ +public abstract class CompletionAccessor { + private static CompletionAccessor INSTANCE; + + static { + new TypeItem("", "", 0, ElementKind.CLASS); + } + + public static void setInstance(CompletionAccessor acc) { + synchronized (CompletionAccessor.class) { + if (INSTANCE != null && INSTANCE != acc) { + throw new IllegalStateException(); + } + INSTANCE = acc; + } + } + + public static CompletionAccessor instance() { + return INSTANCE; + } + + /** + * Assigns a Handle to a java element item. The passed item must be an instance of JavaElementItem + * Created by forMethod() or forField() factories. + */ + public abstract CompletionItem assignHandle(CompletionItem item, JavaElementHandle jh); + + public abstract ConstructorItem createConstructor(JavaElementHandle h, List<MethodElement.MethodParameter> parameters, int anchorOffset, boolean expand); + + public abstract TypeItem createType(JavaElementHandle h, String qn, String n, int anchorOffset, javax.lang.model.element.ElementKind ek); +} diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/GroovyElementsProvider.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/GroovyElementsProvider.java index 1853c5d..b7af058 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/GroovyElementsProvider.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/GroovyElementsProvider.java @@ -19,12 +19,14 @@ package org.netbeans.modules.groovy.editor.completion.provider; +import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.lang.model.element.Modifier; import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.modules.csl.api.ElementKind; import org.netbeans.modules.groovy.editor.api.GroovyIndex; import org.netbeans.modules.groovy.editor.api.completion.CompletionItem; import org.netbeans.modules.groovy.editor.api.completion.FieldSignature; @@ -35,9 +37,11 @@ import org.netbeans.modules.groovy.editor.api.elements.index.IndexedMethod; import org.netbeans.modules.groovy.editor.completion.AccessLevel; import org.netbeans.modules.groovy.editor.java.Utilities; import org.netbeans.modules.groovy.editor.api.completion.util.CompletionContext; +import org.netbeans.modules.groovy.editor.java.JavaElementHandle; import org.netbeans.modules.groovy.editor.spi.completion.CompletionProvider; import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport; import org.openide.filesystems.FileObject; +import org.openide.filesystems.URLMapper; /** * @@ -62,15 +66,28 @@ public final class GroovyElementsProvider implements CompletionProvider { for (IndexedMethod indexedMethod : methods) { if (accept(context.access, indexedMethod)) { - result.put(getMethodSignature(indexedMethod), CompletionItem.forJavaMethod( - context.getTypeName(), - indexedMethod.getName(), - indexedMethod.getParameterTypes(), - indexedMethod.getReturnType(), - Utilities.gsfModifiersToModel(indexedMethod.getModifiers(), Modifier.PUBLIC), - context.getAnchor(), - false, - context.isNameOnly())); + JavaElementHandle jeh = null; + if (indexedMethod.getFileObject().getMIMEType("text/x-java") != null) { + URL u = URLMapper.findURL(indexedMethod.getFileObject(), URLMapper.INTERNAL); + jeh = new JavaElementHandle(u, + indexedMethod.getName(), context.getTypeName(), ElementKind.METHOD, + indexedMethod.getParameterTypes(), + indexedMethod.getModifiers()); + } + + CompletionItem ci = CompletionItem.forJavaMethod( + context.getTypeName(), + indexedMethod.getName(), + indexedMethod.getParameterTypes(), + indexedMethod.getReturnType(), + Utilities.gsfModifiersToModel(indexedMethod.getModifiers(), Modifier.PUBLIC), + context.getAnchor(), + false, + context.isNameOnly()); + + result.put(getMethodSignature(indexedMethod), + CompletionAccessor.instance().assignHandle(ci, jeh) + ); } } } diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/JavaElementHandler.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/JavaElementHandler.java index 4d70d13..4979ef9 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/JavaElementHandler.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/JavaElementHandler.java @@ -44,15 +44,19 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import org.netbeans.api.java.source.ClasspathInfo; import org.netbeans.api.java.source.CompilationController; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.java.source.ElementHandle; import org.netbeans.api.java.source.ElementUtilities.ElementAcceptor; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.Task; +import org.netbeans.api.java.source.TypeUtilities; import org.netbeans.modules.csl.spi.ParserResult; import org.netbeans.modules.groovy.editor.utils.GroovyUtils; -import org.netbeans.modules.groovy.editor.api.completion.CompletionHandler; import org.netbeans.modules.groovy.editor.api.completion.FieldSignature; import org.netbeans.modules.groovy.editor.api.completion.MethodSignature; import org.netbeans.modules.groovy.editor.completion.AccessLevel; +import org.netbeans.modules.groovy.editor.java.JavaElementHandle; +import org.netbeans.modules.groovy.editor.java.Utilities; import org.openide.filesystems.FileObject; /** @@ -78,16 +82,18 @@ public final class JavaElementHandler { public Map<MethodSignature, CompletionItem> getMethods(String className, String prefix, int anchor, String[] typeParameters, boolean emphasise, Set<AccessLevel> levels, boolean nameOnly) { JavaSource javaSource = createJavaSource(); - + if (javaSource == null) { return Collections.emptyMap(); } + + FileObject f = info.getSnapshot().getSource().getFileObject(); CountDownLatch cnt = new CountDownLatch(1); Map<MethodSignature, CompletionItem> result = Collections.synchronizedMap(new HashMap<MethodSignature, CompletionItem>()); try { - javaSource.runUserActionTask(new MethodCompletionHelper(cnt, javaSource, className, typeParameters, + javaSource.runUserActionTask(new MethodCompletionHelper(cnt, javaSource, f, className, typeParameters, levels, prefix, anchor, result, emphasise, nameOnly), true); } catch (IOException ex) { LOG.log(Level.FINEST, "Problem in runUserActionTask : {0}", ex.getMessage()); @@ -114,8 +120,9 @@ public final class JavaElementHandler { CountDownLatch cnt = new CountDownLatch(1); Map<FieldSignature, CompletionItem> result = Collections.synchronizedMap(new HashMap<FieldSignature, CompletionItem>()); + FileObject f = info.getSnapshot().getSource().getFileObject(); try { - javaSource.runUserActionTask(new FieldCompletionHelper(cnt, javaSource, className, + javaSource.runUserActionTask(new FieldCompletionHelper(cnt, javaSource, f, className, Collections.singleton(AccessLevel.PUBLIC), prefix, anchor, result, emphasise), true); } catch (IOException ex) { LOG.log(Level.FINEST, "Problem in runUserActionTask : {0}", ex.getMessage()); @@ -154,6 +161,8 @@ public final class JavaElementHandler { private final JavaSource javaSource; + private final FileObject groovySource; + private final String className; private final String[] typeParameters; @@ -170,12 +179,13 @@ public final class JavaElementHandler { private final boolean nameOnly; - public MethodCompletionHelper(CountDownLatch cnt, JavaSource javaSource, String className, + public MethodCompletionHelper(CountDownLatch cnt, JavaSource javaSource, FileObject groovySource, String className, String[] typeParameters, Set<AccessLevel> levels, String prefix, int anchor, Map<MethodSignature, CompletionItem> proposals, boolean emphasise, boolean nameOnly) { this.cnt = cnt; this.javaSource = javaSource; + this.groovySource = groovySource; this.className = className; this.typeParameters = typeParameters; this.levels = levels; @@ -219,8 +229,15 @@ public final class JavaElementHandler { if (simpleName.toUpperCase(Locale.ENGLISH).startsWith(prefix.toUpperCase(Locale.ENGLISH)) && !simpleName.contains("$")) { - proposals.put(getSignature(te, element, typeParameters, info.getTypes()), CompletionItem.forJavaMethod( - className, simpleName, params, returnType, element.getModifiers(), anchor, emphasise, nameOnly)); + JavaElementHandle h = new JavaElementHandle( + simpleName, className, ElementHandle.create(element), + signatureOf(info, element), Utilities.modelModifiersToGsf(element.getModifiers())); + + CompletionItem ci = CompletionItem.forJavaMethod( + className, simpleName, params, returnType, element.getModifiers(), anchor, emphasise, nameOnly); + proposals.put(getSignature(te, element, typeParameters, info.getTypes()), + CompletionAccessor.instance().assignHandle(ci, h) + ); } } } @@ -228,6 +245,15 @@ public final class JavaElementHandler { cnt.countDown(); } + private List<String> signatureOf(CompilationInfo info, ExecutableElement exe) { + List<String> fqns = new ArrayList<>(exe.getParameters().size()); + for (VariableElement v : exe.getParameters()) { + fqns.add(info.getTypeUtilities().getTypeName(v.asType(), TypeUtilities.TypeNameOptions.PRINT_FQN). + toString()); + } + return fqns; + } + private List<String> getParameterListForMethod(ExecutableElement exe) { List<String> parameters = new ArrayList<String>(); @@ -312,13 +338,16 @@ public final class JavaElementHandler { private final boolean emphasise; private final Map<FieldSignature, CompletionItem> proposals; - - public FieldCompletionHelper(CountDownLatch cnt, JavaSource javaSource, String className, + + private final FileObject groovySource; + + public FieldCompletionHelper(CountDownLatch cnt, JavaSource javaSource, FileObject groovySource, String className, Set<AccessLevel> levels, String prefix, int anchor, Map<FieldSignature, CompletionItem> proposals, boolean emphasise) { this.cnt = cnt; this.javaSource = javaSource; + this.groovySource = groovySource; this.className = className; this.levels = levels; this.prefix = prefix; @@ -360,9 +389,15 @@ public final class JavaElementHandler { if (LOG.isLoggable(Level.FINEST)) { LOG.log(Level.FINEST, simpleName + " " + type.toString()); } - - proposals.put(getSignature(te, element), new CompletionItem.JavaFieldItem( - className, simpleName, type, element.getModifiers(), anchor, emphasise)); + + JavaElementHandle jh = new JavaElementHandle( + simpleName, className, ElementHandle.create(element), null, + Utilities.modelModifiersToGsf(element.getModifiers())); + + CompletionItem ci = new CompletionItem.JavaFieldItem( + className, simpleName, type, element.getModifiers(), anchor, emphasise); + proposals.put(getSignature(te, element), + CompletionAccessor.instance().assignHandle(ci, jh)); } } } diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/java/JavaElementHandle.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/java/JavaElementHandle.java new file mode 100644 index 0000000..8a765c9 --- /dev/null +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/java/JavaElementHandle.java @@ -0,0 +1,304 @@ +/* + * 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.groovy.editor.java; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.api.java.source.ClasspathInfo; +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.Task; +import org.netbeans.api.java.source.TypeUtilities; +import org.netbeans.modules.csl.api.ElementHandle; +import org.netbeans.modules.csl.api.ElementKind; +import org.netbeans.modules.csl.api.Modifier; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.spi.ParserResult; +import org.netbeans.modules.groovy.editor.api.elements.index.IndexedElement; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.URLMapper; + +/** + * Handle that represent an {@link IndexedElement}. But as the {@link IndexedElement} is stateful + * ant may keep the Document instance, this just uses the qualified name to identify the + * real element. + * <p> + * Some items are created from Groovy indexes, so there's no real Element to create ElementHandle from, + * so if an ElementHandle is available, the JavaElementHandle should be created on top of the real + * java source APIs ElementHandle. Otherwise, the coordinates are computed from URL, qualified parent, simple + * name kind and signature types. + * + * @author sdedic + */ +public final class JavaElementHandle implements ElementHandle { + /** + * URL of the file where the handle was created; used to construct classpaths etc. + */ + private final URL anchorURL; + + /** + * Name of the element + */ + private final String name; + + /** + * FQN of the outer/owner element. + */ + private final String ownerFQN; + + /** + * Kind of the element + */ + private final ElementKind kind; + + /** + * Signature info, for methods and ctors only + */ + private final List<String> signatureInfo; + + private final Set<Modifier> modifiers; + + private final org.netbeans.api.java.source.ElementHandle elementHandle; + + public JavaElementHandle(String name, String ownerFQN, org.netbeans.api.java.source.ElementHandle h, List<String> signatureInfo, Set<Modifier> modifiers) { + assert name != null : "simple name is needed"; + this.anchorURL = null; + this.name = name; + this.ownerFQN = ownerFQN; + this.elementHandle = h; + this.kind = fromJavaKind(h.getKind()); + this.signatureInfo = signatureInfo == null ? Collections.emptyList() : signatureInfo; + this.modifiers = modifiers == null ? Collections.emptySet() : modifiers; + } + + private static ElementKind fromJavaKind(javax.lang.model.element.ElementKind k) { + switch (k) { + case CLASS: return ElementKind.CLASS; + case INTERFACE: return ElementKind.INTERFACE; + case METHOD: return ElementKind.METHOD; + case FIELD: return ElementKind.FIELD; + case CONSTRUCTOR: return ElementKind.CONSTRUCTOR; + case ENUM: return ElementKind.CONSTANT; + case ANNOTATION_TYPE: return ElementKind.INTERFACE; + + default: + throw new IllegalArgumentException("Unsupported: " + k); + } + } + + public JavaElementHandle(URL anchorURL, String name, String ownerFQN, ElementKind kind, List<String> signatureInfo, Set<Modifier> modifiers) { + assert anchorURL != null : "Need an anchor to resolve the handle in the future"; + assert name != null : "simple name is needed"; + assert kind != null; + this.anchorURL = anchorURL; + this.name = name; + this.ownerFQN = ownerFQN; + this.kind = kind; + this.signatureInfo = signatureInfo == null ? Collections.emptyList() : signatureInfo; + this.modifiers = modifiers == null ? Collections.emptySet() : modifiers; + this.elementHandle = null; + } + + @Override + public FileObject getFileObject() { + return URLMapper.findFileObject(anchorURL); + } + + @Override + public String getMimeType() { + return "text/x-java"; // NOI18N + } + + @Override + public String getName() { + return name; + } + + @Override + public String getIn() { + return ownerFQN; + } + + @Override + public ElementKind getKind() { + return kind; + } + + @Override + public Set<Modifier> getModifiers() { + return modifiers; + } + + @Override + public boolean signatureEquals(ElementHandle handle) { + if (handle instanceof JavaElementHandle) { + JavaElementHandle jeh = (JavaElementHandle)handle; + if (jeh.elementHandle != null && elementHandle != null) { + return elementHandle.signatureEquals(jeh.elementHandle); + } else { + return Objects.equals(jeh.signatureInfo, signatureInfo); + } + } else { + return false; + } + } + + @Override + public OffsetRange getOffsetRange(ParserResult result) { + // for now, to be replaced by lazy-loaded range from resolved Element. + return OffsetRange.NONE; + } + + /** + * Resolves the handle for the passed CompilationInfo + * @param <T> element type + * @param info compilation info instance + * @return resolved element, or {@code null} if none. + */ + public <T extends Element> @CheckForNull T resolve(@NonNull CompilationInfo info) { + return (T)toElement(info); + } + + /** + * Extracts data from the element. + * @param <T> + * @param resolver + * @return + * @throws IOException + */ + public <T> T extract(ParserResult groovyResult, ElementFunction<T> resolver) throws IOException { + FileObject origin = groovyResult.getSnapshot().getSource().getFileObject(); + if (origin == null) { + if (anchorURL == null) { + return null; + } + origin = URLMapper.findFileObject(anchorURL); + } + ClasspathInfo cpi = ClasspathInfo.create(origin); + + class Processor implements Task<CompilationController>, ClasspathInfo.Provider { + T result; + Exception thrown; + + @Override + public void run(CompilationController parameter) throws Exception { + try { + result = resolver.apply(parameter, toElement(parameter)); + } catch (Exception ex) { + thrown = ex; + } + } + + @Override + public ClasspathInfo getClasspathInfo() { + return cpi; + } + } + + Processor inst = new Processor(); + + JavaSource.create(cpi).runUserActionTask(inst, true); + if (inst.thrown != null) { + throw new IOException(inst.thrown); + } else { + return inst.result; + } + } + + private Element toElement(CompilationInfo info) { + if (elementHandle != null) { + return elementHandle.resolve(info); + } + switch (kind) { + case CLASS: + return info.getElements().getTypeElement(name); + + case FIELD: + case CONSTANT: { + Element owner = info.getElements().getTypeElement(ownerFQN); + if (owner == null) { + return null; + } + + return ElementFilter.fieldsIn(owner.getEnclosedElements()).stream(). + filter(f -> f.getSimpleName().contentEquals(name)). + findAny().orElse(null); + } + + case METHOD: + case CONSTRUCTOR: { + Element owner = info.getElements().getTypeElement(ownerFQN); + if (owner == null || !(owner.getKind().isClass() || owner.getKind().isInterface())) { + return null; + } + for (ExecutableElement e : (kind == ElementKind.METHOD ? ElementFilter.methodsIn(owner.getEnclosedElements()) : ElementFilter.constructorsIn(owner.getEnclosedElements()))) { + if (kind == ElementKind.METHOD && !e.getSimpleName().contentEquals(name)) { + continue; + } + List<String> sigTypes = new ArrayList<>(); + for (VariableElement v : e.getParameters()) { + TypeMirror t = v.asType(); + sigTypes.add(info.getTypeUtilities().getTypeName(t, TypeUtilities.TypeNameOptions.PRINT_FQN).toString()); + } + if (sigTypes.equals(signatureInfo)) { + return e; + } + } + return null; + } + } + + return null; + } + + /** + * Processes the ElementHandle into the desired result. The processor's callback + * is called within parser's action task, so it can get a valid {@link CompilationInfo} + * and the resolved {@link Element} instance. + * <p> + * The implementation must not leak the {@code CompliationInfo} or process the Element instance + * outside of the {@link #apply} call. + * + * @param <T> + */ + @FunctionalInterface + public interface ElementFunction<T> { + /** + * Produces the desired result from the Element. + * @param info compilation info + * @param el resolved element; can be {@code null}, if resolution fails. + * @return info extracted from the element. + */ + public @CheckForNull T apply(@NonNull CompilationInfo info, @NullAllowed Element el); + } +} --------------------------------------------------------------------- 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