Coder Social home page Coder Social logo

sugar-cubes-cloner's Introduction

Java deep cloning library

build maven central

Deep cloning of any objects.

  • Intuitive.
  • Fast.
  • Thread safe.
  • Supports parallel execution.
  • Java 8+ compatible.

Maven:

<dependency>
    <groupId>io.github.sugar-cubes</groupId>
    <artifactId>sugar-cubes-cloner</artifactId>
    <version>1.2.3</version>
</dependency>

Gradle:

implementation "io.github.sugar-cubes:sugar-cubes-cloner:1.2.3"

It is recommended also to include Objenesis library into your application.

Objectives

  • To get simple, convenient and configurable way of deep-cloning of objects of any types.
  • Make clean and extensible classes structure.

Existing solutions

Java serialization

Many projects use serialization for object cloning. The raw solution is:

ByteArrayOutputStream buffer = new ByteArrayOutputStream();
new ObjectOutputStream(buffer).writeObject(original);
return new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())).readObject();

This code can be simplified with Apache commons-lang SerializationUtils.clone(object) or Spring Framework SerializationUtils.deserialize(SerializationUtils.serialize(object)).

Pros:

  • portable, works on any JVM
  • no external dependencies

Cons:

  • slow
  • requires all objects to be serializable
  • may be customized only by changing serialization process

Other serialization libraries

Still serialization, but with non-standard libraries, such as:

Faster than java.io serialization.

Other cloning libraries

The solution

Interface

Class Description
Cloner The cloner interface.
ClonerException Wrapper for all (checked and unchecked) exceptions, happened during cloning. Unchecked.
Cloners Factory for standard cloners.
CopyAction Copy action (skip/null/original/clone).
CopyPolicy Set of class/field rules for cloning.
ObjectCopier Object copier interface.
Predicates Predicates factory to configure policies.
ReflectionClonerBuilder Builder for creating custom cloners.
TraversalAlgorithm Depth-first (default) or breadth-first.

Usage

Clone an object

SomeObject clone = Cloners.reflection().clone(original);

Do not clone instances of a class

Cloner cloner = Cloners.builder()
    .setTypeAction(NonCloneableType.class, CopyAction.NULL)
    .build();
SomeObject clone = cloner.clone(original);

Here the instances of NonCloneableType in the original's object tree will be replaced with nulls in the clone.

Cloner cloner = Cloners.builder()
    .setTypeAction(NonCloneableType.class, CopyAction.ORIGINAL)
    .build();
SomeObject clone = cloner.clone(original);

Here the instances of NonCloneableType in the original's object tree will be copied by reference into the clone.

The same thing can also be done with annotations:

@TypePolicy(CopyAction.NULL)
public class NonCloneableType {

or

@TypePolicy(CopyAction.ORIGINAL)
public class NonCloneableType {

Skip some fields

Skip transient fields:

Cloner cloner = Cloners.builder()
    .fieldAction(field -> Modifier.isTransient(field.getModifiers()), CopyAction.SKIP)
    .build();
SomeObject clone = cloner.clone(original);

Or (Hibernate case):

Cloner cloner = Cloners.builder()
    .fieldAction(Predicates.annotatedWith(Transient.class), CopyAction.SKIP)
    .build();
SomeObject clone = cloner.clone(original);

Creating shallow copy

If you wish to create a shallow copy of an object, i.e. another object which references same children as the original, you may do:

Cloner cloner = Cloners.builder()
    .fieldPolicy(CopyPolicy.original())
    .build();
SomeObject clone = cloner.clone(original);

If you want to have deep cloning by default and shallow cloning of all instances of SomeObject, you may do:

Cloner cloner = Cloners.builder()
    .shallow(SomeObject.class)
    .build();

Customization

Cloner cloner =
    // new builder instance
    Cloners.builder()
        // custom allocator
        .objectFactoryProvider(new ObjenesisObjectFactoryProvider())
        // copy thread locals by reference
        .typeAction(Predicates.subclass(ThreadLocal.class), CopyAction.ORIGINAL)
        // copy closeables by reference
        .typeAction(Predicates.subclass(AutoCloseable.class), CopyAction.ORIGINAL)
        // skip SomeObject.cachedValue field when cloning
        .fieldAction(SomeObject.class, "cachedValue", CopyAction.SKIP)
        // set fields with @Transient annotation to null
        .fieldAction(Predicates.annotatedWith(Transient.class), CopyAction.NULL)
        // custom copier for SomeOtherObject type
        .copier(CustomObject.class, new CustomObjectCopier())
        // parallel mode
        .mode(CloningMode.PARALLEL)
        // create cloner
        .build();

// perform cloning
SomeObject clone = cloner.clone(original);

Annotations

It's possible to use annotations to configure field/type actions and custom type copiers.

Annotation Description
FieldPolicy Field copy policy.
TypeCopier Type copier.
TypePolicy Type copy policy.

Implementation

There is three modes of execution: recursive, sequential (default) and parallel.

In sequential mode does not use recursion. Uses Depth-first (by default) or Breadth-first algorithm for the object graph traversal.

In parallel mode the order of copying is unpredictable.

If the Objenesis library is available in the classpath, uses it to instantiate objects. Otherwise, uses reflection.

The priority of copy configurations is:

  1. (high) builder configuration;
  2. annotations;
  3. (low) default configuration (JDK immutable classes).

Known limitations

The library requires Java 8 or higher.

Default configuration of reflection cloner does not clone lambdas and method references. These can be cloned using UnsafeObjectFactoryProvider.

Java 9+ restricts access to objects members via Reflection API. To solve this one may

  • use --illegal-access=permit JVM argument (works on Java below 17);
  • if you add the cloner into classpath, use --add-opens JVM arguments, e.g. --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED;
  • if you have an application with the modules which are properly configured, then use cloner's module name, i.e. --add-opens java.base/java.lang=io.github.sugarcubes.cloner --add-opens java.base/java.lang.invoke=io.github.sugarcubes.cloner --add-opens java.base/java.util=io.github.sugarcubes.cloner --add-opens java.base/java.util.concurrent=io.github.sugarcubes.cloner;
  • the library also contains a Java agent which opens modules for the cloner, just run java with argument -javaagent:/path/to/sugar-cubes-cloner-1.2.3.jar.

Versioning

Semantic Versioning.

License

Apache License 2.0 © Maxim Butov

sugar-cubes-cloner's People

Contributors

mbutov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

rabshohid10

sugar-cubes-cloner's Issues

Enum duplication.

Cloners.reflection() creates duplicate enum instance when enum constant defines a body.

enum MyEnum {
  X {};
}

Then Cloners.reflection().clone(MyEnum.X) != MyEnum.X.
Expected Cloners.reflection().clone(MyEnum.X) == MyEnum.X.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.