Skip to content

Commit

Permalink
Load the Default Groovy Method types from project classpath
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Apr 8, 2018
1 parent 59c4370 commit d58c0e9
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -175,9 +178,9 @@ private static IProject findProject(String projectName) {

private static void calculateClasspath(IJavaProject javaProject, Set<String> classPaths, Set<String> 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<String> paths = (unresolved.getType() == IRuntimeClasspathEntry.CONTAINER ? classPaths : xformPaths);
for (IRuntimeClasspathEntry resolved : resolveRuntimeClasspathEntry(unresolved, javaProject)) {
paths.add(getAbsoluteLocation(resolved));
Expand Down Expand Up @@ -238,11 +241,9 @@ private static URLClassLoader newClassLoader(Set<String> 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);
}
Expand All @@ -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<Class> defaultCategories;
private volatile Set<Class> defaultStaticCategories;

public Set<Class> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ 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);
}
}
String getterName = AccessorSupport.GETTER.createAccessorName(simpleName);
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);
}
}
Expand All @@ -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);
}
}
Expand All @@ -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;
}
Expand All @@ -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);
}
}
}
Expand All @@ -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()));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -126,17 +126,6 @@ public class VariableScope implements Iterable<VariableScope.VariableInfo> {
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<ClassNode> ALL_DEFAULT_CATEGORIES;
static {
Set<ClassNode> 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;
Expand Down Expand Up @@ -475,18 +464,27 @@ public boolean isPrimaryNode() {
* @return all categories active in this scope
*/
public Set<ClassNode> getCategoryNames() {
if (parent == null) {
return ALL_DEFAULT_CATEGORIES;
}
Set<ClassNode> 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<ClassNode> 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;
}

Expand All @@ -498,6 +496,20 @@ public void setCategoryBeingDeclared(ClassNode categoryBeingDeclared) {
this.categoryBeingDeclared = categoryBeingDeclared;
}

public boolean isDefaultCategory(ClassNode category) {
ModuleNode module = getEnclosingModuleNode();
Set<ClassNode> 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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -62,10 +62,10 @@ public List<IGroovyProposal> findAllProposals(ClassNode selfType, Set<ClassNode>

List<IGroovyProposal> 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();
Expand Down Expand Up @@ -126,13 +126,12 @@ protected boolean isDuplicate(MethodNode newMethod, Map<String, List<MethodNode>
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
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit d58c0e9

Please sign in to comment.