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

lt.runtime.Dynamic Maven / Gradle / Ivy

Go to download

The latte-lang compiler project, which contains compiler and runtime required library.

The newest version!
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 KuiGang Wang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package lt.runtime;

import lt.compiler.LtBug;
import lt.lang.FunctionalAbstractClass;
import lt.lang.FunctionalInterface;
import lt.lang.Unit;
import lt.lang.function.Function;
import lt.lang.function.Function1;

import java.lang.reflect.*;
import java.util.*;
import java.util.List;
import java.util.Map;

/**
 * invoke dynamic
 */
public class Dynamic {
        private static final int PRIMITIVE_BOX_CAST_BASE = 233;
        private static final int COLLECTION_OBJECT_CAST_BASE = 2333;

        /**
         * check cast when parameter and argument are both primitive
         * the primitive argument would be cast to box type when handled
         * so add box types into list
         */
        private static final Map, Set>> primitiveCast = new HashMap, Set>>() {{
                final Map, Set>> self = this;
                put(int.class, new HashSet>() {{
                        add(Byte.class);
                        add(Short.class);
                        add(Character.class);
                        add(Integer.class);
                }});
                put(long.class, new HashSet>() {{
                        addAll(self.get(int.class));
                        add(Long.class);
                }});
                put(float.class, new HashSet>() {{
                        addAll(self.get(long.class));
                        add(Float.class);
                }});
                put(double.class, new HashSet>() {{
                        addAll(self.get(long.class));
                        addAll(self.get(float.class));
                        add(Double.class);
                }});
                put(byte.class, new HashSet>() {{
                        add(Byte.class);
                }});
                put(short.class, new HashSet>() {{
                        add(Short.class);
                }});
                put(char.class, new HashSet>() {{
                        add(Character.class);
                }});
                put(boolean.class, new HashSet>() {{
                        add(Boolean.class);
                }});
        }};

        private Dynamic() {
        }

        /**
         * check whether the parameters' declaring type can be candidate of invoking by these arguments.
         *
         * @param params     parameters
         * @param args       arguments
         * @param primitives whether the arg is a primitive
         * @return true or false
         */
        private static boolean canBeCandidate(Class[] params, Object[] args, boolean[] primitives) {
                for (int i = 0; i < args.length; ++i) {
                        if (!params[i].isInstance(args[i]) &&
                                (
                                        // null can be assigned to any reference type
                                        params[i].isPrimitive()
                                                ||
                                                args[i] != null
                                )) {
                                // is not instance
                                // check primitive
                                Class cls = params[i];

                                Object obj = args[i];
                                if (null == obj) continue;

                                if (cls.isPrimitive() && primitives[i]) {
                                        if (!primitiveCast.get(cls).contains(obj.getClass())) {
                                                return false;
                                        }
                                        continue;
                                }
                                if (cls.equals(int.class)) {
                                        if (!(obj instanceof Integer)) return false;
                                } else if (cls.equals(short.class)) {
                                        if (!(obj instanceof Short)) return false;
                                } else if (cls.equals(byte.class)) {
                                        if (!(obj instanceof Byte)) return false;
                                } else if (cls.equals(boolean.class)) {
                                        if (!(obj instanceof Boolean)) return false;
                                } else if (cls.equals(char.class)) {
                                        if (!(obj instanceof Character)) return false;
                                } else if (cls.equals(long.class)) {
                                        if (!(obj instanceof Long)) return false;
                                } else if (cls.equals(double.class)) {
                                        if (!(obj instanceof Double)) return false;
                                } else if (cls.equals(float.class)) {
                                        if (!(obj instanceof Float)) return false;
                                } else if (cls.isArray()) {
                                        if (!(obj instanceof java.util.List)) return false;
                                } else if (!cls.isArray() && !cls.isInterface() && !cls.isAnonymousClass()
                                        && !cls.isAnnotation() && !cls.isEnum() && !cls.isLocalClass()
                                        && !cls.isMemberClass() && !cls.isPrimitive() && !cls.isSynthetic()
                                        && (obj instanceof java.util.Map || obj instanceof java.util.List)) {
                                        // obj is map
                                        // and cast to a java object
                                        Constructor con;
                                        try {
                                                con = cls.getConstructor();
                                        } catch (Exception e) {
                                                // constructor without parameter
                                                return false;
                                        }
                                        // constructor modifier public
                                        if (!Modifier.isPublic(con.getModifiers())) return false;

                                        if (obj instanceof Map) {
                                                // each key is string
                                                Map map = (Map) obj;
                                                for (Object key : map.keySet()) {
                                                        if (!(key instanceof String)) {
                                                                return false;
                                                        }
                                                }
                                        }

                                        return true;

                                } else if (cls.isInterface() && isFunctionalInterface(cls)) {
                                        if (!(obj instanceof Function)) return false;
                                } else if (!cls.isAnnotation() && !cls.isAnonymousClass() && !cls.isArray() &&
                                        !cls.isEnum() && !cls.isLocalClass() && !cls.isMemberClass()
                                        && !cls.isPrimitive() && !cls.isSynthetic() &&
                                        isFunctionalAbstractClass(cls)) {
                                        if (!(obj instanceof Function)) return false;
                                } else {
                                        notFunctionalAbstractClass.put(cls, null);
                                        notFunctionalInterfaces.put(cls, null);
                                        return false;
                                }
                        }
                }
                return true;
        }

        private static Class chooseType(Class targetType, Object target) {
                if (target == null) return targetType;
                if (targetType.isAnnotationPresent(Implicit.class)) return targetType;
                if (targetType.isInstance(target)) return target.getClass();
                return targetType;
        }

        public static Method findMethod(Class invoker, Class targetType, Object target, String method, boolean[] primitives, Object[] args) throws Throwable {
                if (primitives.length != args.length) throw new LtBug("primitives.length should equal to args.length");
                List methodList = new ArrayList();

                Queue> interfaces = new ArrayDeque>();
                Class c = chooseType(targetType, target);
                while (c != null) {
                        Collections.addAll(interfaces, c.getInterfaces());
                        fillMethodCandidates(c, invoker, method, primitives, args, methodList, target == null);
                        c = c.getSuperclass();
                }
                c = chooseType(targetType, target);

                Collections.addAll(interfaces, c.getInterfaces());
                while (!interfaces.isEmpty()) {
                        Class i = interfaces.remove();
                        fillMethodCandidates(i, invoker, method, primitives, args, methodList, target == null);
                        Collections.addAll(interfaces, i.getInterfaces());
                }

                if (methodList.isEmpty()) {
                        return null;
                }

                // find best match
                Method methodToInvoke = findBestMatch(methodList, args, primitives);
                // trans to required type
                transToRequiredType(args, methodToInvoke.getParameterTypes());

                return methodToInvoke;
        }

        /**
         * fill in method candidates
         *
         * @param c          class
         * @param invoker    invoker
         * @param method     method
         * @param args       arguments
         * @param methodList method list (fill into this list)
         * @param onlyStatic only find static methods
         */
        private static void fillMethodCandidates(Class c,
                                                 Class invoker,
                                                 String method,
                                                 boolean[] primitives,
                                                 Object[] args,
                                                 List methodList,
                                                 boolean onlyStatic) {
                for (Method m : c.getDeclaredMethods()) {
                        if (!m.getName().equals(method)) continue;
                        if (m.getParameterTypes().length != args.length) continue;

                        // access check
                        if (!LtRuntime.haveAccess(m.getModifiers(), c, invoker)) continue;

                        if (onlyStatic) {
                                if (!Modifier.isStatic(m.getModifiers())) continue;
                        }

                        // else public
                        // do nothing

                        if (canBeCandidate(m.getParameterTypes(), args, primitives)) {
                                methodList.add(m);
                        }
                }
        }

        /**
         * overridden methods
         * (superClass method) => (subClass method)
         */
        private static Map> overriddenMethods = new WeakHashMap>();
        /**
         * classes already done override analysis
         */
        private static Map, Object> classesDoneOverrideAnalysis = new WeakHashMap, Object>();
        /**
         * functional abstract classes
         */
        private static Map, Object> functionalAbstractClasses = new WeakHashMap, Object>();
        /**
         * not functional abstract classes
         */
        private static Map, Object> notFunctionalAbstractClass = new WeakHashMap, Object>();
        /**
         * functional interfaces
         */
        private static Map, Object> functionalInterfaces = new WeakHashMap, Object>();
        /**
         * not functional interfaces
         */
        private static Map, Object> notFunctionalInterfaces = new WeakHashMap, Object>();
        /**
         * abstract method of a functional interface/abstract class
         */
        private static Map, Method> abstractMethod = new WeakHashMap, Method>();

        /**
         * check signature, whether they are the same.
         *
         * @param subM    the method in sub class
         * @param parentM the method in super class
         * @return true or false
         */
        private static boolean signaturesAreTheSame(Method subM, Method parentM) {
                String name = parentM.getName();

                if (subM.getName().equals(name)) {
                        if (subM.getParameterTypes().length == parentM.getParameterTypes().length) {
                                for (int i = 0; i < subM.getParameterTypes().length; ++i) {
                                        Class subP = subM.getParameterTypes()[i];
                                        Class parentP = parentM.getParameterTypes()[i];
                                        if (!subP.equals(parentP)) return false;
                                }
                        }
                        // parentM is overridden by subM
                        Set set;
                        if (overriddenMethods.containsKey(parentM)) {
                                set = overriddenMethods.get(parentM);
                        } else {
                                set = new HashSet();
                                overriddenMethods.put(parentM, set);
                        }
                        set.add(subM);
                        return true;
                }

                return false;
        }

        /**
         * analyse the override relation of the methods in the class/interface
         *
         * @param c class object
         */
        private static void analyseClassOverride(Class c) {
                if (classesDoneOverrideAnalysis.containsKey(c)) return;

                if (!c.isInterface()) {
                        // classes should check super classes
                        // interfaces don't have super classes (except java.lang.Object)

                        // find overridden abstract methods
                        Class parent = c.getSuperclass();

                        if (parent != null) {

                                for (Method parentM : parent.getDeclaredMethods()) {
                                        for (Method subM : c.getDeclaredMethods()) {
                                                if (signaturesAreTheSame(subM, parentM)) break;
                                        }
                                }

                                analyseClassOverride(parent);
                        }
                }

                // check interfaces
                for (Class i : c.getInterfaces()) {
                        for (Method iM : i.getDeclaredMethods()) {
                                for (Method cM : c.getDeclaredMethods()) {
                                        if (signaturesAreTheSame(cM, iM)) break;
                                }
                        }

                        analyseClassOverride(i);
                }

                classesDoneOverrideAnalysis.put(c, null);
        }

        /**
         * check whether the method is overridden in the sub class
         *
         * @param parentM method in parent class
         * @param sub     sub class
         * @return true or false
         */
        private static boolean isOverriddenInClass(Method parentM, Class sub) {
                Set methods = overriddenMethods.get(parentM);
                if (methods == null) return false;
                for (Method m : methods) {
                        if (m.getDeclaringClass().equals(sub)) return true;
                        if (isOverriddenInClass(m, sub)) return true;
                }
                return false;
        }

        /**
         * find one abstract method in the class
         *
         * @param c the class to retrieve method from
         * @return the retrieved abstract method
         * @throws LtRuntimeException no abstract method found
         */
        public static Method findAbstractMethod(Class c) {
                if (abstractMethod.containsKey(c)) return abstractMethod.get(c);

                // find in current class
                for (Method m : c.getDeclaredMethods()) {
                        if (Modifier.isAbstract(m.getModifiers())) {
                                abstractMethod.put(c, m);
                                return m;
                        }
                }

                if (!c.isInterface()) {
                        // check super class
                        Class tmp = c.getSuperclass();
                        while (tmp != null) {
                                if (!Modifier.isAbstract(tmp.getModifiers())) break;

                                for (Method method : tmp.getDeclaredMethods()) {
                                        if (Modifier.isAbstract(method.getModifiers())) {
                                                if (isOverriddenInClass(method, c)) continue;

                                                abstractMethod.put(c, method);
                                                return method;
                                        }
                                }
                                tmp = tmp.getSuperclass();
                        }
                }

                // check interfaces

                Set> visited = new HashSet>();
                Queue> interfaces = new ArrayDeque>();

                Collections.addAll(interfaces);

                while (!interfaces.isEmpty()) {
                        Class ii = interfaces.remove();
                        if (visited.contains(ii)) continue;
                        for (Method m : ii.getDeclaredMethods()) {
                                if (Modifier.isAbstract(m.getModifiers())) {
                                        if (isOverriddenInClass(m, c)) continue;

                                        abstractMethod.put(c, m);
                                        return m;
                                }
                        }

                        visited.add(ii);
                        Collections.addAll(interfaces, ii.getInterfaces());
                }

                throw new LtRuntimeException("cannot find abstract method in " + c);
        }

        /**
         * check whether the interface is a functional interface
         *
         * @param i the interface to be checked
         * @return true/false
         */
        public static boolean isFunctionalInterface(Class i) {
                if (i.isAnnotationPresent(FunctionalInterface.class)) return true;

                if (functionalInterfaces.containsKey(i)) return true;
                if (notFunctionalInterfaces.containsKey(i)) return false;

                analyseClassOverride(i);

                Set> visited = new HashSet>();

                boolean found = false;
                Queue> interfaces = new ArrayDeque>();
                interfaces.add(i);

                while (!interfaces.isEmpty()) {
                        Class ii = interfaces.remove();
                        if (visited.contains(ii)) continue;
                        for (Method m : ii.getDeclaredMethods()) {
                                if (Modifier.isAbstract(m.getModifiers())) {
                                        if (isOverriddenInClass(m, i)) continue;

                                        if (found) return false;
                                        found = true;
                                }
                        }

                        visited.add(ii);
                        Collections.addAll(interfaces, ii.getInterfaces());
                }

                if (found)
                        functionalInterfaces.put(i, null);
                return found;
        }

        /**
         * check whether the class is a functional abstract class
         *
         * @param c the class to be checked
         * @return true/false
         */
        public static boolean isFunctionalAbstractClass(Class c) {
                if (!Modifier.isAbstract(c.getModifiers())) return false;

                if (c.isAnnotationPresent(FunctionalAbstractClass.class)) return true;

                if (functionalAbstractClasses.containsKey(c)) return true;
                if (notFunctionalAbstractClass.containsKey(c)) return false;

                Constructor[] cons = c.getDeclaredConstructors();
                boolean containsPublicZeroParamConstructor = false;
                for (Constructor con : cons) {
                        if (Modifier.isPublic(con.getModifiers())) {
                                if (con.getParameterTypes().length == 0) {
                                        containsPublicZeroParamConstructor = true;
                                        break;
                                }
                        }
                }

                if (!containsPublicZeroParamConstructor) return false;

                analyseClassOverride(c);

                Set> visited = new HashSet>();

                boolean found = false;

                Class tmpCls = c;
                while (tmpCls != null) {
                        for (Method m : tmpCls.getDeclaredMethods()) {
                                if (Modifier.isAbstract(m.getModifiers())) {
                                        if (isOverriddenInClass(m, c)) continue;

                                        if (found) return false;
                                        found = true;
                                }
                        }

                        visited.add(tmpCls);
                        tmpCls = tmpCls.getSuperclass();
                }

                Queue> interfaces = new ArrayDeque>();
                Collections.addAll(interfaces);

                while (!interfaces.isEmpty()) {
                        Class ii = interfaces.remove();
                        if (visited.contains(ii)) continue;
                        for (Method m : ii.getDeclaredMethods()) {
                                if (Modifier.isAbstract(m.getModifiers())) {
                                        if (isOverriddenInClass(m, c)) continue;

                                        if (found) return false;
                                        found = true;
                                }
                        }

                        visited.add(ii);
                        Collections.addAll(interfaces, ii.getInterfaces());
                }

                if (found)
                        functionalAbstractClasses.put(c, null);
                return found;
        }

        /**
         * bfs search the required type
         *
         * @param current  current
         * @param required required
         * @return cast steps - 0 means no cast
         */
        private static int bfsSearch(Class current, Class required) {
                if (current.isArray() && required.isArray()) {
                        return bfsSearch(current.getComponentType(), required.getComponentType());
                }
                Set> visited = new HashSet>();
                Queue> queue = new ArrayDeque>();
                List> ready = new LinkedList>();
                queue.add(current);
                visited.add(current);
                int count = 0;
                while (!queue.isEmpty() || !ready.isEmpty()) {
                        if (queue.isEmpty()) {
                                queue.addAll(ready);
                                ready.clear();
                                ++count;
                        }

                        Class c = queue.remove();
                        if (c.equals(required)) return count;
                        // fill in super
                        if (c.getSuperclass() != null
                                &&
                                !visited.contains(c.getSuperclass())) {

                                ready.add(c.getSuperclass());
                                visited.add(c.getSuperclass());
                        }
                        for (Class i : c.getInterfaces()) {
                                if (!visited.contains(i)) {
                                        ready.add(i);
                                        visited.add(i);
                                }
                        }
                }
                throw new LtBug(required + " is not assignable from " + current);
        }

        /**
         * transform the argument into required types. the transformed object will replace elements in the argument array.
         *
         * @param args   arguments
         * @param params required types
         * @throws Exception exception
         */
        private static void transToRequiredType(Object[] args, Class[] params) throws Throwable {
                for (int i = 0; i < params.length; ++i) {
                        Class c = params[i];
                        if (c.isPrimitive()) continue;
                        Object o = args[i];

                        if (o == null || c.isInstance(o)) continue;

                        args[i] = LtRuntime.cast(o, c, null);
                }
        }

        /**
         * get number type primitives cast depth
         *
         * @param from from type
         * @param to   to type
         * @return depth
         */
        private static int getNumberPrimitiveCastDepth(Class from, Class to) {
                if (from == Integer.class) from = int.class;
                if (from == Float.class) from = float.class;
                if (from == Long.class) from = long.class;
                if (from == Double.class) from = double.class;
                if (from == Short.class) from = short.class;
                if (from == Byte.class) from = byte.class;
                if (from == Character.class) from = char.class;

                if (from == to) return 0;
                if (from == byte.class || from == short.class || from == char.class) {
                        if (to == int.class) {
                                return 1;
                        } else if (to == long.class) {
                                return 2;
                        } else if (to == float.class) {
                                return 3;
                        } else if (to == double.class) {
                                return 4;
                        } else throw new LtBug("should not reach here, from: " + from + " to: " + to);
                } else if (from == int.class) {
                        if (to == long.class) {
                                return 1;
                        } else if (to == float.class) {
                                return 2;
                        } else if (to == double.class) {
                                return 3;
                        } else throw new LtBug("should not reach here, from: " + from + " to: " + to);
                } else if (from == long.class) {
                        if (to == float.class) {
                                return 1;
                        } else if (to == double.class) {
                                return 2;
                        } else throw new LtBug("should not reach here, from: " + from + " to: " + to);
                } else if (from == float.class) {
                        if (to == double.class) {
                                return 1;
                        } else throw new LtBug("should not reach here, from: " + from + " to: " + to);
                } else throw new LtBug("should not reach here, from: " + from);
        }

        /**
         * find best match.
         *
         * @param methodList method list (constructors or methods)
         * @param args       arguments
         * @param primitives whether the argument is a primitive
         * @param         {@link Constructor} or {@link Method}
         * @return the found method
         */
        private static  T findBestMatch(List methodList, Object[] args, boolean[] primitives) {
                // calculate every method's cast steps
                Map steps = new HashMap();
                for (T m : methodList) {
                        int[] step = new int[args.length];
                        for (int i = 0; i < args.length; ++i) {
                                Class type;
                                if (m instanceof Method) {
                                        type = ((Method) m).getParameterTypes()[i];
                                } else {
                                        type = ((Constructor) m).getParameterTypes()[i];
                                }
                                if (primitives[i] && type.isPrimitive()) {
                                        step[i] = getNumberPrimitiveCastDepth(args[i].getClass(), type);
                                } else if (primitives[i]) {
                                        // param is not primitive
                                        step[i] = PRIMITIVE_BOX_CAST_BASE; // first cast to box type
                                        step[i] += bfsSearch(args[i].getClass(), type);
                                } else if (type.isPrimitive()) {
                                        // arg is not primitive
                                        step[i] = PRIMITIVE_BOX_CAST_BASE; // cast to primitive
                                } else {
                                        // both not primitive
                                        // check null
                                        if (args[i] == null) step[i] = 0;
                                        else {
                                                if (type.isAssignableFrom(args[i].getClass()))
                                                        step[i] = bfsSearch(args[i].getClass(), type);
                                                else {
                                                        if (type.isArray()
                                                                || isFunctionalAbstractClass(type)
                                                                || isFunctionalInterface(type)) {
                                                                step[i] = 1;
                                                        } else if (args[i] instanceof Map || args[i] instanceof List) {
                                                                step[i] = COLLECTION_OBJECT_CAST_BASE;
                                                        } else throw new LtBug("unsupported type cast");
                                                }
                                        }
                                }
                        }
                        steps.put(m, step);
                }

                // choose the best match
                T methodToInvoke = null;
                int[] step = null;
                for (Map.Entry entry : steps.entrySet()) {
                        if (methodToInvoke == null) {
                                methodToInvoke = entry.getKey();
                                step = entry.getValue();
                        } else {
                                int[] newStep = entry.getValue();
                                boolean isBetter = false;
                                boolean isWorse = false;
                                for (int i = 0; i < step.length; ++i) {
                                        if (step[i] == newStep[i]) continue;
                                        else if (step[i] > newStep[i]) isBetter = true;
                                        else if (step[i] < newStep[i]) isWorse = true;

                                        if (isBetter && isWorse)
                                                throw new LtRuntimeException(
                                                        "cannot decide which method to invoke:\n"
                                                                + methodToInvoke + ":" + Arrays.toString(step) + "\n"
                                                                + entry.getKey() + ":" + Arrays.toString(newStep));
                                }

                                if (isBetter) {
                                        methodToInvoke = entry.getKey();
                                        step = entry.getValue();
                                }
                        }
                }

                assert methodToInvoke != null;

                return methodToInvoke;
        }

        /**
         * construct an object.
         *
         * @param targetType the type to instantiate.
         * @param invoker    from which class invokes the method
         * @param primitives whether the argument is primitive
         * @param args       arguments
         * @return the constructed object
         * @throws Throwable exceptions
         */
        public static Object construct(Class targetType, Class invoker, boolean[] primitives, Object[] args) throws Throwable {
                if (primitives.length != args.length) throw new LtBug("primitives.length should equal to args.length");

                Constructor[] constructors = targetType.getDeclaredConstructors();

                // select candidates
                List> candidates = new ArrayList>();
                for (Constructor con : constructors) {
                        if (!LtRuntime.haveAccess(con.getModifiers(), targetType, invoker)) continue;

                        if (con.getParameterTypes().length == args.length) {
                                if (canBeCandidate(con.getParameterTypes(), args, primitives)) {
                                        candidates.add(con);
                                }
                        }
                }

                if (candidates.isEmpty()) {
                        StringBuilder sb = new StringBuilder().append(targetType.getName()).append("(");
                        buildErrorMessageArgsPart(sb, args);
                        sb.append(")");
                        throw new LtRuntimeException("cannot find constructor " + sb.toString());
                } else {
                        Constructor constructor = findBestMatch(candidates, args, primitives);
                        transToRequiredType(args, constructor.getParameterTypes());

                        constructor.setAccessible(true);

                        try {
                                return constructor.newInstance(args);
                        } catch (InvocationTargetException e) {
                                throw e.getTargetException();
                        }
                }
        }

        /**
         * the invocation state.
         */
        public static class InvocationState {
                /**
                 * whether the method is found
                 */
                public boolean methodFound = false;
                /**
                 * the method that invokes this, is trying to get or put a field
                 */
                public boolean fromField = false;
        }

        /**
         * invoke method with a invocationState.
         *
         * @param invocationState  invocationState
         * @param targetClass      the method is in this class
         * @param o                invoke the method on the object (or null if invoke static)
         * @param isStatic         whether the invocation is static
         * @param functionalObject the object to invoke functional method on if method not found
         * @param invoker          from which class invokes the method
         * @param method           method name
         * @param primitives       whether the argument is primitive
         * @param args             the arguments
         * @param canInvokeImport  the method is invoked directly by the method's name, which could be invoking an import static method
         * @return the method result (void methods' results are Unit)
         * @throws Throwable exception
         */
        public static Object invoke(InvocationState invocationState,
                                    Class targetClass,
                                    Object o,
                                    boolean isStatic,
                                    Object functionalObject,
                                    Class invoker,
                                    String method,
                                    boolean[] primitives,
                                    Object[] args,
                                    boolean canInvokeImport) throws Throwable {

                if (primitives.length != args.length) throw new LtBug("primitives.length should equal to args.length");
                Method methodToInvoke = findMethod(invoker, targetClass, o, method, primitives, args);
                // method found ?
                if (null != methodToInvoke) {
                        try {
                                return invokeMethod(methodToInvoke, o, args);
                        } catch (InvocationTargetException e) {
                                throw e.getTargetException();
                        }
                }

                ExceptionContainer ec = new ExceptionContainer();

                Class c = o == null ? targetClass : o.getClass();
                if (c.isArray()) {
                        if (method.equals("get") && args.length >= 1 && args[0] instanceof Integer) {
                                Object res = Array.get(o, (Integer) args[0]);
                                if (args.length == 1) return res;

                                boolean[] bs = new boolean[primitives.length - 1];
                                Object[] as = new Object[args.length - 1];
                                for (int i = 1; i < args.length; ++i) {
                                        bs[i - 1] = primitives[i];
                                        as[i - 1] = args[i];
                                }

                                return invoke(invocationState, targetClass, res, isStatic, null, invoker, "get", bs, as, canInvokeImport);
                        } else if (method.equals("set") && args.length >= 2 && args[0] instanceof Integer) {
                                if (args.length == 2) {
                                        Array.set(o, (Integer) args[0], args[1]);
                                        return args[1];
                                } else {
                                        Object elem = Array.get(o, (Integer) args[0]);

                                        boolean[] bs = new boolean[primitives.length - 1];
                                        Object[] as = new Object[args.length - 1];
                                        for (int i = 1; i < args.length; ++i) {
                                                bs[i - 1] = primitives[i];
                                                as[i - 1] = args[i];
                                        }

                                        return invoke(invocationState, targetClass, elem, isStatic, null, invoker, "set", bs, as, canInvokeImport);
                                }
                        } else {
                                ec.add("Target is array but method is not get(int)");
                                ec.add("Target is array but method is not set(int, ...)");
                        }
                } else {
                        // null string append
                        if (!isStatic && o == null && method.equals("add") && args.length == 1 && args[0] instanceof String) {
                                return "null" + args[0];
                        }
                        // implicit cast
                        if (o != null && invoker.isAnnotationPresent(ImplicitImports.class)) {
                                Class[] implicitClasses = invoker.getAnnotation(ImplicitImports.class).implicitImports();
                                if (implicitClasses.length == 0) {
                                        ec.add("No implicit casts enabled");
                                } else {
                                        for (Class ic : implicitClasses) {
                                                if (!ic.isAnnotationPresent(LatteObject.class)) continue;
                                                Method[] methods = ic.getDeclaredMethods();
                                                for (Method m : methods) {
                                                        if (m.isAnnotationPresent(Implicit.class) && m.getParameterTypes().length == 1 && m.getReturnType() != void.class) {
                                                                Class inputType = m.getParameterTypes()[0];
                                                                if (inputType.isInstance(o)) {
                                                                        Class outputType = m.getReturnType();
                                                                        Method foundMethod = findMethod(invoker, outputType, o, method, primitives, args);
                                                                        if (foundMethod == null) {
                                                                                ec.add("Still cannot find method if casting " + o.getClass().getName() + " to " + m.getReturnType());
                                                                                continue;
                                                                        }
                                                                        // get object instance
                                                                        Object implicitInstance = ic.getField("singletonInstance").get(null);
                                                                        // invoke method
                                                                        m.setAccessible(true);
                                                                        Object castInstance = m.invoke(implicitInstance, o);
                                                                        return invokeMethod(foundMethod, castInstance, args);
                                                                }
                                                        }
                                                }
                                        }
                                }
                        } else {
                                ec.add("No implicit casts enabled");
                        }

                        if (method.equals("set")) {
                                return invoke(invocationState, targetClass, o, isStatic, functionalObject, invoker, "put", primitives, args, canInvokeImport);
                        } else {
                                ec.add("Is not set/put transform");
                        }
                }

                // functional object
                if (functionalObject != null) {
                        InvocationState callFunctionalState = new InvocationState();
                        try {
                                return callFunctionalObject(callFunctionalState, functionalObject, invoker, args);
                        } catch (Throwable t) {
                                if (callFunctionalState.methodFound) throw t;
                                ec.add("Cannot invoke functional object");
                        }
                } else {
                        ec.add("No functional object");
                }

                invocationState.methodFound = false; // method still not found

                // dynamically get field `o.methodName`
                // if it's not `null` and not `Unit` then invoke the retrieved object
                if (!invocationState.fromField && o != null) {
                        Object result = null;
                        boolean fieldFound = false;
                        try {
                                result = LtRuntime.getField(o, method, invoker);
                                fieldFound = true;
                        } catch (NoSuchFieldException e) {
                                ec.add("Cannot get field " + targetClass.getName() + "#" + e.getMessage());
                        }
                        if (fieldFound) {
                                if (result != null && !result.equals(Unit.get())) {
                                        invocationState.methodFound = true;
                                        return callFunctionalObject(result, invoker, args);
                                } else {
                                        ec.add("Field " + targetClass.getName() + "#" + method + " is null or Unit");
                                }
                        }
                }

                // check import static
                if (canInvokeImport) {
                        if (invoker.isAnnotationPresent(StaticImports.class)) {
                                StaticImports si = invoker.getAnnotation(StaticImports.class);
                                Class[] classes = si.staticImports();
                                for (Class cls : classes) {
                                        Method m = findMethod(invoker, cls, null, method, primitives, args);
                                        if (m == null) continue;
                                        return invokeMethod(m, null, args);
                                }
                        }
                }

                // method not found
                // build exception message
                StringBuilder sb = new StringBuilder().append(
                        o == null
                                ? targetClass.getName()
                                : o.getClass().getName()
                ).append("#").append(method).append("(");
                buildErrorMessageArgsPart(sb, args);
                sb.append(")");
                ec.throwIfNotEmpty("Cannot find method to invoke: " + sb.toString(), new Function1() {
                        @Override
                        public Throwable apply(String s) {
                                return new LtRuntimeException(s);
                        }
                });
                // code won't reach here
                throw new LtBug("code won't reach here");
        }

        private static Object invokeMethod(Method m, Object target, Object[] args) throws InvocationTargetException, IllegalAccessException {
                m.setAccessible(true);
                Object res = m.invoke(target, args);
                if (m.getReturnType().equals(void.class)) return Unit.get();
                return res;
        }

        private static void buildErrorMessageArgsPart(StringBuilder sb, Object[] args) {
                boolean isFirst = true;
                for (Object arg : args) {
                        if (isFirst) isFirst = false;
                        else sb.append(", ");
                        sb.append(arg == null ? "null" : arg.getClass().getName());
                }
        }

        /**
         * call the functional object. This method is the CallSite method.
         *
         * @param functionalObject the functional object.
         * @param callerClass      caller class
         * @param args             arguments.
         * @return the calling result.
         * @throws Throwable exception when calling the functional object.
         */
        @SuppressWarnings("unused")
        public static Object callFunctionalObject(Object functionalObject,
                                                  Class callerClass,
                                                  Object[] args) throws Throwable {
                return callFunctionalObject(new InvocationState(), functionalObject, callerClass, args);
        }

        /**
         * call the functional object. This method is the actual method which can be directly used by other methods.
         *
         * @param invocationState  invocation state.
         * @param functionalObject the functional object.
         * @param callerClass      caller class
         * @param args             arguments.
         * @return the calling result.
         * @throws Throwable exception when calling the functional object.
         */
        public static Object callFunctionalObject(InvocationState invocationState,
                                                  Object functionalObject,
                                                  Class callerClass,
                                                  Object[] args) throws Throwable {
                if (functionalObject == null) throw new NullPointerException();

                // check whether it's a functional object
                Class cls = functionalObject.getClass();
                Method theMethodToInvoke;
                if (cls.getSuperclass() != null && isFunctionalAbstractClass(cls.getSuperclass())) {
                        theMethodToInvoke = findAbstractMethod(cls.getSuperclass());
                } else if (cls.getInterfaces().length == 1 && isFunctionalInterface(cls.getInterfaces()[0])) {
                        theMethodToInvoke = findAbstractMethod(cls.getInterfaces()[0]);
                } else {
                        // try to invoke apply(...) on this object
                        return invoke(invocationState,
                                functionalObject.getClass(), functionalObject, false, null, callerClass, "apply", new boolean[args.length], args, false);
                }

                // continue processing `theMethodToInvoke`
                invocationState.methodFound = true;
                try {
                        transToRequiredType(args, theMethodToInvoke.getParameterTypes());
                        Object theRes = theMethodToInvoke.invoke(functionalObject, args);
                        if (theMethodToInvoke.getReturnType().equals(Void.TYPE)) {
                                return Unit.get();
                        }
                        return theRes;
                } catch (InvocationTargetException e) {
                        throw e.getTargetException();
                }
        }

        /**
         * invoke a method.
         *
         * @param targetClass      the method is in this class
         * @param o                invoke the method on the object (or null if invoke static)\
         * @param isStatic         whether the invocation is static
         * @param functionalObject the object to invoke functional method on if method not found
         * @param invoker          from which class invokes the method
         * @param method           method name
         * @param primitives       whether the argument is primitive
         * @param args             the arguments
         * @param canInvokeImport  whether the invocation is allowed to invoke methods from import static
         * @return the method result (void methods' results are Unit)
         * @throws Throwable exception
         */
        @SuppressWarnings("unused")
        public static Object invoke(Class targetClass, Object o, boolean isStatic, Object functionalObject, Class invoker,
                                    String method, boolean[] primitives, Object[] args, boolean canInvokeImport) throws Throwable {
                return invoke(new InvocationState(), targetClass, o, isStatic, functionalObject, invoker, method, primitives, args, canInvokeImport);
        }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy