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

ru.progrm_jarvis.javacommons.collection.CollectionFactory Maven / Gradle / Ivy

package ru.progrm_jarvis.javacommons.collection;

import javassist.*;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.val;
import lombok.var;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import ru.progrm_jarvis.javacommons.bytecode.CommonBytecodeLibrary;
import ru.progrm_jarvis.javacommons.bytecode.annotation.UsesBytecodeModification;
import ru.progrm_jarvis.javacommons.cache.Cache;
import ru.progrm_jarvis.javacommons.cache.Caches;
import ru.progrm_jarvis.javacommons.classloading.ClassNamingStrategy;
import ru.progrm_jarvis.javacommons.classloading.GcClassDefiners;
import ru.progrm_jarvis.javacommons.lazy.Lazy;
import ru.progrm_jarvis.javacommons.object.Pair;

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

import static java.util.Collections.unmodifiableSet;

/**
 * Factory used for creation of special {@link java.util.Collection collections} at classload.
 */
@UtilityClass
public class CollectionFactory {

    /**
     * {@link Lookup lookup} of this class.
     */
    private static final Lookup LOOKUP = MethodHandles.lookup();

    /**
     * Class naming strategy used to allocate names for generated immutable enum set classes
     */
    private static final @NonNull ClassNamingStrategy IMMUTABLE_ENUM_SET_CLASS_NAMING_STRATEGY = ClassNamingStrategy
            .createPaginated(CollectionFactory.class.getName() + "$$Generated$$ImmutableEnumSet$$");

    /**
     * Default javassist class pool
     */
    private final @NonNull Lazy CLASS_POOL = Lazy.createThreadSafe(ClassPool::getDefault);

    /**
     * Cache of instances of generated enum sets using naturally sorted array of its elements as the key
     */
    private final @NonNull Cache[], Set>> IMMUTABLE_ENUM_SETS = Caches.weakValuesCache();

    /**
     * {@link CtClass} representation of {@link AbstractImmutableSet} wrapped in {@link Lazy}
     */
    private static final @NonNull Lazy ABSTRACT_IMMUTABLE_SET_CT_CLASS = Lazy
            .createThreadSafe(() -> toCtClass(AbstractImmutableSet.class));
    /**
     * Array storing single reference to {@link CtClass} representation of {@link Iterator} wrapped in {@link Lazy}
     */
    private static final @NonNull Lazy ITERATOR_CT_CLASS_ARRAY = Lazy
            .createThreadSafe(() -> new CtClass[]{toCtClass(Iterator.class)});

    /**
     * Empty array of {@link CtClass}es.
     */
    public static final @NotNull CtClass @NotNull @Unmodifiable [] EMPTY_CT_CLASS_ARRAY = new CtClass[0];

    /**
     * Creates an immutable enum {@link Set set} from the given array of stored enum constants.
     *
     * @param values enum constants to be stored in the given set
     * @param  type of enum
     * @return runtime-compiled optimized immutable enum {@link Set set} for the given enum values or a pre-compiled n
     * empty set if {@code values} is empty
     *
     * @apiNote at current, the instances are cached and not GC-ed (as their classes) so this should be used carefully
     * @apiNote this implementation of immutable enum {@link Set}s is specific and should be used carefully {@code
     * instanceof} and {@code switch} by {@link Enum#ordinal()} are used for containment-related checks and {@link}
     */
    @SafeVarargs
    @SuppressWarnings("unchecked")
    @Deprecated // should be remade via ASM or totally removed due to specific behaviour of anonymous class referencing
    @UsesBytecodeModification(value = CommonBytecodeLibrary.JAVASSIST, optional = true)
    public > Set createImmutableEnumSet(final @NonNull E... values) {
        if (values.length == 0) return Collections.emptySet();

        if (CommonBytecodeLibrary.JAVASSIST.isAvailable()) {
            // sort enum values so that the cache does not store different instances
            // for different orders of same elements
            @SuppressWarnings("SuspiciousArrayCast") val enumValues = (E[]) Arrays.stream(values)
                    .distinct()
                    .sorted()
                    .toArray(Enum[]::new);

            //noinspection unchecked
            return (Set) IMMUTABLE_ENUM_SETS.get(enumValues, valuesArray -> {
                //
                val enumType = valuesArray.getClass().getComponentType();
                // name of the class of the enum
                val enumTypeName = enumType.getCanonicalName();
                // number of enum constants
                val elementsCount = enumValues.length;
                // class pool instance
                val classPool = CLASS_POOL.get();
                // generated class
                val clazz = classPool.makeClass(IMMUTABLE_ENUM_SET_CLASS_NAMING_STRATEGY.get());
                setSuperClass(clazz, ABSTRACT_IMMUTABLE_SET_CT_CLASS.get());
                addEmptyConstructor(clazz);

                /* Generated methods */

                // `boolean contains(Object)` method
                {
                    val src = new StringBuilder("public boolean contains(Object element) {");
                    // simply check type if any enum values is included
                    if (Arrays.equals(enumValues, enumType.getEnumConstants())) src
                            .append("return element instanceof ").append(enumTypeName).append(';');
                        // use ordinal switch if not all enum values are included
                    else {
                        src
                                .append("if (element instanceof ").append(enumTypeName).append(")")
                                .append("switch (((Enum) element).ordinal()) {");

                        for (val enumValue : enumValues)
                            src
                                    .append("case ").append(enumValue.ordinal()).append(": return true;");

                        src.append("default: return false;").append("}").append("return false;");
                    }
                    addCtMethod(src.append('}').toString(), clazz);
                }

                // `boolean isEmpty()` method
                addCtMethod(
                        "public boolean isEmpty() {return false;}",
                        clazz
                );

                // `int size()` method
                addCtMethod(
                        "public int size() {return " + elementsCount + ";}",
                        clazz
                );


                val enumNames = Arrays.stream(enumValues)
                        .map(element -> enumTypeName + '.' + element.name())
                        .toArray(String[]::new);
                { // field for storing array of elements and `toArray` methods
                    val newArrayStatement = "new " + enumTypeName + "[]{" + String.join(",", enumNames) + '}';

                    // add field with maximal accessibility so that it can be JITed as much as possible
                    addCtField(
                            "public static final " + enumTypeName + "[] ARRAY_DATA = " + newArrayStatement + ';', clazz
                    );

                    // non-generic `toArray`
                    addCtMethod(
                            "public Object[] toArray() {return " + newArrayStatement + ";}",
                            clazz
                    );
                /* Not following the contract
                addCtMethod(""
                                + "public  T[] toArray(final T[] target) {"
                                + "if (!target.getClass().getComponentType()"
                                + ".isAssignableFrom(" + enumClassName + ".class)) throw new ArrayStoreException("
                                + "\"Attempt to call toArray(T[]) with wrong array type ("
                                + enumClassName + " or its super-type was expected)\");"
                                + "return (T[])" + newArrayStatement +";"
                                + "}",
                        clazz
                );
                 */
                    // generic `toArray`
                    // don't forget that javassist is not friendly with those and so use manual type erasure
                    addCtMethod(
                            "public Object[] toArray(Object[] target) {"
                                    + "if (target.length < " + elementsCount + ") return java.util.Arrays.copyOf("
                                    + "ARRAY_DATA, " + elementsCount + ", target.getClass()"
                                    + ");"
                                    + "System.arraycopy(ARRAY_DATA, 0, target, 0, " + elementsCount + ");"
                                    + "if (target.length > " + elementsCount + ") target[" + elementsCount + "] = null;"
                                    + "return target;"
                                    + "}",
                            clazz
                    );
                }

                {// `void forEach(Consumer)`
                    val src = new StringBuilder(
                            "public void forEach(java.util.function.Consumer consumer) {"
                                    + "if (consumer == null) throw new NullPointerException(\"consumer should not be "
                                    + "empty\");"
                    );
                    for (val enumName : enumNames)
                        src.append("consumer.accept(").append(enumName).append(')').append(';');

                    addCtMethod(src.append('}').toString(), clazz);
                }
                // `hashCode()`
                addCtMethod("int hashCode() {return " + Arrays.hashCode(valuesArray) + ";}", clazz);
                // `equals()`
                {// `boolean equals(Object)`
                    addCtMethod(
                            "public boolean equals(Object object) {"
                                    + "if (object == this) return true;"
                                    + "if (!(object instanceof java.util.Collection)) return false;"
                                    + "java.util.Collection set =  (java.util.Collection) object;"
                                    + "if (set.size() != " + elementsCount + ") return set.size()"
                                    + " == missingValue && set.containsAll(this);"
                                    + ""
                                    + "}",
                            clazz
                    );
                }

            /*
            Generate iterator
             */
                final CtClass iteratorClazz;
                {
                    // Use short name as Javassist appends it to parent class name
                    iteratorClazz = clazz.makeNestedClass("Iterator", true);
                    iteratorClazz.setInterfaces(ITERATOR_CT_CLASS_ARRAY.get());
                    addEmptyConstructor(iteratorClazz);

                    addCtField("public int index = 0;", iteratorClazz);
                    addCtMethod(
                            "public java.util.Iterator iterator() {"
                                    + "return new " + iteratorClazz.getName() + "();"
                                    + "}",
                            clazz
                    );

                    // boolean hasNext()
                    addCtMethod("public boolean hasNext() {return index < " + elementsCount + ";}", iteratorClazz);
                    {
                        // T next()
                        final StringBuilder src = new StringBuilder("public Object next() {switch (index++) {");

                        for (var i = 0; i < elementsCount; i++)
                            src
                                    .append("case ").append(i).append(':').append("return ").append(enumNames[i])
                                    .append(';');

                        addCtMethod(
                                src.append("default: throw new java.util.NoSuchElementException();}}")
                                        .toString(),
                                iteratorClazz
                        );
                    }
                }

                final Constructor>> constructor;
                {
                    final byte[] iteratorBytecode, bytecode;
                    try {
                        iteratorBytecode = iteratorClazz.toBytecode();
                        bytecode = clazz.toBytecode();
                    } catch (final IOException | CannotCompileException e) {
                        throw new RuntimeException("Cannot get bytecode for generated classes", e);
                    }

                    val loadedClass = GcClassDefiners.getDefault()
                            .defineClasses(
                                    LOOKUP,
                                    Pair.of(iteratorClazz.getName(), iteratorBytecode),
                                    Pair.of(clazz.getName(), bytecode)
                            )[1];
                    try {
                        constructor = (Constructor>>) loadedClass.getDeclaredConstructor();
                    } catch (final NoSuchMethodException e) {
                        throw new RuntimeException("Could not find generated constructor", e);
                    }
                }

                try {
                    return constructor.newInstance();
                } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
                    throw new RuntimeException("Could not call generated constructor", e);
                }
                //
            });
        } else {
            val length = values.length;
            switch (length) {
                case 1: return unmodifiableSet(EnumSet.of(values[0]));
                case 2: return unmodifiableSet(EnumSet.of(values[0], values[1]));
                case 3: return unmodifiableSet(EnumSet.of(values[0], values[1], values[2]));
                case 4: return unmodifiableSet(EnumSet.of(values[0], values[1], values[2], values[3]));
                case 5: return unmodifiableSet(EnumSet.of(values[0], values[1], values[2], values[3], values[4]));
                default: return unmodifiableSet(EnumSet.copyOf(Arrays.asList(values)));
            }
        }
    }

    /**
     * Gets {@link CtClass} representation of the given class.
     *
     * @param clazz class whose {@link CtClass} representation is needed
     * @return {@link CtClass} representation of the given class
     *
     * @throws IllegalStateException if it is impossible to find {@link CtClass} representation of the given class
     */
    private @NotNull CtClass toCtClass(final @NotNull Class clazz) {
        try {
            return CLASS_POOL.get().get(clazz.getName());
        } catch (final NotFoundException e) {
            throw new IllegalStateException("Unable to get CtClass by name " + clazz.getName(), e);
        }
    }

    /**
     * Adds an empty constructor to given {@link CtClass}.
     *
     * @param targetClass class to which to add an empty constructor
     */
    @SneakyThrows(CannotCompileException.class)
    private void addEmptyConstructor(final @NotNull CtClass targetClass) {
        targetClass.addConstructor(CtNewConstructor.make(EMPTY_CT_CLASS_ARRAY, EMPTY_CT_CLASS_ARRAY, targetClass));
    }

    /**
     * Sets the super-class of the given {@link CtClass}.
     *
     * @param targetClass class to which to set the super-class
     * @param superClass super-class of the target class
     */
    @SneakyThrows(CannotCompileException.class)
    private void setSuperClass(final @NotNull CtClass targetClass, final @NotNull CtClass superClass) {
        targetClass.setSuperclass(superClass);
    }

    /**
     * Adds a field based on given source code to given {@link CtClass}.
     *
     * @param src source code of the field
     * @param targetClass class to which to add the field
     */
    @SneakyThrows(CannotCompileException.class)
    private void addCtField(
            @NonNull @Language(value = "java", prefix = "public class Foo extends GeneratedImmutableEnumSetTemplate {",
                               suffix = "}") final String src, final @NonNull CtClass targetClass) {
        targetClass.addField(CtField.make(src, targetClass));
    }

    /**
     * Adds a method based on given source code to given {@link CtClass}.
     *
     * @param src source code of the method
     * @param targetClass class to which to add the method
     */
    @SneakyThrows(CannotCompileException.class)
    private void addCtMethod(
            @NonNull @Language(value = "java", prefix = "public class Foo extends GeneratedImmutableEnumSetTemplate {",
                               suffix = "}") final String src, final @NonNull CtClass targetClass) {
        targetClass.addMethod(CtNewMethod.make(src, targetClass));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy