blob: db0fc3f46aafa9fc160ed1d01060e81384ea0598 [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.vmplugin.v5;
import groovy.lang.MetaClass;
import groovy.lang.MetaMethod;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CompileUnit;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.PackageNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.vmplugin.VMPlugin;
import org.codehaus.groovy.vmplugin.VMPluginFactory;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.MalformedParameterizedTypeException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.ReflectPermission;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.security.Permission;
import java.util.List;
/**
* java 5 based functions
*/
public class Java5 implements VMPlugin {
private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
private static final Class[] PLUGIN_DGM = {PluginDefaultGroovyMethods.class};
private static final Method[] EMPTY_METHOD_ARRAY = new Method[0];
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
public void setAdditionalClassInformation(ClassNode cn) {
setGenericsTypes(cn);
}
private void setGenericsTypes(ClassNode cn) {
TypeVariable[] tvs = cn.getTypeClass().getTypeParameters();
GenericsType[] gts = configureTypeVariable(tvs);
cn.setGenericsTypes(gts);
}
private GenericsType[] configureTypeVariable(TypeVariable[] tvs) {
if (tvs.length == 0) return null;
GenericsType[] gts = new GenericsType[tvs.length];
for (int i = 0; i < tvs.length; i++) {
gts[i] = configureTypeVariableDefinition(tvs[i]);
}
return gts;
}
private GenericsType configureTypeVariableDefinition(TypeVariable tv) {
return configureTypeVariableDefinition(configureTypeVariableReference(tv.getName()), configureTypes(tv.getBounds()));
}
public static GenericsType configureTypeVariableDefinition(ClassNode base, ClassNode[] cBounds) {
ClassNode redirect = base.redirect();
base.setRedirect(null);
GenericsType gt;
if (cBounds == null || cBounds.length == 0) {
gt = new GenericsType(base);
} else {
gt = new GenericsType(base, cBounds, null);
gt.setName(base.getName());
gt.setPlaceholder(true);
}
base.setRedirect(redirect);
return gt;
}
private ClassNode[] configureTypes(Type[] types) {
if (types.length == 0) return null;
ClassNode[] nodes = new ClassNode[types.length];
for (int i = 0; i < types.length; i++) {
nodes[i] = configureType(types[i]);
}
return nodes;
}
private ClassNode configureType(Type type) {
if (type instanceof WildcardType) {
return configureWildcardType((WildcardType) type);
} else if (type instanceof ParameterizedType) {
return configureParameterizedType((ParameterizedType) type);
} else if (type instanceof GenericArrayType) {
return configureGenericArray((GenericArrayType) type);
} else if (type instanceof TypeVariable) {
return configureTypeVariableReference(((TypeVariable) type).getName());
} else if (type instanceof Class) {
return configureClass((Class) type);
} else if (type==null) {
throw new GroovyBugError("Type is null. Most probably you let a transform reuse existing ClassNodes with generics information, that is now used in a wrong context.");
} else {
throw new GroovyBugError("unknown type: " + type + " := " + type.getClass());
}
}
private static ClassNode configureClass(Class c) {
if (c.isPrimitive()) {
return ClassHelper.make(c);
} else {
return ClassHelper.makeWithoutCaching(c, false);
}
}
private ClassNode configureGenericArray(GenericArrayType genericArrayType) {
Type component = genericArrayType.getGenericComponentType();
ClassNode node = configureType(component);
return node.makeArray();
}
private ClassNode configureWildcardType(WildcardType wildcardType) {
ClassNode base = ClassHelper.makeWithoutCaching("?");
base.setRedirect(ClassHelper.OBJECT_TYPE);
//TODO: more than one lower bound for wildcards?
ClassNode[] lowers = configureTypes(wildcardType.getLowerBounds());
ClassNode lower = null;
// TODO: is it safe to remove this? What was the original intention?
if (lowers != null) lower = lowers[0];
ClassNode[] upper = configureTypes(wildcardType.getUpperBounds());
GenericsType t = new GenericsType(base, upper, lower);
t.setWildcard(true);
ClassNode ref = ClassHelper.makeWithoutCaching(Object.class, false);
ref.setGenericsTypes(new GenericsType[]{t});
return ref;
}
private ClassNode configureParameterizedType(ParameterizedType parameterizedType) {
ClassNode base = configureType(parameterizedType.getRawType());
GenericsType[] gts = configureTypeArguments(parameterizedType.getActualTypeArguments());
base.setGenericsTypes(gts);
return base;
}
public static ClassNode configureTypeVariableReference(String name) {
ClassNode cn = ClassHelper.makeWithoutCaching(name);
cn.setGenericsPlaceHolder(true);
ClassNode cn2 = ClassHelper.makeWithoutCaching(name);
cn2.setGenericsPlaceHolder(true);
GenericsType[] gts = new GenericsType[]{new GenericsType(cn2)};
cn.setGenericsTypes(gts);
cn.setRedirect(ClassHelper.OBJECT_TYPE);
return cn;
}
private GenericsType[] configureTypeArguments(Type[] ta) {
if (ta.length == 0) return null;
GenericsType[] gts = new GenericsType[ta.length];
for (int i = 0; i < ta.length; i++) {
ClassNode t = configureType(ta[i]);
if (ta[i] instanceof WildcardType) {
GenericsType[] gen = t.getGenericsTypes();
gts[i] = gen[0];
} else {
gts[i] = new GenericsType(t);
}
}
return gts;
}
public Class[] getPluginDefaultGroovyMethods() {
return PLUGIN_DGM;
}
public Class[] getPluginStaticGroovyMethods() {
return EMPTY_CLASS_ARRAY;
}
private void setAnnotationMetaData(Annotation[] annotations, AnnotatedNode an) {
for (Annotation annotation : annotations) {
AnnotationNode node = new AnnotationNode(ClassHelper.make(annotation.annotationType()));
configureAnnotation(node, annotation);
an.addAnnotation(node);
}
}
@Deprecated
public void configureAnnotationFromDefinition(AnnotationNode definition, AnnotationNode root) {
VMPlugin plugin = VMPluginFactory.getPlugin();
plugin.configureAnnotationNodeFromDefinition(definition, root);
}
public void configureAnnotationNodeFromDefinition(AnnotationNode definition, AnnotationNode root) {
ClassNode type = definition.getClassNode();
if ("java.lang.annotation.Retention".equals(type.getName())) {
Expression exp = definition.getMember("value");
if (!(exp instanceof PropertyExpression)) return;
PropertyExpression pe = (PropertyExpression) exp;
String name = pe.getPropertyAsString();
RetentionPolicy policy = RetentionPolicy.valueOf(name);
setRetentionPolicy(policy, root);
} else if ("java.lang.annotation.Target".equals(type.getName())) {
Expression exp = definition.getMember("value");
if (!(exp instanceof ListExpression)) return;
ListExpression le = (ListExpression) exp;
int bitmap = 0;
for (Expression e : le.getExpressions()) {
if (!(e instanceof PropertyExpression)) return;
PropertyExpression element = (PropertyExpression) e;
String name = element.getPropertyAsString();
ElementType value = ElementType.valueOf(name);
bitmap |= getElementCode(value);
}
root.setAllowedTargets(bitmap);
}
}
public void configureAnnotation(AnnotationNode node) {
ClassNode type = node.getClassNode();
VMPlugin plugin = VMPluginFactory.getPlugin();
List<AnnotationNode> annotations = type.getAnnotations();
for (AnnotationNode an : annotations) {
plugin.configureAnnotationNodeFromDefinition(an, node);
}
plugin.configureAnnotationNodeFromDefinition(node, node);
}
private void configureAnnotation(AnnotationNode node, Annotation annotation) {
Class type = annotation.annotationType();
if (type == Retention.class) {
Retention r = (Retention) annotation;
RetentionPolicy value = r.value();
setRetentionPolicy(value, node);
node.setMember("value", new PropertyExpression(
new ClassExpression(ClassHelper.makeWithoutCaching(RetentionPolicy.class, false)),
value.toString()));
} else if (type == Target.class) {
Target t = (Target) annotation;
ElementType[] elements = t.value();
ListExpression elementExprs = new ListExpression();
for (ElementType element : elements) {
elementExprs.addExpression(new PropertyExpression(
new ClassExpression(ClassHelper.ELEMENT_TYPE_TYPE), element.name()));
}
node.setMember("value", elementExprs);
} else {
Method[] declaredMethods;
try {
declaredMethods = type.getDeclaredMethods();
} catch (SecurityException se) {
declaredMethods = EMPTY_METHOD_ARRAY;
}
for (Method declaredMethod : declaredMethods) {
try {
Object value = declaredMethod.invoke(annotation);
Expression valueExpression = annotationValueToExpression(value);
if (valueExpression == null)
continue;
node.setMember(declaredMethod.getName(), valueExpression);
} catch (IllegalAccessException | InvocationTargetException e) {
}
}
}
}
private Expression annotationValueToExpression (Object value) {
if (value == null || value instanceof String || value instanceof Number || value instanceof Character || value instanceof Boolean)
return new ConstantExpression(value);
if (value instanceof Class)
return new ClassExpression(ClassHelper.makeWithoutCaching((Class)value));
if (value.getClass().isArray()) {
ListExpression elementExprs = new ListExpression();
int len = Array.getLength(value);
for (int i = 0; i != len; ++i)
elementExprs.addExpression(annotationValueToExpression(Array.get(value, i)));
return elementExprs;
}
return null;
}
private static void setRetentionPolicy(RetentionPolicy value, AnnotationNode node) {
switch (value) {
case RUNTIME:
node.setRuntimeRetention(true);
break;
case SOURCE:
node.setSourceRetention(true);
break;
case CLASS:
node.setClassRetention(true);
break;
default:
throw new GroovyBugError("unsupported Retention " + value);
}
}
protected int getElementCode(ElementType value) {
switch (value) {
case TYPE:
return AnnotationNode.TYPE_TARGET;
case CONSTRUCTOR:
return AnnotationNode.CONSTRUCTOR_TARGET;
case METHOD:
return AnnotationNode.METHOD_TARGET;
case FIELD:
return AnnotationNode.FIELD_TARGET;
case PARAMETER:
return AnnotationNode.PARAMETER_TARGET;
case LOCAL_VARIABLE:
return AnnotationNode.LOCAL_VARIABLE_TARGET;
case ANNOTATION_TYPE:
return AnnotationNode.ANNOTATION_TARGET;
case PACKAGE:
return AnnotationNode.PACKAGE_TARGET;
}
if ("MODULE".equals(value.name())) {
return AnnotationNode.TYPE_TARGET;
} else {
throw new GroovyBugError("unsupported Target " + value);
}
}
private static void setMethodDefaultValue(MethodNode mn, Method m) {
ConstantExpression cExp = new ConstantExpression(m.getDefaultValue());
mn.setCode(new ReturnStatement(cExp));
mn.setAnnotationDefault(true);
}
public void configureClassNode(CompileUnit compileUnit, ClassNode classNode) {
try {
Class clazz = classNode.getTypeClass();
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
ClassNode ret = makeClassNode(compileUnit, f.getGenericType(), f.getType());
FieldNode fn = new FieldNode(f.getName(), f.getModifiers(), ret, classNode, null);
setAnnotationMetaData(f.getAnnotations(), fn);
classNode.addField(fn);
}
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
ClassNode ret = makeClassNode(compileUnit, m.getGenericReturnType(), m.getReturnType());
Parameter[] params = processParameters(compileUnit, m);
ClassNode[] exceptions = makeClassNodes(compileUnit, m.getGenericExceptionTypes(), m.getExceptionTypes());
MethodNode mn = new MethodNode(m.getName(), m.getModifiers(), ret, params, exceptions, null);
mn.setSynthetic(m.isSynthetic());
setMethodDefaultValue(mn, m);
setAnnotationMetaData(m.getAnnotations(), mn);
mn.setGenericsTypes(configureTypeVariable(m.getTypeParameters()));
classNode.addMethod(mn);
}
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor ctor : constructors) {
Type[] types = ctor.getGenericParameterTypes();
Parameter[] params1 = Parameter.EMPTY_ARRAY;
if (types.length > 0) {
params1 = new Parameter[types.length];
for (int i = 0; i < params1.length; i++) {
params1[i] = makeParameter(compileUnit, types[i], ctor.getParameterTypes()[i], getConstructorParameterAnnotations(ctor)[i], "param" + i);
}
}
Parameter[] params = params1;
ClassNode[] exceptions = makeClassNodes(compileUnit, ctor.getGenericExceptionTypes(), ctor.getExceptionTypes());
ConstructorNode cn = classNode.addConstructor(ctor.getModifiers(), params, exceptions, null);
setAnnotationMetaData(ctor.getAnnotations(), cn);
}
Class sc = clazz.getSuperclass();
if (sc != null) classNode.setUnresolvedSuperClass(makeClassNode(compileUnit, clazz.getGenericSuperclass(), sc));
makeInterfaceTypes(compileUnit, classNode, clazz);
setAnnotationMetaData(classNode.getTypeClass().getAnnotations(), classNode);
PackageNode packageNode = classNode.getPackage();
if (packageNode != null) {
setAnnotationMetaData(classNode.getTypeClass().getPackage().getAnnotations(), packageNode);
}
} catch (NoClassDefFoundError e) {
throw new NoClassDefFoundError("Unable to load class "+classNode.toString(false)+" due to missing dependency "+e.getMessage());
} catch (MalformedParameterizedTypeException e) {
throw new RuntimeException("Unable to configure class node for class "+classNode.toString(false)+" due to malformed parameterized types", e);
}
}
protected Parameter[] processParameters(CompileUnit compileUnit, Method m) {
Type[] types = m.getGenericParameterTypes();
Parameter[] params = Parameter.EMPTY_ARRAY;
if (types.length > 0) {
params = new Parameter[types.length];
for (int i = 0; i < params.length; i++) {
params[i] = makeParameter(compileUnit, types[i], m.getParameterTypes()[i], m.getParameterAnnotations()[i], "param" + i);
}
}
return params;
}
/**
* Synthetic parameters such as those added for inner class constructors may
* not be included in the parameter annotations array. This is the case when
* at least one parameter of an inner class constructor has an annotation with
* a RUNTIME retention (this occurs for JDK8 and below). This method will
* normalize the annotations array so that it contains the same number of
* elements as the array returned from {@link Constructor#getParameterTypes()}.
*
* If adjustment is required, the adjusted array will be prepended with a
* zero-length element. If no adjustment is required, the original array
* from {@link Constructor#getParameterAnnotations()} will be returned.
*
* @param constructor the Constructor for which to return parameter annotations
* @return array of arrays containing the annotations on the parameters of the given Constructor
*/
private Annotation[][] getConstructorParameterAnnotations(Constructor<?> constructor) {
/*
* TODO: Remove after JDK9 is the minimum JDK supported
*
* JDK9+ correctly accounts for the synthetic parameter and when it becomes
* the minimum version this method should no longer be required.
*/
int parameterCount = constructor.getParameterTypes().length;
Annotation[][] annotations = constructor.getParameterAnnotations();
int diff = parameterCount - annotations.length;
if (diff > 0) {
// May happen on JDK8 and below. We add elements to the front of the array to account for the synthetic params:
// - for an inner class we expect one param to account for the synthetic outer reference
// - for an enum we expect two params to account for the synthetic name and ordinal
if ((!constructor.getDeclaringClass().isEnum() && diff > 1) || diff > 2) {
throw new GroovyBugError(
"Constructor parameter annotations length [" + annotations.length + "] " +
"does not match the parameter length: " + constructor
);
}
Annotation[][] adjusted = new Annotation[parameterCount][];
for (int i = 0; i < diff; i++) {
adjusted[i] = EMPTY_ANNOTATION_ARRAY;
}
System.arraycopy(annotations, 0, adjusted, diff, annotations.length);
return adjusted;
}
return annotations;
}
private void makeInterfaceTypes(CompileUnit cu, ClassNode classNode, Class clazz) {
Type[] interfaceTypes = clazz.getGenericInterfaces();
if (interfaceTypes.length == 0) {
classNode.setInterfaces(ClassNode.EMPTY_ARRAY);
} else {
ClassNode[] ret = new ClassNode[interfaceTypes.length];
for (int i = 0; i < interfaceTypes.length; i++) {
Type type = interfaceTypes[i];
while (!(type instanceof Class)) {
ParameterizedType pt = (ParameterizedType) type;
Type t2 = pt.getRawType();
if (t2==type) {
throw new GroovyBugError("Cannot transform generic signature of "+clazz+
" with generic interface "+interfaceTypes[i]+" to a class.");
}
type = t2;
}
ret[i] = makeClassNode(cu, interfaceTypes[i], (Class) type);
}
classNode.setInterfaces(ret);
}
}
private ClassNode[] makeClassNodes(CompileUnit cu, Type[] types, Class[] cls) {
ClassNode[] nodes = new ClassNode[types.length];
for (int i = 0; i < nodes.length; i++) {
nodes[i] = makeClassNode(cu, types[i], cls[i]);
}
return nodes;
}
private ClassNode makeClassNode(CompileUnit cu, Type t, Class c) {
ClassNode back = null;
if (cu != null) back = cu.getClass(c.getName());
if (back == null) back = ClassHelper.make(c);
if (!(t instanceof Class)) {
ClassNode front = configureType(t);
front.setRedirect(back);
return front;
}
return back.getPlainNodeReference();
}
protected Parameter makeParameter(CompileUnit cu, Type type, Class cl, Annotation[] annotations, String name) {
ClassNode cn = makeClassNode(cu, type, cl);
Parameter parameter = new Parameter(cn, name);
setAnnotationMetaData(annotations, parameter);
return parameter;
}
public void invalidateCallSites() {}
@Override
public Object getInvokeSpecialHandle(Method m, Object receiver){
throw new GroovyBugError("getInvokeSpecialHandle requires at least JDK 7 wot private access to Lookup");
}
@Override
public int getVersion() {
return 5;
}
@Override
public Object invokeHandle(Object handle, Object[] args) throws Throwable {
throw new GroovyBugError("invokeHandle requires at least JDK 7");
}
/**
* The following scenarios can not set accessible, i.e. the return value is false
* 1) SecurityException occurred
* 2) the accessible object is a Constructor object for the Class class
*
* @param accessibleObject the accessible object to check
* @param callerClass the callerClass to invoke {@code setAccessible}
* @return the check result
*/
@Override
public boolean checkCanSetAccessible(AccessibleObject accessibleObject, Class<?> callerClass) {
SecurityManager sm = System.getSecurityManager();
try {
if (sm != null) {
sm.checkPermission(ACCESS_PERMISSION);
}
} catch (SecurityException e) {
return false;
}
if (accessibleObject instanceof Constructor) {
Constructor c = (Constructor) accessibleObject;
if (c.getDeclaringClass() == Class.class) {
return false; // Cannot make a java.lang.Class constructor accessible
}
}
return true;
}
@Override
public boolean checkAccessible(Class<?> callerClass, Class<?> declaringClass, int memberModifiers, boolean allowIllegalAccess) {
return true;
}
@Override
public boolean trySetAccessible(AccessibleObject ao) {
try {
ao.setAccessible(true);
return true;
} catch (SecurityException e) {
throw e;
} catch (Throwable t) {
return false;
}
}
@Override
public MetaMethod transformMetaMethod(MetaClass metaClass, MetaMethod metaMethod, Class<?> caller) {
return metaMethod;
}
@Override
public MetaMethod transformMetaMethod(MetaClass metaClass, MetaMethod metaMethod) {
return transformMetaMethod(metaClass, metaMethod, null);
}
private static final Permission ACCESS_PERMISSION = new ReflectPermission("suppressAccessChecks");
}