/*
   Copyright 2009-2011 Attila Szegedi

   Licensed 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.dynalang.dynalink.support;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

/**
 * Utility methods for creating typical guards. TODO: introduce reasonable
 * caching of created guards.
 *
 * @author Attila Szegedi
 * @version $Id: $
 */
public class Guards {
    /**
     * Creates a guard method handle with arguments of a specified type, but
     * with boolean return value. When invoked, it returns true if the first
     * argument is of the specified class (exactly of it, not a subclass). The
     * rest of the arguments will be ignored.
     *
     * @param clazz the class of the first argument to test for
     * @param type the method type
     * @return a method handle testing whether its first argument is of the
     * specified class.
     */
    public static MethodHandle isOfClass(Class<?> clazz, MethodType type) {
        return getMatchedHandle(IS_OF_CLASS, clazz, 0, type);
    }

    /**
     * Creates a method handle with arguments of a specified type, but with
     * boolean return value. When invoked, it returns true if the first argument
     * is instance of the specified class or its subclass). The rest of the
     * arguments will be ignored.
     *
     * @param clazz the class of the first argument to test for
     * @param type the method type
     * @return a method handle testing whether its first argument is of the
     * specified class or subclass.
     */
    public static MethodHandle isInstance(Class<?> clazz, MethodType type) {
        return isInstance(clazz, 0, type);
    }

    /**
     * Creates a method handle with arguments of a specified type, but with
     * boolean return value. When invoked, it returns true if the n'th argument
     * is instance of the specified class or its subclass). The rest of the
     * arguments will be ignored.
     *
     * @param clazz the class of the first argument to test for
     * @param pos the position on the argument list to test
     * @param type the method type
     * @return a method handle testing whether its first argument is of the
     * specified class or subclass.
     */
    public static MethodHandle isInstance(Class<?> clazz, int pos,
            MethodType type) {
        return getMatchedHandle(IS_INSTANCE, clazz, pos, type);
    }

    /**
     * Creates a method handle that returns true if the argument in the
     * specified position is a Java array.
     *
     * @param pos the position in the argument lit
     * @param type the method type of the handle
     * @return a method handle that returns true if the argument in the
     * specified position is a Java array; the rest of the arguments are
     * ignored.
     */
    public static MethodHandle isArray(int pos, MethodType type) {
        return MethodHandles.permuteArguments(IS_ARRAY, type
                .changeReturnType(Boolean.TYPE), new int[] { pos });
    }

    /**
     * Return true if it is safe to strongly reference a class from the referred
     * class loader from a class associated with the referring class loader
     * without risking a class loader memory leak.
     *
     * @param referrerLoader the referrer class loader
     * @param referredLoader the referred class loader
     * @return true if it is safe to strongly reference the class
     */
    public static boolean canReferenceDirectly(ClassLoader referrerLoader,
            final ClassLoader referredLoader) {
        if(referredLoader == null) {
            // Can always refer directly to a system class
            return true;
        }
        if(referrerLoader == null) {
            // System classes can't refer directly to any non-system class
            return false;
        }
        // Otherwise, can only refer directly to classes residing in same or
        // parent class loader.
        do {
            if(referrerLoader == referredLoader) {
                return true;
            }
            referrerLoader = referrerLoader.getParent();
        } while(referrerLoader != null);
        return false;
    }

    private static MethodHandle getMatchedHandle(MethodHandle test,
            Object classOrRef, int pos, MethodType type) {
        if(type.parameterCount() < 1) {
            throw new IllegalArgumentException(
                    "method type must specify at least one argument");
        }
        final MethodHandle boundToClass =
                MethodHandles.insertArguments(test, 0, classOrRef);
        final MethodHandle narrowed =
                MethodHandles.permuteArguments(boundToClass, type
                        .changeReturnType(Boolean.TYPE), new int[] { pos });
        return narrowed;
    }

    private static final MethodHandle IS_OF_CLASS =
            Lookup.PUBLIC.findStatic(Guards.class, "_isOfClass", MethodType
                    .methodType(Boolean.TYPE, Class.class, Object.class));

    private static final MethodHandle IS_INSTANCE =
            Lookup.PUBLIC.findVirtual(Class.class, "isInstance", MethodType
                    .methodType(Boolean.TYPE, Object.class));

    private static final MethodHandle IS_ARRAY =
            Lookup.PUBLIC.findStatic(Guards.class, "_isArray", MethodType
                    .methodType(Boolean.TYPE, Object.class));

    /**
     * This method is public for implementation reasons. Do not invoke it
     * directly. Determines whether the passed object is a Java array.
     *
     * @param o an object
     * @return true if o is an instance of a Java array.
     */
    public static boolean _isArray(Object o) {
        return o != null && o.getClass().isArray();
    }

    /**
     * This method public for implementation reasons. Do not invoke directly.
     * Determines whether the class of the object is what's expected to be.
     *
     * @param c the class
     * @param o the object
     * @return true if the object is exactly of the class, false otherwise.
     */
    public static boolean _isOfClass(Class<?> c, Object o) {
        return o != null && o.getClass() == c;
    }
}
@@ -5320,22 +5320,119 @@
 dToClass
-, type
+.asType(%0D%0A                        boundToClass.type().changeParameterType(0, type.parameterType(pos))),
 %0D%0A      
@@ -5441,32 +5441,36 @@
                 
+type
 .changeReturnTyp

Make sure getMatchedHandle can satisfy parameters other than Object in target types.