views:

338

answers:

4

Let's say you have two instances of the same bean type, and you'd like to display a summary of what has changed between the two instances - for example, you have a bean representing a user's settings in your application, and you'd like to be able to display a list of what has changed in the new settings the user is submitting (instance #1) versus what is stored already for the user (instance #2).

Is there a commonly used algorithm or design pattern for a task such as this, perhaps something that can be abstracted and re-used for different types of beans? (I'm having a hard time thinking of a good name for this type of problem to know what to Google on). I've checked commons-beanutils and nothing popped out at me.

+4  A: 

If you are talking about comparing values, I would consider using reflection and just comparing them field by field.

Something like this:


    Field[] oldFields = oldInstance.class.getDeclaredFields();
    Field[] newFields = newInstance.class.getDeclaredFields();
    StringBuilder changes = new StringBuilder();

    Arrays.sort(oldFields);
    Arrays.sort(newFields);

    int i = 0;
    for(Field f : oldFields)
    {
       if(!f.equals(newFields[i]))
       {
          changes.append(f.getName()).append(" has changed.\n");
       }
       i++;
    }

This code hasn't been tested. You might need to get the values in the fields and compare them instead of just comparing fields to each other, but it should work in theory.

kgrad
There are some problems with this code. First, it assumes that oldFields.length is the same as newFields.length. You need some logic to determine brand new fields and fields that are missing. Finally, I wouldn't use the foreach loop when you need to increment a counter anyway; just use a 'for'.
Outlaw Programmer
they are both instances of the exact same class, how can their number of fields be different?
kgrad
Also, if this is single-threaded, you should be using StringBuilder instead of StringBuffer.
cdmckay
@cdmckay oh right, I actually meant to use StringBuilder, thanks for pointing that out. Fixed.
kgrad
@kgrad: also, that should be changes.append(f.getName()).append(" has changed.\n"); Otherwise you're creating a new StringBuilder every time through the loop.
Michael Myers
Er, not *every* time through the loop. Just when there's an unequal field. It's not really vital. But it is annoying. :)
Michael Myers
Alright thanks, i've also added in the sorting as alepuzio points out the fields are not necessarily returned in the same ordering. thanks all.
kgrad
Also, it appears (according to Javadoc) that getDeclaredFields() returns: "The elements in the array returned are not sorted and are not in any particular order." I'm not sure if they come back the in the same unsorted way each time it's called.
cdmckay
thanks for pointing out the potential for the array to be unsorted - I'd probably sort them both first just to be sure
matt b
+2  A: 

The reflection not mantains the order of the Field in next calling: it's safier order the arrays.

/*
*declarations of variables
*/

Arrays.sort(oldFields);//natural order - choice 1
Arrays.sort(newFields, new Ordinator());//custom Comparator - choice 2

/*
*logic of comparations between elements
*/

In choice 2 you can decide the logic of sorting (HOW SORTING THE ELEMENTS) with an inner class Ordinator extending Comparator.

PS the code is a draft

alepuzio
+2  A: 

We've done something similar with bean utils and it worked well. Things to consider: Do you drill down into field objects - If a Person contains an Address and the address changes do you say the address changed or that address.postalCode changed(we do)? Do you return a list propety name, old value, new value from the diff (we do)? How do you want to handle dates - if all you care about is date part then your comparison should ignore the time? How do you say which fields to ignore?

This isn't really a copy and paste answer but more of list of things that weren't immediately obvious when we wrote our differ.

As for implementation, we just have a static util method that takes two beans and a list of properties to compare and then returns a map of properties to a Pair containing the old value and the new value. Then each bean has a diff(Object o) method that calls the static util method as needed.

Patrick
+1  A: 

Good answers above.

If your data changes structurally, i.e. whole collections of fields may be relevant or not depending on others, you might want to consider differential execution.

Basically, you have a loop over the fields, and you serialize the current field values at the same time as you deserialize the prior values, comparing them as you go.

If there is a conditional test that makes a block of fields relevant or not, you serialize/deserialize the true-or-false value of the conditional test, and use that to decide whether or not to serialize and/or deserialize the affected fields. And it recurs nicely.

Just a suggestion.

Mike Dunlavey
Thanks for the link, I'll definitely look into this
matt b