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

org.sugarcubes.cloner.ReflectionCopierProvider Maven / Gradle / Ivy

There is a newer version: 1.2.3
Show newest version
/*
 * Copyright 2017-2023 the original author or authors.
 *
 * 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.sugarcubes.cloner;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

/**
 * Copier provider implementation.
 *
 * @author Maxim Butov
 */
public class ReflectionCopierProvider implements CopierProvider {

    /**
     * Object policy.
     */
    private final CopyPolicy objectPolicy;

    /**
     * Type policy.
     */
    private final CopyPolicy> typePolicy;

    /**
     * Field policy.
     */
    private final CopyPolicy fieldPolicy;

    /**
     * Object allocator.
     */
    private final ObjectFactoryProvider allocator;

    /**
     * Field copier factory.
     */
    private final FieldCopierFactory fieldCopierFactory;

    /**
     * Cache of copiers.
     */
    private final ConcurrentLazyCache, ObjectCopier> copiers = new ConcurrentLazyCache<>(this::findCopier);

    /**
     * Cache of reflection copiers.
     */
    private final Map, ReflectionCopier> reflectionCopiers = new ConcurrentHashMap<>();

    /**
     * Constructor.
     *
     * @param objectPolicy object policy
     * @param typePolicy type policy
     * @param fieldPolicy field policy
     * @param allocator object allocator
     * @param copiers predefined copiers
     * @param fieldCopierFactory field copier factory
     */
    public ReflectionCopierProvider(CopyPolicy objectPolicy, CopyPolicy> typePolicy,
        CopyPolicy fieldPolicy, ObjectFactoryProvider allocator, Map, ObjectCopier> copiers,
        FieldCopierFactory fieldCopierFactory) {
        this.objectPolicy = objectPolicy;
        this.typePolicy = typePolicy;
        this.fieldPolicy = fieldPolicy;
        this.allocator = allocator;
        this.fieldCopierFactory = fieldCopierFactory;
        this.copiers.putAll(copiers);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  ObjectCopier getCopier(T original) {
        if (objectPolicy != null) {
            return (ObjectCopier) processAction(objectPolicy.getAction(original), () -> copiers.get(original.getClass()));
        }
        return (ObjectCopier) copiers.get(original.getClass());
    }

    /**
     * Finds or creates copier if it was not created yet.
     *
     * @param type object type
     * @return copier
     */
    private ObjectCopier findCopier(Class type) {
        return processAction(typePolicy.getAction(type), () -> findCopierForType(type));
    }

    /**
     * Object copier for {@link CopyAction#NULL} or {@link CopyAction#ORIGINAL} can be returned instantly. For the
     * {@link CopyAction#DEFAULT} action #defaultCopierSupplier will be called.
     *
     * @param action copy action
     * @param defaultCopierSupplier supplier of default copier
     * @return object copier
     */
    private ObjectCopier processAction(CopyAction action, Supplier> defaultCopierSupplier) {
        switch (action) {
            case SKIP:
                throw new IllegalStateException("SKIP action is not applicable for objects.");
            case NULL:
                return ObjectCopier.NULL;
            case ORIGINAL:
                return ObjectCopier.NOOP;
            case DEFAULT:
                return defaultCopierSupplier.get();
            default:
                throw Checks.mustNotHappen();
        }
    }

    /**
     * Finds copier for type.
     *
     * @param type type
     * @return object copier
     */
    private ObjectCopier findCopierForType(Class type) {
        if (Enum.class.isAssignableFrom(type)) {
            return ObjectCopier.NOOP;
        }
        if (type.isArray()) {
            Class componentType = type.getComponentType();
            if (componentType.isPrimitive() || componentType.isEnum()) {
                return ObjectCopier.SHALLOW;
            }
            if (Modifier.isFinal(componentType.getModifiers()) &&
                typePolicy.getAction(componentType) == CopyAction.ORIGINAL) {
                // there is no mutable subtype of componentType, so, we can shallow clone array
                return ObjectCopier.SHALLOW;
            }
            return ObjectCopier.OBJECT_ARRAY;
        }
        TypeCopier annotation = type.getDeclaredAnnotation(TypeCopier.class);
        if (annotation != null) {
            return createCopierFromAnnotation(annotation);
        }
        if (Copyable.class.isAssignableFrom(type)) {
            return ObjectCopier.COPYABLE;
        }
        return findReflectionCopier(type);
    }

    /**
     * Creates an instance of object copier on the basis of annotation properties.
     *
     * @param annotation annotation
     * @return object copier
     */
    private ObjectCopier createCopierFromAnnotation(TypeCopier annotation) {
        Class> copierClass = (Class) annotation.value();
        return ReflectionUtils.newInstance(copierClass);
    }

    /**
     * Returns {@link ReflectionCopier} instance for the type.
     *
     * @param type object type
     * @return copier instance
     */
    private ReflectionCopier findReflectionCopier(Class type) {
        ReflectionCopier copier = reflectionCopiers.get(type);
        if (copier == null) {
            Class superType = type.getSuperclass();
            ReflectionCopier parent = superType != null ? findReflectionCopier(superType) : null;
            copier = new ReflectionCopier<>(fieldPolicy, allocator, type, fieldCopierFactory, parent);
            reflectionCopiers.put(type, copier);
        }
        return copier;
    }

}