Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java detect circular references during custom cloning

Tags:

java

I am writing a custom clone method for each entity. for deep copy is there a way to detect circular references or do I have to manually figure it out and restrict cloning to be unidirectional instead of bidirectional.

For example we use hibernate an hence a User object has a reference to Address and Address has a reference to User. Trying to see if doing a deep copy of Address as well as User is possible without running into circular reference issues

like image 247
user373201 Avatar asked Mar 01 '11 17:03

user373201


2 Answers

To implement this, you need a Map of references to already cloned objects. We implemented deep clone something like this:

In our entity base class:

public void deepClone() {
    Map<EntityBase,EntityBase> alreadyCloned = 
        new IdentityHashMap<EntityBase,EntityBase>();
    return deepClone(this,alreadyCloned);
}

private static EntityBase deepClone(EntityBase entity, 
                                    Map<EntityBase,EntityBase> alreadyCloned) {
    EntityBase clone = alreadyCloned.get(entity);
    if( clone != null ) {
        return alreadyClonedEntity;
    }
    clone = newInstance(entity.getClass);
    alreadyCloned.put(this,clone);
    // fill clone's attributes from original entity. Call
    // deepClone(entity,alreadyCloned) 
    // recursively for each entity valued object.
    ...
}
like image 127
Daniel Avatar answered Nov 09 '22 01:11

Daniel


@Daniel: Thank you very much for this great answer!

I just used your code and modified it a little based on my needs which makes it easier to use with subclasses. Maybe someone else is interested in that as well so here is my code for the base class:

/**
 * Perform a deep clone.
 * 
 * @return Deep Clone.
 * @throws CloneNotSupportedException
 */
@SuppressWarnings("unchecked")
public VersionedEntityImpl deepClone() throws CloneNotSupportedException {
    Map<VersionedEntityImpl, VersionedEntityImpl> alreadyCloned = new IdentityHashMap<VersionedEntityImpl, VersionedEntityImpl>();
    return deepClone(this, alreadyCloned);
}

/**
 * Perform a deep clone.
 * 
 * @param entity
 * @param alreadyCloned
 * @return Deep Clone.
 * @throws CloneNotSupportedException
 */
@SuppressWarnings("unchecked")
protected VersionedEntityImpl deepClone(VersionedEntityImpl entity,
        Map<VersionedEntityImpl, VersionedEntityImpl> alreadyCloned) throws CloneNotSupportedException {
    if (entity != null) {
        VersionedEntityImpl clone = alreadyCloned.get(entity);
        if (clone != null) {
            return clone;
        }
        clone = entity.clone();
        alreadyCloned.put(entity, clone);
        return entity.deepCloneEntity(clone, alreadyCloned);
    }
    return null;
}

/**
 * Method performing a deep clone of an entity (circles are eliminated automatically). Calls
 * deepClone(entity,alreadyCloned) recursively for each entity valued object.
 *
 * @param clone
 * @param alreadyCloned
 * @return clone
 * @throws CloneNotSupportedException
 */
protected abstract VersionedEntityImpl deepCloneEntity(VersionedEntityImpl clone,
        Map<VersionedEntityImpl, VersionedEntityImpl> alreadyCloned) throws CloneNotSupportedException;

What to put into the subclasses:

@SuppressWarnings("unchecked")
@Override
protected VersionedEntityImpl deepCloneEntity(VersionedEntityImpl clone,
        Map<VersionedEntityImpl, VersionedEntityImpl> alreadyCloned) throws CloneNotSupportedException {
    // fill clone's attributes from original entity. Call
    // deepClone(entity,alreadyCloned)
    // recursively for each entity valued object.
    if (this.associatedItems != null) {
        List<SomeClass> listClone = new LinkedList<SomeClass>();
        for (SomeClass someClass: this.associatedItems) {
            listClone.add((SomeClass) super.deepClone(someClass, alreadyCloned));
        }
        ((SomeOtherClass) clone).setAssociatedItems(listClone);
    }
    ((SomeOtherClass) clone).setYetAnotherItem((YetAnotherClass) super.deepClone(this.yai, alreadyCloned));

    return clone;
}

It is not perfect as of yet but it gets the job done nicely for now :)

like image 27
Michael Colin Avatar answered Nov 09 '22 00:11

Michael Colin