blob: df8f8b6f56b0fbb5a6a81bb24c27ba4dbe9fc1a1 [file] [log] [blame]
/*
* 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.codehaus.groovy.transform.stc;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.runtime.m12n.ExtensionModule;
import org.codehaus.groovy.runtime.m12n.ExtensionModuleScanner;
import org.codehaus.groovy.runtime.m12n.MetaInfExtensionModule;
import org.codehaus.groovy.runtime.memoize.EvictableCache;
import org.codehaus.groovy.runtime.memoize.StampedCommonCache;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
/**
* @since 3.0.0
*/
public abstract class AbstractExtensionMethodCache {
final EvictableCache<ClassLoader, Map<String, List<MethodNode>>> cache = new StampedCommonCache<>(new WeakHashMap<>());
public Map<String, List<MethodNode>> get(ClassLoader loader) {
return cache.getAndPut(loader, this::getMethodsFromClassLoader);
}
private Map<String, List<MethodNode>> getMethodsFromClassLoader(ClassLoader classLoader) {
final List<ExtensionModule> modules = new LinkedList<>();
ExtensionModuleScanner scanner = new ExtensionModuleScanner(
module -> {
if (!(module instanceof MetaInfExtensionModule)) return;
boolean skip = false;
for (ExtensionModule extensionModule : modules) {
if (extensionModule.getName().equals(module.getName())) {
skip = true;
break;
}
}
if (!skip) modules.add(module);
},
classLoader
);
scanner.scanClasspathModules();
return makeMethodsUnmodifiable(getMethods(modules));
}
/**
* Returns a map which contains, as the key, the name of a class. The value
* consists of a list of MethodNode, one for each groovy default method found
* which is applicable for this class.
*
* @param modules extension modules
* @return
*/
private Map<String, List<MethodNode>> getMethods(List<ExtensionModule> modules) {
Set<Class> instanceExtClasses = new LinkedHashSet<>();
Set<Class> staticExtClasses = new LinkedHashSet<>();
for (ExtensionModule module : modules) {
MetaInfExtensionModule extensionModule = (MetaInfExtensionModule) module;
instanceExtClasses.addAll(extensionModule.getInstanceMethodsExtensionClasses());
staticExtClasses.addAll(extensionModule.getStaticMethodsExtensionClasses());
}
Map<String, List<MethodNode>> methods = new HashMap<>();
addAdditionalClassesToScan(instanceExtClasses, staticExtClasses);
scan(methods, staticExtClasses, true);
scan(methods, instanceExtClasses, false);
return methods;
}
private Map<String, List<MethodNode>> makeMethodsUnmodifiable(Map<String, List<MethodNode>> methods) {
methods.replaceAll((k, v) -> Collections.unmodifiableList(v));
return Collections.unmodifiableMap(methods);
}
protected abstract void addAdditionalClassesToScan(Set<Class> instanceExtClasses, Set<Class> staticExtClasses);
private void scan(Map<String, List<MethodNode>> accumulator, Iterable<Class> allClasses, boolean isStatic) {
Predicate<MethodNode> methodFilter = getMethodFilter();
Function<MethodNode, String> methodMapper = getMethodMapper();
for (Class dgmLikeClass : allClasses) {
ClassNode cn = makeWithoutCaching(dgmLikeClass, true);
for (MethodNode methodNode : cn.getMethods()) {
if (!(methodNode.isStatic() && methodNode.isPublic()) || methodNode.getParameters().length == 0) continue;
if (methodFilter.test(methodNode)) continue;
accumulate(accumulator, isStatic, methodNode, methodMapper);
}
}
}
protected abstract Predicate<MethodNode> getMethodFilter();
protected abstract Function<MethodNode, String> getMethodMapper();
private void accumulate(Map<String, List<MethodNode>> accumulator, boolean isStatic, MethodNode metaMethod,
Function<MethodNode, String> mapperFunction) {
Parameter[] types = metaMethod.getParameters();
Parameter[] parameters = new Parameter[types.length - 1];
System.arraycopy(types, 1, parameters, 0, parameters.length);
ExtensionMethodNode node = new ExtensionMethodNode(
metaMethod,
metaMethod.getName(),
metaMethod.getModifiers(),
metaMethod.getReturnType(),
parameters,
ClassNode.EMPTY_ARRAY, null,
isStatic);
node.setGenericsTypes(metaMethod.getGenericsTypes());
ClassNode declaringClass = types[0].getType();
node.setDeclaringClass(declaringClass);
String key = mapperFunction.apply(metaMethod);
List<MethodNode> nodes = accumulator.computeIfAbsent(key, k -> new ArrayList<>());
nodes.add(node);
}
}