Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.github.sugarcubes.cloner.ReflectionClonerBuilder Maven / Gradle / Ivy
/*
* 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 io.github.sugarcubes.cloner;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* Builder for reflection cloners.
*
* @author Maxim Butov
*/
@SuppressWarnings("checkstyle:MultipleStringLiterals")
public final class ReflectionClonerBuilder {
/**
* JDK configuration.
*/
private static final JdkConfiguration JDK_CONFIGURATION = JdkConfigurationHolder.CONFIGURATION;
/**
* Default copiers for well-known JDK types.
*/
private static final Map, ObjectCopier>> DEFAULT_COPIERS;
static {
Map, ObjectCopier>> defaultCopiers = new LinkedHashMap<>();
JDK_CONFIGURATION.getImmutableTypes().forEach(type -> defaultCopiers.put(type, ObjectCopier.NOOP));
JDK_CONFIGURATION.getCloneableTypes().forEach(type -> defaultCopiers.put(type, ObjectCopier.SHALLOW));
defaultCopiers.put(java.util.ArrayDeque.class, new SimpleCollectionCopier<>(java.util.ArrayDeque::new));
defaultCopiers.put(java.util.ArrayList.class, new SimpleCollectionCopier<>(java.util.ArrayList::new));
defaultCopiers.put(java.util.LinkedList.class, new SimpleCollectionCopier<>(size -> new java.util.LinkedList<>()));
defaultCopiers.put(java.util.Stack.class, new SimpleCollectionCopier<>(size -> new java.util.Stack<>()));
defaultCopiers.put(java.util.Vector.class, new SimpleCollectionCopier<>(java.util.Vector::new));
defaultCopiers.put(java.util.EnumMap.class, new EnumMapCopier<>());
defaultCopiers.put(java.util.IdentityHashMap.class, new IdentityHashMapCopier());
DEFAULT_COPIERS = Collections.unmodifiableMap(defaultCopiers);
}
/**
* Object factory provider.
*/
private ObjectFactoryProvider objectFactoryProvider;
/**
* Field copier factory.
*/
private FieldCopierFactory fieldCopierFactory;
/**
* Cloning mode.
*/
private CloningMode mode;
/**
* Traversal algorithm for sequential mode.
*/
private TraversalAlgorithm traversalAlgorithm;
/**
* Executor service for parallel mode.
*/
private ExecutorService executor;
/**
* Object copy policy.
*/
private CopyPolicy objectPolicy;
/**
* Custom actions for objects.
*/
private final Map objectActions = new IdentityHashMap<>();
/**
* Custom type copy policy.
*/
private CopyPolicy> typePolicy;
/**
* Custom actions for types.
*/
private final Map, CopyAction> typeActions = new LinkedHashMap<>();
/**
* Predicate actions for types.
*/
private final Map>, CopyAction> typePredicateActions = new LinkedHashMap<>();
/**
* Custom field copy policy.
*/
private CopyPolicy fieldPolicy;
/**
* Custom actions for fields.
*/
private final Map fieldActions = new LinkedHashMap<>();
/**
* Predicate actions for fields.
*/
private final Map, CopyAction> fieldPredicateActions = new LinkedHashMap<>();
/**
* Custom copiers for types.
*/
private final Map, ObjectCopier>> copiers = new LinkedHashMap<>(DEFAULT_COPIERS);
/**
* Predefined clones.
*/
private final Map clones = new IdentityHashMap<>();
/**
* Creates a builder.
*/
public ReflectionClonerBuilder() {
}
/**
* Sets object allocator.
*
* @param objectFactoryProvider object factory provider
* @return same builder instance
*/
public ReflectionClonerBuilder objectFactoryProvider(ObjectFactoryProvider objectFactoryProvider) {
this.objectFactoryProvider = check(objectFactoryProvider, this.objectFactoryProvider, "Object factory provider");
return this;
}
/**
* Sets field copier factory.
*
* @param fieldCopierFactory field copier factory
* @return same builder instance
*/
public ReflectionClonerBuilder fieldCopierFactory(FieldCopierFactory fieldCopierFactory) {
this.fieldCopierFactory = check(fieldCopierFactory, this.fieldCopierFactory, "Field copier factory");
return this;
}
/**
* Sets allocator and field copier factory which use {@link sun.misc.Unsafe}.
*
* @return same builder instance
*/
public ReflectionClonerBuilder unsafe() {
return objectFactoryProvider(new UnsafeObjectFactoryProvider()).fieldCopierFactory(new UnsafeFieldCopierFactory());
}
/**
* Sets cloning mode.
*
* @param mode cloning mode
* @return same builder instance
*/
public ReflectionClonerBuilder mode(CloningMode mode) {
this.mode = check(mode, this.mode, "Mode");
return this;
}
/**
* Sets traversal algorithm for objects graph.
*
* @param traversalAlgorithm traversal algorithm
* @return same builder instance
*/
public ReflectionClonerBuilder traversalAlgorithm(TraversalAlgorithm traversalAlgorithm) {
this.traversalAlgorithm = check(traversalAlgorithm, this.traversalAlgorithm, "Traversal algorithm");
return this;
}
/**
* Enables parallel mode with given executor service.
*
* @param executor executor service
* @return same builder instance
*/
public ReflectionClonerBuilder executor(ExecutorService executor) {
this.executor = check(executor, this.executor, "Executor");
return this;
}
/**
* Sets object policy. Using object policy significantly slows down cloning process, thus,
* type and field policies should be used if possible.
*
* @param objectPolicy object policy
* @return same builder instance
*/
public ReflectionClonerBuilder objectPolicy(CopyPolicy objectPolicy) {
this.objectPolicy = check(objectPolicy, this.objectPolicy, "Object policy");
return this;
}
/**
* Registers action for object. Usage of object actions significantly slows down cloning process, thus,
* type and field policies should be used if possible.
*
* @param original original object
* @param action action
* @return same builder instance
*/
public ReflectionClonerBuilder objectAction(Object original, CopyAction action) {
Checks.argNotNull(original, "Original");
Checks.argNotNull(action, "Action");
Checks.illegalArg(action == CopyAction.SKIP, "SKIP action is not applicable for objects.");
Checks.illegalArg(objectActions.containsKey(original), "Action for %s already set.", original);
if (action != CopyAction.DEFAULT) {
objectActions.put(original, action);
}
return this;
}
/**
* Sets type copy policy.
*
* @param typePolicy type copy policy
* @return same builder instance
*/
public ReflectionClonerBuilder typePolicy(CopyPolicy> typePolicy) {
this.typePolicy = check(typePolicy, this.typePolicy, "Type policy");
return this;
}
/**
* Registers action for type.
*
* @param type object type
* @param action action
* @return same builder instance
*/
public ReflectionClonerBuilder typeAction(Class> type, CopyAction action) {
Checks.argNotNull(type, "Type");
Checks.argNotNull(action, "Action");
Checks.illegalArg(action == CopyAction.SKIP, "SKIP action is not applicable for objects.");
Checks.illegalArg(typeActions.containsKey(type), "Action for %s already set.", type);
if (action != CopyAction.DEFAULT) {
typeActions.put(type, action);
copiers.remove(type);
}
return this;
}
/**
* Registers action for type.
*
* @param type object type name
* @param action action
* @return same builder instance
*/
public ReflectionClonerBuilder typeAction(String type, CopyAction action) {
Checks.argNotNull(type, "Type");
Checks.argNotNull(action, "Action");
return typeAction(ReflectionUtils.classForName(type), action);
}
/**
* Registers action for type predicate.
*
* @param typePredicate type predicate
* @param action action
* @return same builder instance
*/
public ReflectionClonerBuilder typeAction(Predicate> typePredicate, CopyAction action) {
Checks.argNotNull(typePredicate, "Type predicate");
Checks.argNotNull(action, "Action");
Checks.illegalArg(action == CopyAction.SKIP, "SKIP action is not applicable for objects.");
if (action != CopyAction.DEFAULT) {
typePredicateActions.put(typePredicate, action);
}
return this;
}
/**
* Sets field copy policy.
*
* @param fieldPolicy type copy policy
* @return same builder instance
*/
public ReflectionClonerBuilder fieldPolicy(CopyPolicy fieldPolicy) {
Checks.argNotNull(fieldPolicy, "Field policy");
Checks.isNull(this.fieldPolicy, "Field policy already set.");
this.fieldPolicy = fieldPolicy;
return this;
}
/**
* Registers action for field.
*
* @param field field
* @param action action
* @return same builder instance
*/
public ReflectionClonerBuilder fieldAction(Field field, CopyAction action) {
Checks.argNotNull(field, "Field");
Checks.argNotNull(action, "Action");
Checks.illegalArg(action == CopyAction.NULL && field.getType().isPrimitive(),
"Cannot apply action NULL for primitive field %s.", field);
Checks.illegalArg(fieldActions.containsKey(field), "Action for %s already set.", field);
if (action != CopyAction.DEFAULT) {
fieldActions.put(field, action);
}
return this;
}
/**
* Registers action for field.
*
* @param type object type
* @param field declared field name
* @param action action
* @return same builder instance
*/
public ReflectionClonerBuilder fieldAction(Class> type, String field, CopyAction action) {
Checks.argNotNull(type, "Type");
Checks.argNotNull(field, "Field");
Checks.argNotNull(action, "Action");
return fieldAction(ReflectionUtils.getField(type, field), action);
}
/**
* Registers action for field predicate.
*
* @param fieldPredicate field predicate
* @param action action
* @return same builder instance
*/
public ReflectionClonerBuilder fieldAction(Predicate fieldPredicate, CopyAction action) {
Checks.argNotNull(fieldPredicate, "Field predicate");
Checks.argNotNull(action, "Action");
if (action != CopyAction.DEFAULT) {
fieldPredicateActions.put(fieldPredicate, action);
}
return this;
}
/**
* Registers set of copiers.
*
* @param copiers copiers
* @return same builder instance
*/
public ReflectionClonerBuilder copiers(Map, ObjectCopier>> copiers) {
copiers.forEach(this::copier);
return this;
}
/**
* Registers copier for type.
*
* @param type object type
* @param copier copier
* @return same builder instance
*/
public ReflectionClonerBuilder copier(Class> type, ObjectCopier> copier) {
Checks.argNotNull(type, "Type");
Checks.argNotNull(copier, "Copier");
Checks.illegalArg(copiers.get(type) != DEFAULT_COPIERS.get(type), "Copier for %s already set.", type);
copiers.put(type, copier);
return this;
}
/**
* Registers clone for the object.
*
* @param original original object
* @param clone clone
* @return same builder instance
*/
public ReflectionClonerBuilder clone(Object original, Object clone) {
Checks.argNotNull(original, "Original");
Checks.argNotNull(clone, "Clone");
Checks.illegalArg(clones.get(original) != null, "Clone for %s already set.", original);
clones.put(original, clone);
return this;
}
/**
* Registers singleton, i.e. the object which must be only copied by reference.
*
* @param singleton singleton
* @return same builder instance
*/
public ReflectionClonerBuilder singleton(Object singleton) {
Checks.argNotNull(singleton, "Singleton");
Checks.illegalArg(clones.get(singleton) != null, "Singleton %s already registered.", singleton);
clones.put(singleton, singleton);
return this;
}
/**
* Checks new value to be not null and old value to be null.
*
* @param argument type
* @param value new value
* @param oldValue old value
* @param arg argument name
* @return new value
*/
private static T check(T value, T oldValue, String arg) {
Checks.argNotNull(value, arg);
Checks.isNull(oldValue, "%s already set.", arg);
return value;
}
/**
* Returns value if it is not null or creates with factory.
*
* @param value type
* @param value value
* @param factory default value factory
* @return value if it is not null or factory call result
*/
private static T createIfNull(T value, Supplier factory) {
return value != null ? value : factory.get();
}
/**
* Creates compound policy.
*
* @param input object type
* @param policy custom policy
* @param exactMap map (object, copy action)
* @param predicateMap map (predicate, copy action)
* @param fallbackPolicy fallback policy
* @return compound policy
*/
private static CopyPolicy compound(CopyPolicy policy, Map exactMap,
Map, CopyAction> predicateMap, CopyPolicy fallbackPolicy) {
List> policies = new ArrayList<>();
if (policy != null) {
policies.add(policy);
}
if (!exactMap.isEmpty()) {
policies.add(new ExactPolicy<>(exactMap));
}
if (!predicateMap.isEmpty()) {
policies.add(new PredicatePolicy<>(predicateMap));
}
if (fallbackPolicy != null) {
policies.add(fallbackPolicy);
}
return CopyPolicy.compound(policies);
}
/**
* Creates an instance of the cloner on the basis of the configuration.
*
* @return cloner
*/
public Cloner build() {
CopyPolicy objectPolicy;
if (this.objectPolicy != null || !objectActions.isEmpty()) {
objectPolicy = compound(this.objectPolicy, objectActions, Collections.emptyMap(), null);
}
else {
objectPolicy = null;
}
CopyPolicy> typePolicy = compound(this.typePolicy, typeActions, typePredicateActions, new AnnotatedTypeCopyPolicy());
CopyPolicy fieldPolicy = compound(this.fieldPolicy, fieldActions, fieldPredicateActions, new AnnotatedFieldCopyPolicy());
ObjectFactoryProvider objectFactoryProvider = createIfNull(this.objectFactoryProvider, ObjectFactoryProvider::defaultInstance);
FieldCopierFactory fieldCopierFactory = createIfNull(this.fieldCopierFactory, ReflectionFieldCopierFactory::new);
ReflectionCopierProvider provider =
new ReflectionCopierProvider(objectPolicy, typePolicy, fieldPolicy, objectFactoryProvider, copiers, fieldCopierFactory);
Supplier extends AbstractCopyContext> contextSupplier;
CloningMode mode = this.mode != null ? this.mode : CloningMode.SEQUENTIAL;
switch (mode) {
case RECURSIVE:
Checks.isNull(this.traversalAlgorithm, "Traversal algorithm must be null for recursive mode.");
Checks.isNull(this.executor, "Executor must be null for recursive mode.");
contextSupplier = () -> new RecursiveCopyContext(provider, clones);
break;
case SEQUENTIAL:
Checks.isNull(this.executor, "Executor must be null for sequential mode.");
TraversalAlgorithm traversalAlgorithm = this.traversalAlgorithm != null ? this.traversalAlgorithm : TraversalAlgorithm.DEPTH_FIRST;
contextSupplier = () -> new SequentialCopyContext(provider, clones, traversalAlgorithm);
break;
case PARALLEL:
Checks.isNull(this.traversalAlgorithm, "Traversal algorithm must be null for parallel mode.");
ExecutorService executor = createIfNull(this.executor, ForkJoinPool::commonPool);
contextSupplier = () -> new ParallelCopyContext(provider, clones, executor);
break;
default:
throw Checks.mustNotHappen();
}
return new ClonerImpl(contextSupplier);
}
}