All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.groovy.vmplugin.v7.Selector Maven / Gradle / Ivy

There is a newer version: 3.0.21
Show newest version
/*
 *  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.v7;

import groovy.lang.AdaptingMetaClass;
import groovy.lang.Closure;
import groovy.lang.ExpandoMetaClass;
import groovy.lang.GroovyInterceptable;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassImpl;
import groovy.lang.MetaClassImpl.MetaConstructor;
import groovy.lang.MetaMethod;
import groovy.lang.MetaProperty;
import groovy.lang.MissingMethodException;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.reflection.CachedField;
import org.codehaus.groovy.reflection.CachedMethod;
import org.codehaus.groovy.reflection.ClassInfo;
import org.codehaus.groovy.reflection.GeneratedMetaMethod;
import org.codehaus.groovy.reflection.stdclasses.CachedSAMClass;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.GroovyCategorySupport;
import org.codehaus.groovy.runtime.GroovyCategorySupport.CategoryMethod;
import org.codehaus.groovy.runtime.NullObject;
import org.codehaus.groovy.runtime.dgmimpl.NumberNumberMetaMethod;
import org.codehaus.groovy.runtime.metaclass.ClosureMetaClass;
import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl;
import org.codehaus.groovy.runtime.metaclass.MethodMetaProperty;
import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod;
import org.codehaus.groovy.runtime.metaclass.NewStaticMetaMethod;
import org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod;
import org.codehaus.groovy.runtime.wrappers.Wrapper;
import org.codehaus.groovy.vmplugin.v7.IndyInterface.CALL_TYPES;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

import static org.codehaus.groovy.vmplugin.v7.IndyGuardsFiltersAndSignatures.*;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.LOG;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.LOG_ENABLED;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.LOOKUP;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.makeFallBack;
import static org.codehaus.groovy.vmplugin.v7.IndyInterface.switchPoint;

public abstract class Selector {
    public Object[] args, originalArguments;
    public MetaMethod method;
    public MethodType targetType,currentType;
    public String name;
    public MethodHandle handle;
    public boolean useMetaClass = false, cache = true;
    public MutableCallSite callSite;
    public Class sender;
    public boolean isVargs;
    public boolean safeNavigation, safeNavigationOrig, spread;
    public boolean skipSpreadCollector;
    public boolean thisCall;
    public Class selectionBase;
    public boolean catchException = true;
    public CALL_TYPES callType;

    /**
     * Returns the Selector
     */
    public static Selector getSelector(MutableCallSite callSite, Class sender, String methodName, int callID, boolean safeNavigation, boolean thisCall, boolean spreadCall, Object[] arguments) {
        CALL_TYPES callType = CALL_TYPES.values()[callID];
        switch (callType) {
            case INIT: return new InitSelector(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
            case METHOD: return new MethodSelector(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
            case GET: 
                return new PropertySelector(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
            case SET:
                throw new GroovyBugError("your call tried to do a property set, which is not supported.");
            case CAST:  return new CastSelector(callSite, arguments);
            default: throw new GroovyBugError("unexpected call type");
        }
    }
    abstract void setCallSiteTarget();

    /**
     * Helper method to transform the given arguments, consisting of the receiver 
     * and the actual arguments in an Object[], into a new Object[] consisting
     * of the receiver and the arguments directly. Before the size of args was 
     * always 2, the returned Object[] will have a size of 1+n, where n is the
     * number arguments.
     */
    private static Object[] spread(Object[] args, boolean spreadCall) {
        if (!spreadCall) return args;
        Object[] normalArguments = (Object[]) args[1];
        Object[] ret = new Object[normalArguments.length+1];
        ret[0] = args[0];
        System.arraycopy(normalArguments, 0, ret, 1, ret.length-1);
        return ret;
    }

    private static class CastSelector extends MethodSelector {
        private Class staticSourceType, staticTargetType;

        public CastSelector(MutableCallSite callSite, Object[] arguments) {
            super(callSite, Selector.class, "", CALL_TYPES.CAST, false, false, false, arguments);
            this.staticSourceType = callSite.type().parameterType(0);
            this.staticTargetType = callSite.type().returnType();
        }

        @Override
        public void setCallSiteTarget() {
            // targetTypes String, Enum and Class are handled 
            // by the compiler already
            // Boolean / boolean
            handleBoolean();
            handleNullWithoutBoolean();

            // !! from here on args[0] is always not null !!
            handleInstanceCase();

            // targetType is abstract Collection fitting for HashSet or ArrayList
            // and object is Collection or array
            handleCollections();
            handleSAM();

            // will handle :
            //      * collection case where argument is an array
            //      * array transformation (staticTargetType.isArray())
            //      * constructor invocation
            //      * final GroovyCastException
            castToTypeFallBack();

            if (!handle.type().equals(callSite.type())) castAndSetGuards();
        }

        private void castAndSetGuards() {
            handle =  MethodHandles.explicitCastArguments(handle,targetType);
            setGuards(args[0]);
            doCallSiteTargetSet();
        }

        private void handleNullWithoutBoolean() {
            if (handle!=null || args[0]!=null) return;

            if (staticTargetType.isPrimitive()) {
                handle = MethodHandles.insertArguments(GROOVY_CAST_EXCEPTION,1,staticTargetType);
                // need to call here here because we used the static target type
                // it won't be done otherwise because handle.type() == callSite.type()
                castAndSetGuards(); 
            } else {
               handle = MethodHandles.identity(staticSourceType);
            }
        }

        private void handleInstanceCase() {
            if (handle!=null) return;

            if (staticTargetType.isAssignableFrom(args[0].getClass())) {
                handle = MethodHandles.identity(staticSourceType);
            }
        }

        private static boolean isAbstractClassOf(Class toTest, Class givenOnCallSite) {
            if (!toTest.isAssignableFrom(givenOnCallSite)) return false;
            if (givenOnCallSite.isInterface()) return true;
            return Modifier.isAbstract(givenOnCallSite.getModifiers());
        }

        private void handleCollections() {
            if (handle!=null) return;

            if (!(args[0] instanceof Collection)) return;
            if (isAbstractClassOf(HashSet.class, staticTargetType)) {
                handle = HASHSET_CONSTRUCTOR;
            } else if (isAbstractClassOf(ArrayList.class, staticTargetType)) {
                handle = ARRAYLIST_CONSTRUCTOR;
            }
        }

        private void handleSAM() {
            if (handle!=null) return;

            if (!(args[0] instanceof Closure)) return;
            Method m = CachedSAMClass.getSAMMethod(staticTargetType);
            if (m==null) return;
            //TODO: optimize: add guard based on type Closure
            handle = MethodHandles.insertArguments(SAM_CONVERSION, 1, m, staticTargetType, staticTargetType.isInterface());
        }

        private void castToTypeFallBack() {
            if (handle!=null) return;

            // generic fallback to castToType
            handle = MethodHandles.insertArguments(DTT_CAST_TO_TYPE, 1, staticTargetType);
        }

        private void handleBoolean() {
            if (handle!=null) return;

            // boolean->boolean, Boolean->boolean, boolean->Boolean
            // is handled by compiler
            // that leaves (T)Z and (T)Boolean, where T is the static type
            // but runtime type of T might be Boolean

            boolean primitive = staticTargetType==boolean.class;
            if (!primitive && staticTargetType!=Boolean.class) return;
            if (args[0]==null) {
                if (primitive) {
                    handle = MethodHandles.constant(boolean.class, false);
                    handle = MethodHandles.dropArguments(handle, 0, staticSourceType);
                } else {
                    handle = BOOLEAN_IDENTITY;
                }
            } else if (args[0] instanceof Boolean) {
                // give value through or unbox
                handle = BOOLEAN_IDENTITY;
            } else { 
                //call asBoolean
                name = "asBoolean";
                super.setCallSiteTarget();
                return;
            }
        }
    }

    private static class PropertySelector extends MethodSelector {
        private boolean insertName = false;

        public PropertySelector(MutableCallSite callSite, Class sender, String methodName, CALL_TYPES callType, boolean safeNavigation, boolean thisCall, boolean spreadCall, Object[] arguments) {
            super(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
        }

        /**
         * We never got the interceptor path with a property get
         */
        @Override
        public boolean setInterceptor() {
            return false;
        }

        /**
         * this method chooses a property from the meta class.
         */
        @Override
        public void chooseMeta(MetaClassImpl mci) {
            Object receiver = getCorrectedReceiver();
            if (receiver instanceof GroovyObject) {
                Class aClass = receiver.getClass();
                Method reflectionMethod = null;
                try {
                    reflectionMethod = aClass.getMethod("getProperty", String.class);
                    if (!reflectionMethod.isSynthetic()) {
                        handle = MethodHandles.insertArguments(GROOVY_OBJECT_GET_PROPERTY, 1, name);
                        return;
                    }
                } catch (ReflectiveOperationException e)  {}
            } else if (receiver instanceof Class) {
                handle = MOP_GET;
                handle = MethodHandles.insertArguments(handle, 2, name);
                handle = MethodHandles.insertArguments(handle, 0, this.mc);
                return;
            }

            if (method!=null || mci==null) return;
            Class chosenSender = this.sender;
            if (mci.getTheClass()!= chosenSender && GroovyCategorySupport.hasCategoryInCurrentThread()) {
                chosenSender = mci.getTheClass();
            }
            MetaProperty res = mci.getEffectiveGetMetaProperty(chosenSender, receiver, name, false);
            if (res instanceof MethodMetaProperty) {
                MethodMetaProperty mmp = (MethodMetaProperty) res;
                method = mmp.getMetaMethod();
                insertName = true;
            } else if (res instanceof CachedField) {
                CachedField cf = (CachedField) res;
                Field f = cf.field;
                try {
                    handle = LOOKUP.unreflectGetter(f);
                    if (Modifier.isStatic(f.getModifiers())) {
                        // normally we would do the following
                        // handle = MethodHandles.dropArguments(handle,0,Class.class);
                        // but because there is a bug in invokedynamic in all jdk7 versions 
                        // maybe use Unsafe.ensureClassInitialized
                        handle = META_PROPERTY_GETTER.bindTo(res);
                    }
                } catch (IllegalAccessException iae) {
                    throw new GroovyBugError(iae);
                }
            } else {
                handle = META_PROPERTY_GETTER.bindTo(res);
            } 
        }

        /**
         * Additionally to the normal {@link MethodSelector#setHandleForMetaMethod()}
         * task we have to also take care of generic getter methods, that depend
         * one the name.
         */
        @Override
        public void setHandleForMetaMethod() {
            if (handle!=null) return;
            super.setHandleForMetaMethod();
            if (handle != null && insertName && handle.type().parameterCount()==2) {
                handle = MethodHandles.insertArguments(handle, 1, name);
            }
        }

        /**
         * The MOP requires all get property operations to go through 
         * {@link GroovyObject#getProperty(String)}. We do this in case 
         * no property was found before.
         */
        @Override
        public void setMetaClassCallHandleIfNedded(boolean standardMetaClass) {
            if (handle!=null) return;
            useMetaClass = true;
            if (LOG_ENABLED) LOG.info("set meta class invocation path for property get.");
            handle = MethodHandles.insertArguments(MOP_GET, 2, this.name);
            handle = MethodHandles.insertArguments(handle, 0, mc);
        }
    }

    private static class InitSelector extends MethodSelector {
        private boolean beanConstructor;

        public InitSelector(MutableCallSite callSite, Class sender, String methodName, CALL_TYPES callType, boolean safeNavigation, boolean thisCall, boolean spreadCall, Object[] arguments) {
            super(callSite, sender, methodName, callType, safeNavigation, thisCall, spreadCall, arguments);
        }

        /**
         * Constructor calls are not intercepted, thus always returns false.
         */
        @Override
        public boolean setInterceptor() {
            return false;
        }

        /**
         * For a constructor call we always use the static meta class from the registry
         */
        @Override
        public void getMetaClass() {
            Object receiver = args[0];
            mc = GroovySystem.getMetaClassRegistry().getMetaClass((Class) receiver);
        }

        /**
         * This method chooses a constructor from the meta class.
         */
        @Override
        public void chooseMeta(MetaClassImpl mci) {
            if (mci==null) return;
            if (LOG_ENABLED) LOG.info("getting constructor");
            Object[] newArgs = removeRealReceiver(args);
            method = mci.retrieveConstructor(newArgs);
            if (method instanceof MetaConstructor) {
                MetaConstructor mcon = (MetaConstructor) method;
                if (mcon.isBeanConstructor()) {
                    if (LOG_ENABLED) LOG.info("do beans constructor");
                    beanConstructor = true;
                }
            }
        }

        /**
         * Adds {@link MetaConstructor} handling.
         */
        @Override
        public void setHandleForMetaMethod() {
            if (method==null) return;
            if (method instanceof MetaConstructor) {
                if (LOG_ENABLED) LOG.info("meta method is MetaConstructor instance");
                MetaConstructor mc = (MetaConstructor) method;
                isVargs = mc.isVargsMethod();
                Constructor con = mc.getCachedConstrcutor().cachedConstructor;
                try {
                    handle = LOOKUP.unreflectConstructor(con);
                    if (LOG_ENABLED) LOG.info("successfully unreflected constructor");
                } catch (IllegalAccessException e) {
                    throw new GroovyBugError(e);
                }
            } else {
                super.setHandleForMetaMethod();
            }
            if (beanConstructor) {
                // we have handle that takes no arguments to create the bean, 
                // we have to use its return value to call #setBeanProperties with it
                // and the meta class.

                // to do this we first bind the values to #setBeanProperties
                MethodHandle con = BEAN_CONSTRUCTOR_PROPERTY_SETTER.bindTo(mc);
                // inner class case
                MethodType foldTargetType = MethodType.methodType(Object.class);
                if (args.length==3) {
                    con = MethodHandles.dropArguments(con, 1, targetType.parameterType(1));
                    foldTargetType = foldTargetType.insertParameterTypes(0, targetType.parameterType(1));
                }
                handle = MethodHandles.foldArguments(con, handle.asType(foldTargetType));
            }
            if (method instanceof MetaConstructor) {
                handle = MethodHandles.dropArguments(handle, 0, Class.class);
            }
        }

        /**
         * In case of a bean constructor we don't do any varags or implicit null argument 
         * transformations. Otherwise we do the same as for {@link MethodSelector#correctParameterLength()}
         */
        @Override
        public void correctParameterLength() {
            if (beanConstructor) return;
            super.correctParameterLength();
        }

        /**
         * In case of a bean constructor we don't do any coercion, otherwise
         * we do the same as for {@link MethodSelector#correctCoerce()}
         */
        @Override
        public void correctCoerce() {
            if (beanConstructor) return;
            super.correctCoerce();
        }

        /**
         * Set MOP based constructor invocation path.
         */
        @Override
        public void setMetaClassCallHandleIfNedded(boolean standardMetaClass) {
            if (handle!=null) return;
            useMetaClass = true;
            if (LOG_ENABLED) LOG.info("set meta class invocation path");
            handle = MOP_INVOKE_CONSTRUCTOR.bindTo(mc);
            handle = handle.asCollector(Object[].class, targetType.parameterCount()-1);
            handle = MethodHandles.dropArguments(handle, 0, Class.class);
            if (LOG_ENABLED) LOG.info("create collector for arguments");
        }
    }

    /**
     * Method invocation based {@link Selector}.
     * This Selector is called for method invocations and is base for cosntructor
     * calls as well as getProperty calls.
     * @author Jochen "blackdrag" Theodorou
     */
    private static class MethodSelector extends Selector {
        protected MetaClass mc;
        private boolean isCategoryMethod;
        public MethodSelector(MutableCallSite callSite, Class sender, String methodName, CALL_TYPES callType, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object[] arguments) {
            this.callType = callType;
            this.targetType = callSite.type();
            this.name = methodName;
            this.originalArguments = arguments;
            this.args = spread(arguments, spreadCall);
            this.callSite = callSite;
            this.sender = sender;
            this.safeNavigationOrig = safeNavigation;
            this.safeNavigation = safeNavigation && arguments[0]==null;
            this.thisCall = thisCall;
            this.spread = spreadCall;
            this.cache = !spread;

            if (LOG_ENABLED) {
                String msg =
                    "----------------------------------------------------"+
                    "\n\t\tinvocation of method '"+methodName+"'"+
                    "\n\t\tinvocation type: "+callType+
                    "\n\t\tsender: "+sender+
                    "\n\t\ttargetType: "+targetType+
                    "\n\t\tsafe navigation: "+safeNavigation+
                    "\n\t\tthisCall: "+thisCall+
                    "\n\t\tspreadCall: "+spreadCall+
                    "\n\t\twith "+arguments.length+" arguments";
                for (int i=0; i args.length) {
                // we depend on the method selection having done a good 
                // job before already, so the only case for this here is, that
                // we have no argument for the array, meaning params.length is
                // args.length+1. In that case we have to fill in an empty array
                handle = MethodHandles.insertArguments(handle, params.length-1, Array.newInstance(lastParam.getComponentType(), 0));
                if (LOG_ENABLED) LOG.info("added empty array for missing vargs part");
            } else { //params.length < args.length
                // we depend on the method selection having done a good 
                // job before already, so the only case for this here is, that
                // all trailing arguments belong into the vargs array
                handle = handle.asCollector(
                        lastParam,
                        args.length - params.length + 1);
                if (LOG_ENABLED) LOG.info("changed surplus arguments to be collected for vargs call");
            }
        }

        /**
         * There are some conversions we have to do explicitly.
         * These are GString to String, Number to Byte and Number to BigInteger
         * conversions.
         */
        public void correctCoerce() {
            if (useMetaClass) return;

            Class[] parameters = handle.type().parameterArray();
            if (currentType!=null) parameters = currentType.parameterArray();
            if (args.length != parameters.length) {
                throw new GroovyBugError("At this point argument array length and parameter array length should be the same");
            }
            for (int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy