/*
* 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.ast.tools;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.SpreadExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE;
public class ClassNodeUtils {
public static void addInterfaceMethods(ClassNode cnode, Map<String, MethodNode> methodsMap) {
// add in unimplemented abstract methods from the interfaces
for (ClassNode iface : cnode.getInterfaces()) {
Map<String, MethodNode> ifaceMethodsMap = iface.getDeclaredMethodsMap();
for (String methSig : ifaceMethodsMap.keySet()) {
if (!methodsMap.containsKey(methSig)) {
MethodNode methNode = ifaceMethodsMap.get(methSig);
methodsMap.put(methSig, methNode);
}
}
}
}
public static Map<String, MethodNode> getDeclaredMethodMapsFromInterfaces(ClassNode classNode) {
Map<String, MethodNode> result = new HashMap<String, MethodNode>();
ClassNode[] interfaces = classNode.getInterfaces();
for (ClassNode iface : interfaces) {
result.putAll(iface.getDeclaredMethodsMap());
}
return result;
}
public static void addDeclaredMethodMapsFromSuperInterfaces(ClassNode cn, Map<String, MethodNode> allInterfaceMethods) {
List cnInterfaces = Arrays.asList(cn.getInterfaces());
ClassNode sn = cn.getSuperClass();
while (sn != null && !sn.equals(ClassHelper.OBJECT_TYPE)) {
ClassNode[] interfaces = sn.getInterfaces();
for (ClassNode iface : interfaces) {
if (!cnInterfaces.contains(iface)) {
allInterfaceMethods.putAll(iface.getDeclaredMethodsMap());
}
}
sn = sn.getSuperClass();
}
}
/**
* Returns true if the given method has a possibly matching static method with the given name and arguments.
*
* @param cNode the ClassNode of interest
* @param name the name of the method of interest
* @param arguments the arguments to match against
* @param trySpread whether to try to account for SpreadExpressions within the arguments
* @return true if a matching method was found
*/
public static boolean hasPossibleStaticMethod(ClassNode cNode, String name, Expression arguments, boolean trySpread) {
int count = 0;
boolean foundSpread = false;
if (arguments instanceof TupleExpression) {
TupleExpression tuple = (TupleExpression) arguments;
for (Expression arg : tuple.getExpressions()) {
if (arg instanceof SpreadExpression) {
foundSpread = true;
} else {
count++;
}
}
} else if (arguments instanceof MapExpression) {
count = 1;
}
for (MethodNode method : cNode.getMethods(name)) {
if (method.isStatic()) {
Parameter[] parameters = method.getParameters();
// do fuzzy match for spread case: count will be number of non-spread args
if (trySpread && foundSpread && parameters.length >= count) return true;
if (parameters.length == count) return true;
// handle varargs case
if (parameters.length > 0 && parameters[parameters.length - 1].getType().isArray()) {
if (count >= parameters.length - 1) return true;
// fuzzy match any spread to a varargs
if (trySpread && foundSpread) return true;
}
// handle parameters with default values
int nonDefaultParameters = 0;
for (Parameter parameter : parameters) {
if (!parameter.hasInitialExpression()) {
nonDefaultParameters++;
}
}
if (count < parameters.length && nonDefaultParameters <= count) {
return true;
}
// TODO handle spread with nonDefaultParams?
}
}
return false;
}
/**
* Return true if we have a static accessor
*/
public static boolean hasPossibleStaticProperty(ClassNode candidate, String methodName) {
// assume explicit static method call checked first so we can assume a simple check here
if (!methodName.startsWith("get") && !methodName.startsWith("is")) {
return false;
}
String propName = getPropNameForAccessor(methodName);
PropertyNode pNode = getStaticProperty(candidate, propName);
return pNode != null && (methodName.startsWith("get") || boolean_TYPE.equals(pNode.getType()));
}
/**
* Returns the property name, e.g. age, given an accessor name, e.g. getAge.
* Returns the original if a valid prefix cannot be removed.
*
* @param accessorName the accessor name of interest, e.g. getAge
* @return the property name, e.g. age, or original if not a valid property accessor name
*/
public static String getPropNameForAccessor(String accessorName) {
if (!isValidAccessorName(accessorName)) return accessorName;
int prefixLength = accessorName.startsWith("is") ? 2 : 3;
return String.valueOf(accessorName.charAt(prefixLength)).toLowerCase() + accessorName.substring(prefixLength + 1);
}
/**
* Detect whether the given accessor name starts with "get", "set" or "is" followed by at least one character.
*
* @param accessorName the accessor name of interest, e.g. getAge
* @return true if a valid prefix is found
*/
public static boolean isValidAccessorName(String accessorName) {
if (accessorName.startsWith("get") || accessorName.startsWith("is") || accessorName.startsWith("set")) {
int prefixLength = accessorName.startsWith("is") ? 2 : 3;
return accessorName.length() > prefixLength;
};
return false;
}
public static boolean hasStaticProperty(ClassNode cNode, String propName) {
return getStaticProperty(cNode, propName) != null;
}
/**
* Detect whether a static property with the given name is within the class
* or a super class.
*
* @param cNode the ClassNode of interest
* @param propName the property name
* @return the static property if found or else null
*/
public static PropertyNode getStaticProperty(ClassNode cNode, String propName) {
ClassNode classNode = cNode;
while (classNode != null) {
for (PropertyNode pn : classNode.getProperties()) {
if (pn.getName().equals(propName) && pn.isStatic()) return pn;
}
classNode = classNode.getSuperClass();
}
return null;
}
public static boolean isInnerClass(ClassNode currentClass) {
return currentClass.getOuterClass() != null && !currentClass.isStaticClass();
}
}