Java Deep-Cloning library which uses objenesis

888 views
Skip to first unread message

kostas....@googlemail.com

unread,
Jan 17, 2009, 12:50:40 PM1/17/09
to Objenesis developers
Hi, I've used objenesis to create a deep-cloning library. The lib can
clone
any java object (even those not impl clone()). Comes really handy in
certain cases, i.e. I use it to clone cached objects so that
modifications to the object don't alter the cached instance.

Here is the link: http://robust-it.co.uk/clone/ . I hope you find it
handy.

Kostas

Joe Walnes

unread,
Jan 17, 2009, 2:42:26 PM1/17/09
to objene...@googlegroups.com, kostas....@googlemail.com
Hi Kostas

This is an excellent use of Objenesis. Small, to the point, useful,
and does one thing really well.

I'd like to blog about it and link to it from the Objenesis website.
But first some feedback on some potential improvements to make it more
suitable as a library:

* Using static fields for configuration data (CloneUtils.ignored) and
caching (CloneUtils.objectInstantiators) creates hidden dependencies
that can lead to bugs in runtime systems. For example, suppose two
different parts of an application (or dependencies) attempt to use
CloneUtils in different ways, they could tread on each others feet.
Instead, ensure that CloneUtils can be instantiated, use non-static
fields, and allow the apps that call them to isolate them as they
wish. Of course, you could still provide a static helper class for
users who don't care, but you wouldn't want to force people to do
this.

* By removing the staticness of the fields, you can also do the same
with the methods, allowing power-users to override just parts of it
(e.g. if they wanted a custom newInstance() method).

* Would be handy to allow users to use their own instance of
Objenesis. You could provide an additional constructor or a setter to
allow this.

* I'd steer clear from using 'Util' in a class name of a public API.
How about calling the class 'Cloner'?

* clone0() seems a strange name for a public method. How about just
calling it clone() and allow overloading to take effect. Or maybe name
it more descriptively.

* You can simplify usage for the end user by embedding Objenesis
inside cloning.jar (under com.rits.beans.objenesis to ensure
isolation). This means users only need to include your jar and no
other dependencies. See
http://objenesis.googlecode.com/svn/docs/embedding.html

* JavaDoc on the site would make it easier for people to use. Hmmm...
Objenesis is lacking JavaDoc on the main site too - we should fix that
;)

* It would be simpler for newInstance() to simply call
objenesis.newInstance() rather than looking up the ObjectInstantiator
and caching it, as this is exactly what Objenesis does under the hood
(so there's no performance improvement). Though I see you've used a
ConcurrentHashMap, which I assume is why you've done this. I reckon we
should update Objenesis to do the same. Good idea.

Sounds like a lot of negative feedback, but believe me, that's not how
I mean it. I'm very glad to see this library and I'm just offering
some suggestions to make it even better :).

thanks
-Joe

Konstantine Kougios

unread,
Jan 17, 2009, 4:27:25 PM1/17/09
to j...@truemesh.com, objene...@googlegroups.com
Hi Joe,

    Good points, I know the library needed tidying up. I've converted it to a bean and renamed it to Cloner. I've updated the site and uploaded the zip file.

Now it's much better and can i.e. be used as a proper spring bean. Soon I hope to find time to add a CacheManager bean, the one I use to cache objects (using oscache and Cloner and closures). More on that later on, for now let me reply one by one to your ideas:


Joe Walnes wrote:
Hi Kostas

This is an excellent use of Objenesis. Small, to the point, useful,
and does one thing really well.

I'd like to blog about it and link to it from the Objenesis website.
But first some feedback on some potential improvements to make it more
suitable as a library:

* Using static fields for configuration data (CloneUtils.ignored) and
caching (CloneUtils.objectInstantiators) creates hidden dependencies
that can lead to bugs in runtime systems. For example, suppose two
different parts of an application (or dependencies) attempt to use
CloneUtils in different ways, they could tread on each others feet.
Instead, ensure that CloneUtils can be instantiated, use non-static
fields, and allow the apps that call them to isolate them as they
wish. Of course, you could still provide a static helper class for
users who don't care, but you wouldn't want to force people to do
this.

  
Absolutely, CloneUtils was done in a hurry with other priorities in mind. Now I've converted it to a proper bean.

* By removing the staticness of the fields, you can also do the same
with the methods, allowing power-users to override just parts of it
(e.g. if they wanted a custom newInstance() method).
  
done. newInstance() is refactored to use objenesis.newInstance(). Now it is private because it is better used only
internally by Cloner. (maybe I should make it protected so that it can be overridden. I'll wait for feedback)

* Would be handy to allow users to use their own instance of
Objenesis. You could provide an additional constructor or a setter to
allow this.
  
I've provided the constructor.

* I'd steer clear from using 'Util' in a class name of a public API.
How about calling the class 'Cloner'?
* clone0() seems a strange name for a public method. How about just
calling it clone() and allow overloading to take effect. Or maybe name
it more descriptively.
  
clone0() is now a private method. It is meant to be used internally only.

* You can simplify usage for the end user by embedding Objenesis
inside cloning.jar (under com.rits.beans.objenesis to ensure
isolation). This means users only need to include your jar and no
other dependencies. See
http://objenesis.googlecode.com/svn/docs/embedding.html
  
TODO

* JavaDoc on the site would make it easier for people to use. Hmmm...
Objenesis is lacking JavaDoc on the main site too - we should fix that
;)
  
I added some comments and also uploaded the javadocs. Not much but it's a start.

* It would be simpler for newInstance() to simply call
objenesis.newInstance() rather than looking up the ObjectInstantiator
and caching it, as this is exactly what Objenesis does under the hood
(so there's no performance improvement). Though I see you've used a
ConcurrentHashMap, which I assume is why you've done this. I reckon we
should update Objenesis to do the same. Good idea.
  
Done. It is also synchronized in order for Cloner to be thread-safe (I'll wait till objenesis uses ConcurrentHashMap. Then I'll remove the synchronization)
Sounds like a lot of negative feedback, but believe me, that's not how
I mean it. I'm very glad to see this library and I'm just offering
some suggestions to make it even better :).
  
Feedback was correct. Using so many static methods/variables was done only for convenience at a time that my projects
focus was elsewhere. I believe Cloner now is much better, spring-ready and many instances can be used for different
purposes.

Always open to suggestions. Soon to publish the CacheManager.

Regards,

Kostas

Joe Walnes

unread,
Jan 18, 2009, 5:15:23 AM1/18/09
to objene...@googlegroups.com, Konstantine Kougios
Wow, that was quick!

I've just looked over it again, and it's looking really nice now.
Excellent work.

A few more suggestions:

* cloneSoftenException(). I would maybe make this the default method.
Of the 2 exceptions it wraps, IllegalArgumentException is already a
RuntimeException and IllegalAccessException is an internal
implementation detail that should probably never be exposed. I would
wrap it in a custom exception (e.g. CloneException, which extends
RuntimeException). You'd only need one clone() method then.

That's it for the actual code. The remaining comments are about distribution.

* Pick a license. LICENSE.txt currently contains a whole load of
licenses. Make the license clear on the website. I usally choose
Apache License v2, but any of the main ones should do.

* Include the tests, source (uncompressed), docs and build file in the download.

* Also provide a link on the website directly to the binary .jar file.

* Create a mailing list for users. You may even want to use a full on
code hosting site like Google Code or SourceForge.

* Blatent optional plug: You can use http://xsite.codehaus.org/ to
help you generate a familiar-looking pretty site.

Ship it :)

Henri Tremblay

unread,
Jan 18, 2009, 9:01:39 AM1/18/09
to objene...@googlegroups.com, Konstantine Kougios
Hi,

After the Joe's excellent review, I'll go with some comments on my
own. Most on Objenesis in fact.

First, great job. Your library will definitly be useful. And please
pick a license ASAP to allow it to be used by everyone wihout any
legal unknown. Deploy it in the maven central repository also a must.

1- There's one drawback about embedding Objenesis (even if it's what I
currently do on EasyMock). Since the package changes, if you create or
use someone else InstantiatorStrategy it needs to implement the
interface in the package of the embedding library instead of in
Objenesis genuine package. It then limits reusability.

2- True... javadoc isn't on Objenesis site... Should be

3- I didn't used a ConcurrentHashMap because I was necessary to
synchronized getInstantiatorOf anyway. So there was no point to add a
layer of synchronization.

4- http://robust-it.co.uk/clone/cloning-1.0.zip doesn't exist :-(

5- I couldn't look at the code because of 4 but how do you find out a
class is immutable?

6- I think a undeep clone can also be useful

7- xsite is great :-)

8- Since it's still the season, happy new year everyone :-)

Cheers,
Henri

Konstantine Kougios

unread,
Jan 18, 2009, 11:52:47 AM1/18/09
to Henri Tremblay, objene...@googlegroups.com
Hi,

    I'll be regularly making the changes to cloning lib,trying to impl most of the changes that you guys recommend. Currently I've impl the 3 first requests from Joe. Soon hopefully it'll have a decent site too. I'll post again when I've made all the changes. Henri, please refresh the cloning home page (or clear your browser cache) so that you see the newest page. I've uploaded v1.1.

About #5, I know of no simple way to determine which classes are immutable.  So it is simply implemented for some immutable jdk classes:

        if (clz == String.class || clz == Integer.class || clz == Long.class || clz == Boolean.class || clz == Class.class || clz == Float.class || clz == Double.class || clz == Character.class || clz == Byte.class || clz == Short.class || clz == Void.class || clz == BigDecimal.class || clz == BigInteger.class) return o;

This speeds up things a lot and uses less memory. (I use the lib in a high traffic site. I use it for a cache of 25000 items, cloning the cached objects. Cloning strings was taking time and was also using memory for no good reason)

About #6, shallowClone() would be handy, I know. TODO

Happy new year

Kostas

Jörg Schaible

unread,
Jan 19, 2009, 1:15:06 PM1/19/09
to objene...@googlegroups.com
Hi Kostas,

On Sonntag, 18. Januar 2009, Konstantine Kougios wrote:

[snip]

> About #5, I know of no simple way to determine which classes are
> immutable. So it is simply implemented for some immutable jdk classes:
>
> if (clz == String.class || clz == Integer.class || clz ==
> Long.class || clz == Boolean.class || clz == Class.class || clz ==
> Float.class || clz == Double.class || clz == Character.class || clz ==
> Byte.class || clz == Short.class || clz == Void.class || clz ==
> BigDecimal.class || clz == BigInteger.class) return o;
>
> This speeds up things a lot and uses less memory. (I use the lib in a
> high traffic site. I use it for a cache of 25000 items, cloning the
> cached objects. Cloning strings was taking time and was also using
> memory for no good reason)

There are some more like the complete enum stuff, URI, URL, UUID, the
java.util.ref stuff, the a lot of the reflection stuff, Pattern ... actually
it would be nice to be able to add own types or set the immutable types list
completely.

> About #6, shallowClone() would be handy, I know. TODO

Please also make some notes about limitations. I'd expect problems cloning
structures that contain system resources like sockets, pipes or even worse
implementations that keep a pointer to native memory (like internals of the
BufferedImage - you can wreck your VM).

- Jörg

Henri Tremblay

unread,
Jan 19, 2009, 6:39:18 PM1/19/09
to objene...@googlegroups.com
Yes, I will suggest an HashSet with all the default classes and then
the possibility to add some by configuration (.properties for
instance, or xml) or dynamically (addImmutableClass,
addImmutableClasses).

Another question Kostas, when dontClone is called, is it still copying
the reference? If yes, it means that a dontClone class is exactly like
an immutable class. So you could have only dontClone. However, I think
it could be useful to be able to specify classes that shouldn't be
cloned nor copy as references (and possibly bean path that should be
ignored).

kostas....@googlemail.com

unread,
Jan 23, 2009, 2:38:50 PM1/23/09
to Objenesis developers
Hi,

Jorg, thanks for the list of immutable classes. Enum's are already
considered immutable and are not cloned. I'll add the rest of them in
the next version.

About the dangers of cloning, I believe we should leave it to the user
of Cloner (there are more, i.e. locks on immutable objects remain for
the cloned objects). But I'll have to place a warning on the lib's web
page.

Henri, there is a hashset, dontClone() adds a class to that set.
Effectively dontClone() and immutable classes are treated similarly.
The reference to the immutable or dontClone()'d instance is used for
the clone. Maybe I'll just leave the hashset (and remove the if()),
but I noticed that the if() makes the library faster because of many
String.class'es lying around deep graphs of objects.

This is the code part that interest both of you (o is the object we
try to clone, ignored is the hashset used by dontClone()):

if (o == null) return null;

final Class<T> clz = (Class<T>) o.getClass();
if (clz.isEnum()) return o;
// this big "if" performs better than i.e. a hashset
if (clz == String.class || clz == Integer.class || clz == Long.class
|| clz == Boolean.class || clz == Class.class || clz == Float.class ||
clz == Double.class || clz == Character.class || clz == Byte.class ||
clz == Short.class || clz == Void.class || clz == BigDecimal.class ||
clz == BigInteger.class) return o;
final Object clonedPreviously = clones != null ? clones.get(o) :
null;
if (clonedPreviously != null) return (T) clonedPreviously;

// skip cloning ignored classes
if (ignored.contains(clz)) return o;

FYI, I've updated the site today and now the project contains most of
the requested features.

http://robust-it.co.uk/clone/

Kostas
Reply all
Reply to author
Forward
0 new messages