diff --git a/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/GroovyClassLoaderFactory.java b/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/GroovyClassLoaderFactory.java index 74500590e8..fa275fab3f 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/GroovyClassLoaderFactory.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/GroovyClassLoaderFactory.java @@ -22,6 +22,7 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.Map; @@ -35,6 +36,8 @@ import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.eclipse.GroovyLogManager; import org.codehaus.groovy.eclipse.TraceCategory; +import org.codehaus.groovy.runtime.m12n.ExtensionModuleScanner; +import org.codehaus.groovy.runtime.m12n.SimpleExtensionModule; import org.codehaus.jdt.groovy.internal.compiler.ast.GroovyParser; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; @@ -175,9 +178,9 @@ private static IProject findProject(String projectName) { private static void calculateClasspath(IJavaProject javaProject, Set classPaths, Set xformPaths) { try { - IRuntimeClasspathEntry[] dependencies = JavaRuntime.computeUnresolvedRuntimeClasspath(javaProject); // TODO: Leverage "excludeTestCode" parameter? http://www.eclipse.org/eclipse/news/4.8/M5/index.html#jdt-test-sources - Arrays.sort(dependencies, Comparator.comparing(IRuntimeClasspathEntry::getType)); - for (IRuntimeClasspathEntry unresolved : dependencies) { + IRuntimeClasspathEntry[] entries = JavaRuntime.computeUnresolvedRuntimeClasspath(javaProject); // TODO: Leverage "excludeTestCode" parameter? http://www.eclipse.org/eclipse/news/4.8/M5/index.html#jdt-test-sources + Arrays.sort(entries, Comparator.comparing(IRuntimeClasspathEntry::getType)); + for (IRuntimeClasspathEntry unresolved : entries) { Set paths = (unresolved.getType() == IRuntimeClasspathEntry.CONTAINER ? classPaths : xformPaths); for (IRuntimeClasspathEntry resolved : resolveRuntimeClasspathEntry(unresolved, javaProject)) { paths.add(getAbsoluteLocation(resolved)); @@ -238,11 +241,9 @@ private static URLClassLoader newClassLoader(Set classpath, ClassLoader //-------------------------------------------------------------------------- + @SuppressWarnings("rawtypes") public static class GrapeAwareGroovyClassLoader extends GroovyClassLoader { - /** {@code true} if any grabbing is done */ - public boolean grabbed; - public GrapeAwareGroovyClassLoader(ClassLoader parent, CompilerConfiguration config) { super(parent, config); } @@ -252,5 +253,47 @@ public void addURL(URL url) { this.grabbed = true; super.addURL(url); } + + /** {@code true} if any grabbing is done */ + public boolean grabbed; + + private volatile Set defaultCategories; + private volatile Set defaultStaticCategories; + + public Set getDefaultCategories() { + if (defaultCategories == null) { + synchronized (this) { + if (defaultCategories == null) { + defaultCategories = new LinkedHashSet<>(); defaultStaticCategories = new LinkedHashSet<>(); + try { + Class dgm = loadClass("org.codehaus.groovy.runtime.DefaultGroovyMethods"); + Class dgsm = loadClass("org.codehaus.groovy.runtime.DefaultGroovyStaticMethods"); + + Collections.addAll(defaultCategories, (Class[]) dgm.getField("DGM_LIKE_CLASSES").get(dgm)); + + defaultStaticCategories.add(dgsm); + + new ExtensionModuleScanner(module -> { + if (module instanceof SimpleExtensionModule) { + defaultCategories.addAll(((SimpleExtensionModule) module).getInstanceMethodsExtensionClasses()); + defaultStaticCategories.addAll(((SimpleExtensionModule) module).getStaticMethodsExtensionClasses()); + } + }, this).scanClasspathModules(); + + defaultCategories.addAll(defaultStaticCategories); + + } catch (Exception e) { + Util.log(e, "Failed to find Default Groovy Methods with " + this); + } + } + } + } + return Collections.unmodifiableSet(defaultCategories); + } + + public boolean isDefaultStaticCategory(String name) { + if (defaultStaticCategories == null) getDefaultCategories(); + return defaultStaticCategories.stream().map(Class::getName).anyMatch(name::equals); + } } } diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/CategoryTypeLookup.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/CategoryTypeLookup.java index c8d8759c20..e910f03273 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/CategoryTypeLookup.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/CategoryTypeLookup.java @@ -54,7 +54,7 @@ public TypeLookupResult lookupType(Expression node, VariableScope scope, ClassNo for (ClassNode category : scope.getCategoryNames()) { for (MethodNode method : category.getMethods(simpleName)) { - if (isCompatibleCategoryMethod(method, normalizedType)) { + if (isCompatibleCategoryMethod(method, normalizedType, scope)) { candidates.add(method); } } @@ -62,7 +62,7 @@ public TypeLookupResult lookupType(Expression node, VariableScope scope, ClassNo if (getterName != null) { for (MethodNode method : category.getMethods(getterName)) { if (AccessorSupport.findAccessorKind(method, true) == AccessorSupport.GETTER && - isCompatibleCategoryMethod(method, normalizedType)) { + isCompatibleCategoryMethod(method, normalizedType, scope)) { candidates.add(method); } } @@ -71,7 +71,7 @@ public TypeLookupResult lookupType(Expression node, VariableScope scope, ClassNo if (setterName != null) { for (MethodNode method : category.getMethods(setterName)) { if (AccessorSupport.findAccessorKind(method, true) == AccessorSupport.SETTER && - isCompatibleCategoryMethod(method, normalizedType)) { + isCompatibleCategoryMethod(method, normalizedType, scope)) { candidates.add(method); } } @@ -87,7 +87,7 @@ public TypeLookupResult lookupType(Expression node, VariableScope scope, ClassNo MethodNode method = selectBestMatch(candidates, argumentTypes); TypeLookupResult result = new TypeLookupResult(method.getReturnType(), method.getDeclaringClass(), method, - isDefaultGroovyMethod(method) ? TypeConfidence.LOOSELY_INFERRED : TypeConfidence.INFERRED, scope); + isDefaultGroovyMethod(method, scope) ? TypeConfidence.LOOSELY_INFERRED : TypeConfidence.INFERRED, scope); result.isGroovy = true; // enable semantic highlighting as Groovy method return result; } @@ -105,17 +105,16 @@ protected static boolean isCompatibleConstantExpression(Expression node, Variabl return false; } - protected static boolean isCompatibleCategoryMethod(MethodNode method, ClassNode firstArgumentType) { + protected static boolean isCompatibleCategoryMethod(MethodNode method, ClassNode firstArgumentType, VariableScope scope) { if (method.isStatic()) { Parameter[] paramters = method.getParameters(); if (paramters != null && paramters.length > 0) { ClassNode parameterType = paramters[0].getType(); - if (VariableScope.CLASS_CLASS_NODE.equals(firstArgumentType) && - VariableScope.DGSM_CLASS_NODE.equals(method.getDeclaringClass())) { + if (VariableScope.CLASS_CLASS_NODE.equals(firstArgumentType) && isDefaultGroovyStaticMethod(method, scope)) { parameterType = VariableScope.newClassClassNode(parameterType); } if (isTypeCompatible(firstArgumentType, parameterType)) { - return !isDefaultGroovyMethod(method) || !GroovyUtils.isDeprecated(method); + return !isDefaultGroovyMethod(method, scope) || !GroovyUtils.isDeprecated(method); } } } @@ -138,8 +137,12 @@ protected static boolean isTypeCompatible(ClassNode source, ClassNode target) { return false; } - protected static boolean isDefaultGroovyMethod(MethodNode method) { - return VariableScope.ALL_DEFAULT_CATEGORIES.contains(method.getDeclaringClass()); + protected static boolean isDefaultGroovyMethod(MethodNode method, VariableScope scope) { + return (VariableScope.DGM_CLASS_NODE.equals(method.getDeclaringClass()) || scope.isDefaultCategory(method.getDeclaringClass())); + } + + protected static boolean isDefaultGroovyStaticMethod(MethodNode method, VariableScope scope) { + return (VariableScope.DGSM_CLASS_NODE.equals(method.getDeclaringClass()) || scope.isDefaultStaticCategory(method.getDeclaringClass())); } /** diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/VariableScope.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/VariableScope.java index 696e40cc93..8a22661d0a 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/VariableScope.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/VariableScope.java @@ -31,7 +31,6 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -75,6 +74,7 @@ import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods; +import org.codehaus.jdt.groovy.internal.compiler.GroovyClassLoaderFactory.GrapeAwareGroovyClassLoader; import org.codehaus.jdt.groovy.internal.compiler.ast.JDTMethodNode; import org.eclipse.core.runtime.Assert; import org.eclipse.jdt.groovy.core.util.GroovyUtils; @@ -126,17 +126,6 @@ public class VariableScope implements Iterable { public static final ClassNode DGM_CLASS_NODE = ClassHelper.make(DefaultGroovyMethods.class); public static final ClassNode DGSM_CLASS_NODE = ClassHelper.make(DefaultGroovyStaticMethods.class); - public static final Set ALL_DEFAULT_CATEGORIES; - static { - Set dgm_classes = Arrays.stream(DefaultGroovyMethods.DGM_LIKE_CLASSES) - .map(ClassHelper::make).collect(Collectors.toCollection(LinkedHashSet::new)); - dgm_classes.remove(DGM_CLASS_NODE); - dgm_classes.add(DGSM_CLASS_NODE); - dgm_classes.add(DGM_CLASS_NODE); - - ALL_DEFAULT_CATEGORIES = Collections.unmodifiableSet(dgm_classes); - } - // primitive wrapper classes public static final ClassNode BOOLEAN_CLASS_NODE = ClassHelper.Boolean_TYPE; public static final ClassNode CHARACTER_CLASS_NODE = ClassHelper.Character_TYPE; @@ -475,18 +464,27 @@ public boolean isPrimaryNode() { * @return all categories active in this scope */ public Set getCategoryNames() { - if (parent == null) { - return ALL_DEFAULT_CATEGORIES; - } + Set categories; + + if (parent != null) { + categories = parent.getCategoryNames(); + // look at the parent scope's category, not this scope's category + // if this scope represents a "use" block, category is not active + // until child scopes + if (parent.isCategoryBeingDeclared()) { + categories = new LinkedHashSet<>(categories); + categories.add(parent.categoryBeingDeclared); + } + } else { + categories = scopeNode.getNodeMetaData(DefaultGroovyMethods.class); + if (categories == null) { + GrapeAwareGroovyClassLoader gcl = (GrapeAwareGroovyClassLoader) ((ModuleNode) scopeNode).getUnit().getClassLoader(); + categories = gcl.getDefaultCategories().stream().map(ClassNode::new).collect(Collectors.toCollection(LinkedHashSet::new)); - Set categories = parent.getCategoryNames(); - // look at the parent scope's category, not this scope's category - // if this scope represents a "use" block, category is not active - // until child scopes - if (parent.isCategoryBeingDeclared()) { - categories = new LinkedHashSet<>(categories); - categories.add(parent.categoryBeingDeclared); + scopeNode.putNodeMetaData(DefaultGroovyMethods.class, Collections.unmodifiableSet(categories)); + } } + return categories; } @@ -498,6 +496,20 @@ public void setCategoryBeingDeclared(ClassNode categoryBeingDeclared) { this.categoryBeingDeclared = categoryBeingDeclared; } + public boolean isDefaultCategory(ClassNode category) { + ModuleNode module = getEnclosingModuleNode(); + Set defaultCategories = module.getNodeMetaData(DefaultGroovyMethods.class); + + return defaultCategories.contains(category); + } + + public boolean isDefaultStaticCategory(ClassNode category) { + ModuleNode module = getEnclosingModuleNode(); + GrapeAwareGroovyClassLoader loader = (GrapeAwareGroovyClassLoader) module.getUnit().getClassLoader(); + + return loader.isDefaultStaticCategory(category.getName()); + } + /** * Finds the variable in the current scope or parent scopes. * diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/creators/CategoryProposalCreator.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/creators/CategoryProposalCreator.java index 6c3808aa98..0a5615c494 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/creators/CategoryProposalCreator.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/creators/CategoryProposalCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2017 the original author or authors. + * Copyright 2009-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,10 +62,10 @@ public List findAllProposals(ClassNode selfType, Set List groovyProposals = new LinkedList<>(); for (ClassNode category : categories) { - boolean isDGMCategory = isDGM(category); + boolean isDefaultCategory = isDefaultCategory(category); for (MethodNode method : category.getAllDeclaredMethods()) { // check for DGMs filtered by deprecation or user preference - if (isDGMCategory && (GroovyUtils.isDeprecated(method) || filter.isFiltered(method))) { + if (isDefaultCategory && (GroovyUtils.isDeprecated(method) || filter.isFiltered(method))) { continue; } String methodName = method.getName(); @@ -126,13 +126,12 @@ protected boolean isDuplicate(MethodNode newMethod, Map return false; } - protected boolean isDGM(ClassNode category) { - return VariableScope.ALL_DEFAULT_CATEGORIES.contains(category); + protected boolean isDefaultCategory(ClassNode category) { + return (VariableScope.DGM_CLASS_NODE.equals(category) || (currentScope != null && currentScope.isDefaultCategory(category))); } - protected boolean isDGSM(ClassNode category) { - // TODO: check the runtime DGM configuration - return VariableScope.DGSM_CLASS_NODE.equals(category); + protected boolean isDefaultStaticCategory(ClassNode category) { + return (VariableScope.DGSM_CLASS_NODE.equals(category) || (currentScope != null && currentScope.isDefaultStaticCategory(category))); } @Override @@ -147,7 +146,7 @@ protected class CategoryMethodProposal extends GroovyMethodProposal { protected CategoryMethodProposal(MethodNode method) { super(method, "Groovy"); - if (isDGM(method.getDeclaringClass())) { + if (isDefaultCategory(method.getDeclaringClass())) { setRelevanceMultiplier(0.1f); } else { setRelevanceMultiplier(5); @@ -157,7 +156,7 @@ protected CategoryMethodProposal(MethodNode method) { @Override protected int getModifiers() { int modifiers = super.getModifiers(); - if (!isDGSM(getMethod().getDeclaringClass())) { + if (!isDefaultStaticCategory(getMethod().getDeclaringClass())) { modifiers &= ~Flags.AccStatic; // category methods are defined as static, but should not appear as such } return modifiers; @@ -240,11 +239,11 @@ protected class CategoryPropertyProposal extends GroovyFieldProposal { protected CategoryPropertyProposal(MethodNode method) { super(createMockField(method)); - if (!isDGSM(method.getDeclaringClass())) { + if (!isDefaultStaticCategory(method.getDeclaringClass())) { getField().setModifiers(getField().getModifiers() & ~Flags.AccStatic); } - if (isDGM(method.getDeclaringClass())) { + if (isDefaultCategory(method.getDeclaringClass())) { setRelevanceMultiplier(0.1f); } else { setRelevanceMultiplier(5); diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/StatementAndExpressionCompletionProcessor.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/StatementAndExpressionCompletionProcessor.java index 792ee94c01..d474b521af 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/StatementAndExpressionCompletionProcessor.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/processors/StatementAndExpressionCompletionProcessor.java @@ -26,6 +26,7 @@ import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; @@ -121,11 +122,10 @@ public VisitStatus acceptASTNode(ASTNode node, TypeLookupResult result, IJavaEle if (clazz.redirect() == clazz && clazz.isScript()) { return VisitStatus.CONTINUE; } - } else if (node instanceof MethodNode) { - MethodNode run = (MethodNode) node; - if (run.getName().equals("run") && - run.getDeclaringClass().isScript() && - (run.getParameters() == null || run.getParameters().length == 0)) { + } else if (node instanceof MethodNode && !(node instanceof ConstructorNode)) { + MethodNode meth = (MethodNode) node; + if (meth.getName().equals("run") && meth.getDeclaringClass().isScript() && + (meth.getParameters() == null || meth.getParameters().length == 0)) { return VisitStatus.CONTINUE; } } else if (node == lhsNode) { @@ -220,7 +220,7 @@ private void setResultingType(TypeLookupResult result, boolean derefList) { * Determines if this is the lhs of an array access -- the 'foo' of 'foo[0]'. */ private boolean doTestForAfterArrayAccess(ASTNode node) { - return node == arrayAccessLHS; + return (node == arrayAccessLHS); } private void maybeRememberTypeOfLHS(TypeLookupResult result) { @@ -286,14 +286,18 @@ private boolean doTest(ASTNode node) { } else if (node instanceof BinaryExpression) { BinaryExpression bin = (BinaryExpression) node; if (bin.getLeftExpression() == arrayAccessLHS) { - // don't return true here, but rather wait for the LHS to - // come through - // this way we can use the derefed value as the completion - // type + // don't return true here, but rather wait for the LHS to come through + // this way we can use the derefed value as the completion type return false; } } - return isNotExpressionAndStatement(completionNode, node) && completionNode.getStart() == node.getStart() && completionNode.getEnd() == node.getEnd(); + + boolean rangeMatch = (completionNode.getStart() == node.getStart() && completionNode.getEnd() == node.getEnd()); + if (!rangeMatch && node instanceof MethodNode && getContext().completionExpression.isEmpty()) { + rangeMatch = doTest(((MethodNode) node).getCode()); + } + + return (isNotExpressionAndStatement(completionNode, node) && rangeMatch); } /** @@ -428,7 +432,8 @@ public List generateProposals(IProgressMonitor monitor) { containingClass = null; } if (containingClass != null) { - groovyProposals.addAll(new CategoryProposalCreator().findAllProposals(containingClass, VariableScope.ALL_DEFAULT_CATEGORIES, context.getPerceivedCompletionExpression(), false, isPrimary)); + Set categories = context.unit.getModuleNode().getNodeMetaData(VariableScope.DGM_CLASS_NODE.getTypeClass()); + groovyProposals.addAll(new CategoryProposalCreator().findAllProposals(containingClass, categories, context.getPerceivedCompletionExpression(), false, isPrimary)); } else if (node instanceof ImportNode) { ImportNode importNode = (ImportNode) node; if (importNode.isStatic()) { diff --git a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/CompletionNodeFinder.java b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/CompletionNodeFinder.java index 67c3dd084f..b8897062c5 100644 --- a/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/CompletionNodeFinder.java +++ b/ide/org.codehaus.groovy.eclipse.codeassist.completion/src/org/codehaus/groovy/eclipse/codeassist/requestor/CompletionNodeFinder.java @@ -368,8 +368,7 @@ public void visitBlockStatement(BlockStatement statement) { blockStack.add(statement); super.visitBlockStatement(statement); if (check(statement)) { - // if we get here, then we know that we are in this block statement, - // but not inside any expression. Use this to complete on + // if we get here, then we know that we are in this block statement, but not inside any expression createContext(blockStack.getLast(), statement, expressionOrStatement()); } blockStack.removeLast();