blob: bbca52a826b14defe51150ad06675adc4c06227e [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.ast;
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GString;
import groovy.lang.GroovyInterceptable;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.MetaClass;
import groovy.lang.Range;
import groovy.lang.Reference;
import groovy.lang.Script;
import groovy.lang.Tuple;
import groovy.lang.Tuple0;
import groovy.lang.Tuple1;
import groovy.lang.Tuple10;
import groovy.lang.Tuple11;
import groovy.lang.Tuple12;
import groovy.lang.Tuple13;
import groovy.lang.Tuple14;
import groovy.lang.Tuple15;
import groovy.lang.Tuple16;
import groovy.lang.Tuple2;
import groovy.lang.Tuple3;
import groovy.lang.Tuple4;
import groovy.lang.Tuple5;
import groovy.lang.Tuple6;
import groovy.lang.Tuple7;
import groovy.lang.Tuple8;
import groovy.lang.Tuple9;
import org.apache.groovy.util.Maps;
import org.codehaus.groovy.classgen.asm.util.TypeUtil;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.GeneratedLambda;
import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
import org.codehaus.groovy.transform.trait.Traits;
import org.codehaus.groovy.util.ManagedConcurrentMap;
import org.codehaus.groovy.util.ReferenceBundle;
import org.codehaus.groovy.vmplugin.VMPluginFactory;
import org.objectweb.asm.Opcodes;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.invoke.SerializedLambda;
import java.lang.ref.SoftReference;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Helper for {@link ClassNode} and classes handling them. Contains a set of
* pre-defined instances for the most used types and some code for cached node
* creation and basic handling.
*/
public class ClassHelper {
private static final Class[] classes = new Class[]{
Object.class, Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE,
Integer.TYPE, Long.TYPE, Double.TYPE, Float.TYPE, Void.TYPE,
Closure.class, GString.class, List.class, Map.class, Range.class,
Pattern.class, Script.class, String.class, Boolean.class,
Character.class, Byte.class, Short.class, Integer.class, Long.class,
Double.class, Float.class, BigDecimal.class, BigInteger.class,
Number.class, Void.class, Reference.class, Class.class, MetaClass.class,
Iterator.class, GeneratedClosure.class, GeneratedLambda.class, GroovyObjectSupport.class
};
public static final Class[] TUPLE_CLASSES = new Class[]{
Tuple0.class, Tuple1.class, Tuple2.class, Tuple3.class, Tuple4.class, Tuple5.class, Tuple6.class,
Tuple7.class, Tuple8.class, Tuple9.class, Tuple10.class, Tuple11.class, Tuple12.class, Tuple13.class,
Tuple14.class, Tuple15.class, Tuple16.class
};
private static final String[] primitiveClassNames = new String[]{
"", "boolean", "char", "byte", "short", "int", "long", "double", "float", "void"
};
public static final ClassNode
DYNAMIC_TYPE = makeCached(Object.class),
OBJECT_TYPE = DYNAMIC_TYPE,
CLOSURE_TYPE = makeCached(Closure.class),
SERIALIZEDLAMBDA_TYPE = makeCached(SerializedLambda .class),
GSTRING_TYPE = makeCached(GString.class),
RANGE_TYPE = makeCached(Range.class),
PATTERN_TYPE = makeCached(Pattern.class),
STRING_TYPE = makeCached(String.class),
SCRIPT_TYPE = makeCached(Script.class),
BINDING_TYPE = makeCached(Binding.class),
boolean_TYPE = makeCached(boolean.class),
char_TYPE = makeCached(char.class),
byte_TYPE = makeCached(byte.class),
int_TYPE = makeCached(int.class),
long_TYPE = makeCached(long.class),
short_TYPE = makeCached(short.class),
double_TYPE = makeCached(double.class),
float_TYPE = makeCached(float.class),
Byte_TYPE = makeCached(Byte.class),
Short_TYPE = makeCached(Short.class),
Integer_TYPE = makeCached(Integer.class),
Long_TYPE = makeCached(Long.class),
Character_TYPE = makeCached(Character.class),
Float_TYPE = makeCached(Float.class),
Double_TYPE = makeCached(Double.class),
Boolean_TYPE = makeCached(Boolean.class),
BigInteger_TYPE = makeCached(java.math.BigInteger.class),
BigDecimal_TYPE = makeCached(java.math.BigDecimal.class),
Number_TYPE = makeCached(Number.class),
VOID_TYPE = makeCached(Void.TYPE),
void_WRAPPER_TYPE = makeCached(Void.class),
METACLASS_TYPE = makeCached(MetaClass.class),
Iterator_TYPE = makeCached(Iterator.class),
Annotation_TYPE = makeCached(Annotation.class),
ELEMENT_TYPE_TYPE = makeCached(ElementType.class),
AUTOCLOSEABLE_TYPE = makeCached(AutoCloseable.class),
SERIALIZABLE_TYPE = makeCached(Serializable.class),
// uncached constants
MAP_TYPE = makeWithoutCaching(Map.class),
LIST_TYPE = makeWithoutCaching(List.class),
Enum_Type = makeWithoutCaching(Enum.class),
CLASS_Type = makeWithoutCaching(Class.class),
TUPLE_TYPE = makeWithoutCaching(Tuple.class),
REFERENCE_TYPE = makeWithoutCaching(Reference.class),
COMPARABLE_TYPE = makeWithoutCaching(Comparable.class),
GROOVY_OBJECT_TYPE = makeWithoutCaching(GroovyObject.class),
GENERATED_LAMBDA_TYPE = makeWithoutCaching(GeneratedLambda.class),
GENERATED_CLOSURE_Type = makeWithoutCaching(GeneratedClosure.class),
GROOVY_INTERCEPTABLE_TYPE = makeWithoutCaching(GroovyInterceptable.class),
GROOVY_OBJECT_SUPPORT_TYPE = makeWithoutCaching(GroovyObjectSupport.class);
private static final ClassNode[] types = new ClassNode[]{
OBJECT_TYPE,
boolean_TYPE, char_TYPE, byte_TYPE, short_TYPE,
int_TYPE, long_TYPE, double_TYPE, float_TYPE,
VOID_TYPE, CLOSURE_TYPE, GSTRING_TYPE,
LIST_TYPE, MAP_TYPE, RANGE_TYPE, PATTERN_TYPE,
SCRIPT_TYPE, STRING_TYPE, Boolean_TYPE, Character_TYPE,
Byte_TYPE, Short_TYPE, Integer_TYPE, Long_TYPE,
Double_TYPE, Float_TYPE, BigDecimal_TYPE, BigInteger_TYPE,
Number_TYPE,
void_WRAPPER_TYPE, REFERENCE_TYPE, CLASS_Type, METACLASS_TYPE,
Iterator_TYPE, GENERATED_CLOSURE_Type, GENERATED_LAMBDA_TYPE, GROOVY_OBJECT_SUPPORT_TYPE,
GROOVY_OBJECT_TYPE, GROOVY_INTERCEPTABLE_TYPE, Enum_Type, Annotation_TYPE
};
private static final int ABSTRACT_STATIC_PRIVATE = Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE;
private static final int VISIBILITY = 5; // public|protected
protected static final ClassNode[] EMPTY_TYPE_ARRAY = {};
public static final String OBJECT = "java.lang.Object";
public static ClassNode makeCached(Class c) {
final SoftReference<ClassNode> classNodeSoftReference = ClassHelperCache.classCache.get(c);
ClassNode classNode;
if (classNodeSoftReference == null || (classNode = classNodeSoftReference.get()) == null) {
classNode = new ClassNode(c);
ClassHelperCache.classCache.put(c, new SoftReference<ClassNode>(classNode));
VMPluginFactory.getPlugin().setAdditionalClassInformation(classNode);
}
return classNode;
}
/**
* Creates an array of ClassNodes using an array of classes.
* For each of the given classes a new ClassNode will be
* created
*
* @param classes an array of classes used to create the ClassNodes
* @return an array of ClassNodes
* @see #make(Class)
*/
public static ClassNode[] make(Class[] classes) {
ClassNode[] cns = new ClassNode[classes.length];
for (int i = 0; i < cns.length; i++) {
cns[i] = make(classes[i]);
}
return cns;
}
/**
* Creates a ClassNode using a given class.
* A new ClassNode object is only created if the class
* is not one of the predefined ones
*
* @param c class used to created the ClassNode
* @return ClassNode instance created from the given class
*/
public static ClassNode make(Class c) {
return make(c, true);
}
public static ClassNode make(Class c, boolean includeGenerics) {
for (int i = 0; i < classes.length; i++) {
if (c == classes[i]) return types[i];
}
if (c.isArray()) {
ClassNode cn = make(c.getComponentType(), includeGenerics);
return cn.makeArray();
}
return makeWithoutCaching(c, includeGenerics);
}
public static ClassNode makeWithoutCaching(Class c) {
return makeWithoutCaching(c, true);
}
public static ClassNode makeWithoutCaching(Class c, boolean includeGenerics) {
if (c.isArray()) {
ClassNode cn = makeWithoutCaching(c.getComponentType(), includeGenerics);
return cn.makeArray();
}
final ClassNode cached = makeCached(c);
if (includeGenerics) {
return cached;
} else {
ClassNode t = makeWithoutCaching(c.getName());
t.setRedirect(cached);
return t;
}
}
/**
* Creates a ClassNode using a given class.
* Unlike make(String) this method will not use the cache
* to create the ClassNode. This means the ClassNode created
* from this method using the same name will have a different
* reference
*
* @param name of the class the ClassNode is representing
* @see #make(String)
*/
public static ClassNode makeWithoutCaching(String name) {
ClassNode cn = new ClassNode(name, Opcodes.ACC_PUBLIC, OBJECT_TYPE);
cn.isPrimaryNode = false;
return cn;
}
/**
* Creates a ClassNode using a given class.
* If the name is one of the predefined ClassNodes then the
* corresponding ClassNode instance will be returned. If the
* name is null or of length 0 the dynamic type is returned
*
* @param name of the class the ClassNode is representing
*/
public static ClassNode make(String name) {
if (name == null || name.length() == 0) return DYNAMIC_TYPE;
for (int i = 0; i < primitiveClassNames.length; i++) {
if (primitiveClassNames[i].equals(name)) return types[i];
}
for (int i = 0; i < classes.length; i++) {
String cname = classes[i].getName();
if (name.equals(cname)) return types[i];
}
return makeWithoutCaching(name);
}
private static final Map<ClassNode, ClassNode> PRIMITIVE_TYPE_TO_WRAPPER_TYPE_MAP = Maps.of(
boolean_TYPE, Boolean_TYPE,
byte_TYPE, Byte_TYPE,
char_TYPE, Character_TYPE,
short_TYPE, Short_TYPE,
int_TYPE, Integer_TYPE,
long_TYPE, Long_TYPE,
float_TYPE, Float_TYPE,
double_TYPE, Double_TYPE,
VOID_TYPE, void_WRAPPER_TYPE
);
/**
* Creates a ClassNode containing the wrapper of a ClassNode
* of primitive type. Any ClassNode representing a primitive
* type should be created using the predefined types used in
* class. The method will check the parameter for known
* references of ClassNode representing a primitive type. If
* Reference is found, then a ClassNode will be contained that
* represents the wrapper class. For example for boolean, the
* wrapper class is java.lang.Boolean.
* <p>
* If the parameter is no primitive type, the redirected
* ClassNode will be returned
*
* @param cn the ClassNode containing a possible primitive type
* @see #make(Class)
* @see #make(String)
*/
public static ClassNode getWrapper(ClassNode cn) {
cn = cn.redirect();
if (!isPrimitiveType(cn)) return cn;
ClassNode result = PRIMITIVE_TYPE_TO_WRAPPER_TYPE_MAP.get(cn);
if (null != result) {
return result;
}
return cn;
}
private static final Map<ClassNode, ClassNode> WRAPPER_TYPE_TO_PRIMITIVE_TYPE_MAP = Maps.inverse(PRIMITIVE_TYPE_TO_WRAPPER_TYPE_MAP);
public static ClassNode getUnwrapper(ClassNode cn) {
cn = cn.redirect();
if (isPrimitiveType(cn)) return cn;
ClassNode result = WRAPPER_TYPE_TO_PRIMITIVE_TYPE_MAP.get(cn);
if (null != result) {
return result;
}
return cn;
}
/**
* Test to determine if a ClassNode is a primitive type.
* Note: this only works for ClassNodes created using a
* predefined ClassNode
*
* @param cn the ClassNode containing a possible primitive type
* @return true if the ClassNode is a primitive type
* @see #make(Class)
* @see #make(String)
*/
public static boolean isPrimitiveType(ClassNode cn) {
return TypeUtil.isPrimitiveType(cn);
}
/**
* Test to determine if a ClassNode is a type belongs to the list of types which
* are allowed to initialize constants directly in bytecode instead of using &lt;cinit&gt;
* <p>
* Note: this only works for ClassNodes created using a
* predefined ClassNode
*
* @param cn the ClassNode to be tested
* @return true if the ClassNode is of int, float, long, double or String type
* @see #make(Class)
* @see #make(String)
*/
public static boolean isStaticConstantInitializerType(ClassNode cn) {
cn = cn.redirect();
return cn == int_TYPE ||
cn == float_TYPE ||
cn == long_TYPE ||
cn == double_TYPE ||
cn == STRING_TYPE ||
// the next items require conversion to int when initializing
cn == byte_TYPE ||
cn == char_TYPE ||
cn == short_TYPE;
}
public static boolean isNumberType(ClassNode cn) {
cn = cn.redirect();
return cn == Byte_TYPE ||
cn == Short_TYPE ||
cn == Integer_TYPE ||
cn == Long_TYPE ||
cn == Float_TYPE ||
cn == Double_TYPE ||
cn == byte_TYPE ||
cn == short_TYPE ||
cn == int_TYPE ||
cn == long_TYPE ||
cn == float_TYPE ||
cn == double_TYPE;
}
public static ClassNode makeReference() {
return REFERENCE_TYPE.getPlainNodeReference();
}
public static boolean isCachedType(ClassNode type) {
for (ClassNode cachedType : types) {
if (cachedType == type) return true;
}
return false;
}
static class ClassHelperCache {
static ManagedConcurrentMap<Class, SoftReference<ClassNode>> classCache = new ManagedConcurrentMap<Class, SoftReference<ClassNode>>(ReferenceBundle.getWeakBundle());
}
public static boolean isSAMType(ClassNode type) {
return findSAM(type) != null;
}
public static boolean isFunctionalInterface(ClassNode type) {
// Functional interface must be an interface at first, or the following exception will occur:
// java.lang.invoke.LambdaConversionException: Functional interface SamCallable is not an interface
return type.isInterface() && isSAMType(type);
}
/**
* Returns the single abstract method of a class node, if it is a SAM type, or null otherwise.
*
* @param type a type for which to search for a single abstract method
* @return the method node if type is a SAM type, null otherwise
*/
public static MethodNode findSAM(ClassNode type) {
if (!Modifier.isAbstract(type.getModifiers())) return null;
if (type.isInterface()) {
List<MethodNode> methods;
if (type.isInterface()) {
// e.g. BinaryOperator extends BiFunction, BinaryOperator contains no abstract method, but it is really a SAM
methods = type.redirect().getAllDeclaredMethods();
} else {
methods = type.getMethods();
}
MethodNode found = null;
for (MethodNode mi : methods) {
// ignore methods, that are not abstract and from Object
if (!Modifier.isAbstract(mi.getModifiers())) continue;
// ignore trait methods which have a default implementation
if (Traits.hasDefaultImplementation(mi)) continue;
if (mi.getDeclaringClass().equals(OBJECT_TYPE)) continue;
if (OBJECT_TYPE.getDeclaredMethod(mi.getName(), mi.getParameters()) != null) continue;
// we have two methods, so no SAM
if (found != null) return null;
found = mi;
}
return found;
} else {
MethodNode found = null;
for (MethodNode mi : type.getAbstractMethods()) {
if (!hasUsableImplementation(type, mi)) {
if (found != null) return null;
found = mi;
}
}
return found;
}
}
private static boolean hasUsableImplementation(ClassNode c, MethodNode m) {
if (c == m.getDeclaringClass()) return false;
MethodNode found = c.getDeclaredMethod(m.getName(), m.getParameters());
if (found == null) return false;
int asp = found.getModifiers() & ABSTRACT_STATIC_PRIVATE;
int visible = found.getModifiers() & VISIBILITY;
if (visible != 0 && asp == 0) return true;
if (c.equals(OBJECT_TYPE)) return false;
return hasUsableImplementation(c.getSuperClass(), m);
}
/**
* Returns a super class or interface for a given class depending on a given target.
* If the target is no super class or interface, then null will be returned.
* For a non-primitive array type, returns an array of the componentType's super class
* or interface if the target is also an array.
*
* @param clazz the start class
* @param goalClazz the goal class
* @return the next super class or interface
*/
public static ClassNode getNextSuperClass(ClassNode clazz, ClassNode goalClazz) {
if (clazz.isArray()) {
if (!goalClazz.isArray()) return null;
ClassNode cn = getNextSuperClass(clazz.getComponentType(), goalClazz.getComponentType());
if (cn != null) cn = cn.makeArray();
return cn;
}
if (!goalClazz.isInterface()) {
if (clazz.isInterface()) {
if (OBJECT_TYPE.equals(clazz)) return null;
return OBJECT_TYPE;
} else {
return clazz.getUnresolvedSuperClass();
}
}
ClassNode[] interfaces = clazz.getUnresolvedInterfaces();
for (ClassNode anInterface : interfaces) {
if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(anInterface, goalClazz)) {
return anInterface;
}
}
//none of the interfaces here match, so continue with super class
return clazz.getUnresolvedSuperClass();
}
}