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

org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl Maven / Gradle / Ivy

There is a newer version: 0.21.0
Show newest version
/*
 * Copyright 2018 astonbitecode
 * 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.astonbitecode.j4rs.api.instantiation;

import org.astonbitecode.j4rs.api.Instance;
import org.astonbitecode.j4rs.api.dtos.GeneratedArg;
import org.astonbitecode.j4rs.api.dtos.InvocationArg;
import org.astonbitecode.j4rs.api.dtos.InvocationArgGenerator;
import org.astonbitecode.j4rs.api.invocation.InstanceGenerator;
import org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl;
import org.astonbitecode.j4rs.errors.InstantiationException;
import org.astonbitecode.j4rs.utils.Utils;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class NativeInstantiationImpl {
    static InvocationArgGenerator gen = new InvocationArgGenerator();

    public static Instance instantiate(String className, InvocationArg... args) {
        try {
            CreatedInstance createdInstance = createInstance(className, generateArgObjects(args));
            return InstanceGenerator.create(createdInstance.object, createdInstance.clazz);
        } catch (Exception error) {
            throw new InstantiationException("Cannot create instance of " + className, error);
        }
    }

    public static Instance createForStatic(String className) {
        try {
            Class clazz = Utils.forNameEnhanced(className);
            return InstanceGenerator.create(clazz);
        } catch (Exception error) {
            throw new InstantiationException("Cannot create instance of " + className, error);
        }
    }

    public static Instance createJavaArray(String className, InvocationArg... args) {
        try {
            CreatedInstance createdInstance = createCollection(className, generateArgObjects(args), J4rsCollectionType.Array);
            return new JsonInvocationImpl(createdInstance.object, createdInstance.clazz);
        } catch (Exception error) {
            throw new InstantiationException("Cannot create Java Array of " + className, error);
        }
    }

    public static Instance createJavaList(String className, InvocationArg... args) {
        try {
            CreatedInstance createdInstance = createCollection(className, generateArgObjects(args), J4rsCollectionType.List);
            return new JsonInvocationImpl(createdInstance.object, createdInstance.clazz);
        } catch (Exception error) {
            throw new InstantiationException("Cannot create Java List of " + className, error);
        }
    }

    public static Instance createJavaMap(String keyClassName, String valueClassName, InvocationArg... args) {
        try {
            CreatedInstance createdInstance = createMap(keyClassName, valueClassName, generateArgObjects(args));
            return new JsonInvocationImpl(createdInstance.object, createdInstance.clazz);
        } catch (Exception error) {
            throw new InstantiationException(String.format("Cannot create Java Map of keys %s and values %s", keyClassName, valueClassName), error);
        }
    }

    static GeneratedArg[] generateArgObjects(InvocationArg[] args) throws Exception {
        return gen.generateArgObjects(args);
    }

    static CreatedInstance createInstance(String className, GeneratedArg[] params) throws Exception {
        Class clazz = Utils.forNameEnhanced(className);
        Class[] paramTypes = Arrays.stream(params).map(param -> param.getClazz())
                .toArray(size -> new Class[size]);
        Object[] paramObjects = Arrays.stream(params).map(param -> param.getObject())
                .toArray(size -> new Object[size]);
        Constructor constructor = findConstructor(clazz, paramTypes);
        Object instance = constructor.newInstance(paramObjects);
        return new CreatedInstance(clazz, instance);
    }

    private static Constructor findConstructor(Class clazz, Class[] argTypes) throws NoSuchMethodException {
        List found = Arrays.stream(clazz.getConstructors())
                // Match the params number
                .filter(constructor -> constructor.getGenericParameterTypes().length == argTypes.length)
                .filter(constructor -> {
                    // Each element of the matchedParams list shows whether a parameter is matched or not
                    List matchedParams = new ArrayList<>();
                    // Get the parameter types of the method to check if matches
                    Type[] pts = constructor.getGenericParameterTypes();
                    for (int i = 0; i < argTypes.length; i++) {
                        // Check each parameter type
                        Type typ = pts[i];

                        if (typ instanceof ParameterizedType || typ instanceof WildcardType) {
                            // For generic parameters, the type erasure makes the parameter be an Object.class
                            // Therefore, the argument is always matched
                            matchedParams.add(true);
                        } else if (typ instanceof GenericArrayType) {
                            // TODO: Improve by checking the actual types of the arrays?
                            matchedParams.add(argTypes[i].isArray());
                        } else if (typ instanceof Class) {
                            // In case of TypeVariable, the arg matches via the equals method
                            matchedParams.add(((Class) typ).isAssignableFrom(argTypes[i]));
                        } else {
                            // We get to this point if the TypeVariable is a generic, which is defined with a name like T, U etc.
                            // The type erasure makes the parameter be an Object.class. Therefore, the argument is always matched.
                            // TODO:
                            // We may have some info about the generic types (if they are defined in the Class scope).
                            // Can we use this info to provide some type safety? Use matchedParams.add(validateSomeTypeSafety(argTypes[i]));
                            // In that case however, we don't catch the situation where a class is defined with a generic T in the class scope,
                            // but there is a method that defines another generic U in the method scope.
                            matchedParams.add(true);
                        }
                    }
                    return matchedParams.stream().allMatch(Boolean::booleanValue);
                })
                .collect(Collectors.toList());
        if (!found.isEmpty()) {
            return found.get(0);
        } else {
            Class superclass = clazz.getSuperclass();
            if (superclass == null) {
                throw new NoSuchMethodException("Constructor was not found in " + clazz.getName() + " or its ancestors.");
            }
            return findConstructor(superclass, argTypes);
        }
    }

    static CreatedInstance createCollection(String className, GeneratedArg[] params, J4rsCollectionType collectionType) throws Exception {
        boolean isJ4rsArray = className.equals(InvocationArg.CONTENTS_ARRAY);
        Class clazz = isJ4rsArray ? Utils.forNameBasedOnArgs(params) : Utils.forNameEnhanced(className);
        Object arrayObj = Array.newInstance(clazz, params.length);

        Class[] paramTypes = Arrays.stream(params).map(param -> param.getClazz())
                .toArray(size -> new Class[size]);

        if (!isJ4rsArray && !Arrays.stream(paramTypes).allMatch(type -> type.getName().equals(className))) {
            throw new IllegalArgumentException("Could not create Java array. All the arguments should be of class " + className);
        }

        Object[] paramObjects = Arrays.stream(params).map(param -> param.getObject())
                .toArray(size -> new Object[size]);

        for (int i = 0; i < params.length; i++) {
            Array.set(arrayObj, i, paramObjects[i]);
        }

        switch (collectionType) {
            case Array:
                return new CreatedInstance(arrayObj.getClass(), arrayObj);
            case List: {
                Object l = clazz.isPrimitive() ? arrayObj : Arrays.asList(((Object[]) arrayObj));
                return new CreatedInstance(l.getClass(), l);
            }
            default:
                return new CreatedInstance(arrayObj.getClass(), arrayObj);
        }

    }

    static CreatedInstance createMap(String keyClassName, String valueClassName, GeneratedArg[] params) throws Exception {
        Class keyClazz = Utils.forNameEnhanced(keyClassName);
        Map map = new HashMap();

        int i = 0;
        while (i < params.length) {
            map.put(params[i].getObject(), params[i + 1].getObject());
            i = i + 2;
        }
        return new CreatedInstance(map.getClass(), map);
    }

    static class CreatedInstance {
        private Class clazz;
        private Object object;

        public CreatedInstance(Class clazz, Object object) {
            this.clazz = clazz;
            this.object = object;
        }

        public Class getClazz() {
            return clazz;
        }

        public Object getObject() {
            return object;
        }
    }

    enum J4rsCollectionType {
        Array,
        List
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy