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

io.nosqlbench.virtdata.api.bindings.VirtDataConversions Maven / Gradle / Ivy

There is a newer version: 4.15.102
Show newest version
package io.nosqlbench.virtdata.api.bindings;

import io.nosqlbench.nb.api.errors.BasicError;

import java.lang.reflect.*;
import java.security.InvalidParameterException;
import java.util.*;
import java.util.List;
import java.util.function.*;

public class VirtDataConversions {


    private enum FuncType {
        LongToDoubleFunction(java.util.function.LongToDoubleFunction.class, long.class, double.class),
        LongToIntFunction(java.util.function.LongToIntFunction.class, long.class, int.class),
        LongFunction(LongFunction.class, long.class, Object.class),
        LongUnaryOperator(java.util.function.LongUnaryOperator.class, long.class, long.class),
        IntFunction(java.util.function.IntFunction.class, int.class, Object.class),
        IntUnaryOperator(java.util.function.IntUnaryOperator.class, int.class, int.class),
        DoubleFunction(java.util.function.DoubleFunction.class, double.class, Object.class),
        DoubleUnaryOperator(java.util.function.DoubleUnaryOperator.class, double.class, double.class),
        DoubleToLongFunction(java.util.function.DoubleToLongFunction.class, double.class, long.class),
        Function(java.util.function.Function.class, Object.class, Object.class);

        private final Class functionClazz;
        private final Class inputClazz;
        private final Class outputClazz;

        FuncType(Class functionClazz, Class inputClazz, Class outputClazz) {
            this.functionClazz = functionClazz;
            this.inputClazz = inputClazz;
            this.outputClazz = outputClazz;
        }

        public static FuncType valueOf(Class clazz) {
            for (FuncType value : FuncType.values()) {
                if (value.functionClazz.isAssignableFrom(clazz)) {
                    return value;
                }
            }
            throw new InvalidParameterException("No func type was found for " + clazz.getCanonicalName());
        }

    }

    public static  List adaptFunctionList(F[] funcs, Class functionType, Class... resultSignature) {
        List functions = new ArrayList<>();
        for (Object func : funcs) {
            T adapted = adaptFunction(func, functionType, resultSignature);
            functions.add(adapted);
        }
        return functions;
    }

    /**
     * Adapt a functional object into a different type of functional object.
     *
     * @param func            The original function object.
     * @param functionType    The type of the function you need
     * @param              The generic type of function being converted from
     * @param              The generic type of function being converted to
     * @param resultSignature The signature of all output types, linearized for use after type-erasure.
     * @return An instance of T
     */
    public static  T adaptFunction(F func, Class functionType, Class... resultSignature) {
        FuncType funcType = FuncType.valueOf(func.getClass());

        List> signature = new ArrayList<>();
        List> fromSignature = linearizeObjectSignature(func);

        List> resultTypes = new ArrayList<>();
        resultTypes.add(functionType);
        for (Class aClass : resultSignature) {
            resultTypes.add(aClass);
        }
        List> toSignature = linearizeSignature(resultTypes);

        signature.addAll(fromSignature);
        signature.addAll(toSignature);

        if (fromSignature.equals(toSignature)) {
            return (T) func;
        }
        if (isAssignableFromTo(fromSignature, toSignature)) {
            return (T) func;
        }

        Class[] methodSignature = signature.toArray(new Class[0]);

        Method adapter = null;
        Class hostclass = AdaptFunctionsFlexibly.class;
        try {
            adapter = AdaptFunctionsFlexibly.class.getMethod("adapt", methodSignature);
        } catch (NoSuchMethodException e) {
            StringBuilder example = new StringBuilder();


            example.append("    // Ignore the place holders, but ensure the return type is accurate\n");
            String toTypeSyntax = canonicalSyntaxFor(toSignature);
            example.append("    public static ").append(toTypeSyntax);

            example.append(" adapt(");
            String fromTypeSyntax = canonicalSyntaxFor(fromSignature);
            example.append(fromTypeSyntax).append(" f");
            int idx = 1;

            for (int i = 1; i < signature.size(); i++) {
                Class sigpart = signature.get(i);
                example.append(", ").append(sigpart.getSimpleName()).append(" i").append(idx++);
            }

            example.append(") {\n    }\n");

            String forInstance = example.toString();
            throw new BasicError("adapter method is not implemented on class " + hostclass.getCanonicalName() + ":\n" + forInstance);

        }

        FuncType fromType = FuncType.valueOf(func.getClass());
        if (fromType.functionClazz.getTypeParameters().length > 0) {
            TypeVariable>[] funcParms = func.getClass().getTypeParameters();
        }

        Object[] args = new Object[signature.size()];
        args[0] = func;
        for (int i = 1; i < args.length; i++) {
            args[i] = null;
        }

        T result = null;

        try {
            result = (T) adapter.invoke(null, args);
            return result;
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Slice the incoming object list into a set of functions, based on a grouping interval and an offset.
     * @param mod The grouping interval, or modulo to slice the function groups into
     * @param offset The offset within the group for the provided function
     * @param funcs A list of source objects to convert to functions.
     * @return
     */
    public static  List getFunctions(int mod, int offset, Class functionType, Object... funcs) {
//        if ((funcs.length%mod)!=0) {
//            throw new RuntimeException("uneven division of functions, where multiples of " + mod + " are expected.");
//        }
        List functions = new ArrayList<>();
        for (int i = offset; i < funcs.length; i+=mod) {
            Object func = funcs[i];
            T longFunction = VirtDataConversions.adaptFunction(func, functionType, Object.class);
            functions.add(longFunction);
        }
        return functions;
    }

    private static boolean isAssignableFromTo(List> fromSignature, List> toSignature) {
        if (fromSignature.size() != toSignature.size()) {
            return false;
        }
        for (int i = 0; i < fromSignature.size(); i++) {
            Class aClass0 = fromSignature.get(i);
            Class aClass1 = toSignature.get(i);
            if (!aClass1.isAssignableFrom(aClass0)) {
                return false;
            }
        }
        return true;
    }

    /**
     * This constructs a type-erasure compatible signature of a tuple of types based on the caller's specified types.
     * More specifically, it takes a type A and a type B and reifies their syntactical types as a list of runtime
     * classes.
     * 

* For example, {@code LongFunction, Function} would be converted to {@code * LongFunction.class, Integer.class, Function.class, Integer.class, String.class} *

* The types must be provided by the caller. *

* The purpose of this is to create an unambiguous signature to allow explicit method lookup which is the only * pair-wise association of the two provided types within some namespace. *

* This only works for types recognized by FuncType, as imposing this type system exposes the generic parameters * that are useful for adapting functions. * * @return An array of classes */ private static List> linearizeSignature(Class... types) { List> reified = new ArrayList<>(); LinkedList> provided = new LinkedList<>(Arrays.asList(types)); while (provided.size() > 0) { Class mainType = provided.removeFirst(); FuncType funcType = FuncType.valueOf(mainType); reified.add(funcType.functionClazz); for (TypeVariable> typeParameter : funcType.functionClazz.getTypeParameters()) { if (provided.size() == 0) { throw new RuntimeException("ran out of type parameters while qualifying generic parameter " + typeParameter.getName() + " for " + funcType.functionClazz); } Class paramType = provided.remove(); if (paramType.isPrimitive()) { throw new RuntimeException("You must provide non primitive types for parameter positions here, not " + paramType.getCanonicalName()); } reified.add(paramType); } } return reified; } private static List> linearizeSignature(List> types) { return linearizeSignature(types.toArray(new Class[0])); } /** * Create a linearized list of classes to represent the provided instance. * * @param f An object which is represnted in the FuncTypes enum * @return A list of classes that uniquely describe the functional type signature of f, with generic parameters made * explicit. */ private static List> linearizeObjectSignature(Object f) { LinkedList> linearized = new LinkedList<>(); FuncType type = FuncType.valueOf(f.getClass()); Class functionClazz = type.functionClazz; linearized.add(functionClazz); TypeVariable>[] typeParameters = functionClazz.getTypeParameters(); Method applyMethod = findApplyMethod(functionClazz); switch (typeParameters.length) { case 2: linearized.add(applyMethod.getParameterTypes()[0]); case 1: linearized.add(applyMethod.getReturnType()); } if (linearized.size() > 0 && linearized.peekLast().equals(Object.class)) { Object out = null; try { Class input = applyMethod.getParameterTypes()[0]; switch (input.getSimpleName()) { case "int": case "Integer": out = applyMethod.invoke(f, 1); break; case "long": case "Long": out = applyMethod.invoke(f, 1L); break; case "double": case "Double": out = applyMethod.invoke(f, 1d); break; default: out = Object.class; } } catch (Exception e) { throw new RuntimeException(e); } linearized.removeLast(); linearized.addLast(out.getClass()); } return linearized; } private static String canonicalSyntaxFor(List> elements) { return canonicalSyntaxFor(elements.toArray(new Class[0])); } private static String canonicalSyntaxFor(Class... elements) { StringBuilder sb = new StringBuilder(); sb.append(elements[0].getSimpleName()); if (elements.length > 1) { sb.append("<"); for (int i = 1; i < elements.length; i++) { sb.append(elements[i].getSimpleName()).append(","); } sb.setLength(sb.length() - 1); sb.append(">"); } return sb.toString(); } private static Method findApplyMethod(Class c) { Optional applyMethods = Arrays.stream(c.getMethods()) .filter(m -> { boolean isNotDefault = !m.isDefault(); boolean isNotBridge = !m.isBridge(); boolean isNotSynthetic = !m.isSynthetic(); boolean isPublic = (m.getModifiers() & Modifier.PUBLIC) > 0; boolean isNotString = !m.getName().equals("toString"); boolean isApplyMethod = m.getName().startsWith("apply"); boolean isFunctional = isNotDefault && isNotBridge && isNotSynthetic && isPublic && isNotString && isApplyMethod; return isFunctional; }) .distinct() .findFirst(); return applyMethods.orElseThrow(() -> new RuntimeException("Unable to find apply method on " + c.getCanonicalName())); } private static Method findMethod(Class hostclass, Class fromClass, Class toClass, Class... generics) { Class[] argTypes = new Class[generics.length + 2]; argTypes[0] = fromClass; argTypes[1] = toClass; for (int i = 0; i < generics.length; i++) { argTypes[i + 2] = generics[i]; } try { return hostclass.getMethod("adapt", argTypes); } catch (NoSuchMethodException e) { StringBuilder example = new StringBuilder(); StringBuilder genericsBuffer = new StringBuilder(); TypeVariable>[] typeParameters = toClass.getTypeParameters(); if (typeParameters.length > 0) { genericsBuffer.append("<"); for (int i = 0; i < typeParameters.length; i++) { if (generics.length < typeParameters.length) { throw new RuntimeException("You must provide " + typeParameters.length + " generic parameter types for " + toClass.getCanonicalName()); } // if (generics[i].isPrimitive()) { // throw new RuntimeException("You must declare non-primitive types in generic parameter placeholders, not " + generics[i].getSimpleName()); // } genericsBuffer.append(generics[i].getSimpleName()); genericsBuffer.append(","); } genericsBuffer.setLength(genericsBuffer.length() - 1); genericsBuffer.append(">"); } String genericSignature = genericsBuffer.toString(); example.append(" // Ignore the place holders, but ensure the return type is accurate\n"); example.append(" public static ").append(toClass.getSimpleName()); example.append(genericSignature); example.append(" adapt("); example.append(fromClass.getSimpleName()).append(" f, "); example.append(toClass.getSimpleName()).append(genericSignature).append(" ignore0"); int idx = 1; for (Class generic : generics) { example.append(", ").append(generic.getSimpleName()).append(" ignore").append(+idx); } example.append(") {\n }\n"); String forInstance = example.toString(); throw new BasicError("adapter method is not implemented on class " + hostclass.getCanonicalName() + ":\n" + forInstance); } } // private static void assertOutputAssignable(Object result, Class clazz) { // if (!ClassUtils.isAssignable(result.getClass(), clazz, true)) { // throw new InvalidParameterException("Unable to assign type of " + result.getClass().getCanonicalName() // + " to " + clazz.getCanonicalName()); // } // //// if (!clazz.isAssignableFrom(result.getClass())) { //// throw new InvalidParameterException("Unable to assign type of " + result.getClass().getCanonicalName() //// + " to " + clazz.getCanonicalName()); //// } // } // /** * Given a base object and a wanted type to convert it to, assert that the type of the base object is assignable to * the wanted type. Further, if the wanted type is a generic type, assert that additional classes are assignable to * the generic type parameters. Thus, if you want to assign to a generic type from a non-generic type, you must * qualify the types of values that will be used in those generic parameter positions in declaration order. * *

This is useful for taking any object and a known type and reifying it as the known type so that it can be * then used idiomatically with normal type awareness. This scenario occurs when you are accepting an open type for * flexiblity but then need to narrow the type sufficiently for additional conversion in a type-safe way.

* * @param base The object to be assigned to the wanted type * @param wantType The class type that the base object needs to be assignable to * @param clazzes The types of values which will checked against generic type parameters of the wanted type * @param Generic parameter T for the wanted type * @return The original object casted to the wanted type after verification of parameter assignability */ private static T assertTypesAssignable( Object base, Class wantType, Class... clazzes) { if (!wantType.isAssignableFrom(base.getClass())) { throw new InvalidParameterException("Unable to assign " + wantType.getCanonicalName() + " from " + base.getClass().getCanonicalName()); } TypeVariable>[] typeParameters = base.getClass().getTypeParameters(); if (typeParameters.length > 0) { if (clazzes.length != typeParameters.length) { throw new InvalidParameterException( "type parameter lengths are mismatched:" + clazzes.length + ", " + typeParameters.length ); } for (int i = 0; i < clazzes.length; i++) { Class from = clazzes[i]; TypeVariable> to = typeParameters[i]; boolean assignableFrom = to.getGenericDeclaration().isAssignableFrom(from); if (!assignableFrom) { throw new InvalidParameterException("Can not assign " + from.getCanonicalName() + " to " + to.getGenericDeclaration().getCanonicalName()); } } } return (T) (base); } // // /** // * Throw an error indicating a narrowing conversion was attempted for strict conversion. // * @param func The source function to convert from // * @param targetClass The target class which was requested // */ // private static void throwNarrowingError(Object func, Class targetClass) { // throw new BasicError("Converting from " + func.getClass().getCanonicalName() + " to " + targetClass.getCanonicalName() + // " is not allowed when strict conversion is requested."); // } }