10

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.

matt b
  • 138,234
  • 66
  • 282
  • 345

6 Answers6

6

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
  • 4,672
  • 7
  • 36
  • 57
  • 1
    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'. – Tim Frey Feb 23 '09 at 18:11
  • they are both instances of the exact same class, how can their number of fields be different? – kgrad Feb 23 '09 at 18:18
  • Also, if this is single-threaded, you should be using StringBuilder instead of StringBuffer. – cdmckay Feb 23 '09 at 18:37
  • @cdmckay oh right, I actually meant to use StringBuilder, thanks for pointing that out. Fixed. – kgrad Feb 23 '09 at 18:39
  • @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 Feb 23 '09 at 18:42
  • 1
    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 Feb 23 '09 at 18:44
  • 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 Feb 23 '09 at 18:45
  • 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 Feb 23 '09 at 18:47
  • 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 Feb 23 '09 at 18:50
  • There is another problem - you can't sort them like this because Field class does not implements Comparable interface. – xMort Jul 31 '14 at 08:03
3

These libraries should help.

https://code.google.com/p/beandiff/ - An annotation based bean diffing library. Apache License 2.0

https://github.com/SQiShER/java-object-diff/ - A bean differ based on Visitor pattern. Apache License 2.0

We had a requirement to generate difference between beans in json format for auditing purpose. We ended up implementing it using beandiff library.

** EDIT ** This looks like a newer option. I have not used it though.

http://beandiff.org/

Hope it helps.

krishnakumarp
  • 8,967
  • 3
  • 49
  • 55
3

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
  • 1,382
  • 2
  • 28
  • 38
3

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
  • 3,901
  • 1
  • 25
  • 30
  • I am working on a similar requirement. The solution you outlined looks interesting. Would you be able to share the code or algorithm details? Thanks. – krishnakumarp May 26 '12 at 04:53
  • 1
    I wish I could, but it was two jobs ago. However, I wrote an 'inspired by' sample code for an interview two years ago. It doesn't do as much as the original, but can be useful for inspiration. [Download it](http://stanford.edu/~pradtke/ObjectDiffer.zip) or [browse](http://stanford.edu/~pradtke/ObjectDiffer/). – Patrick May 29 '12 at 18:13
1

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.

Community
  • 1
  • 1
Mike Dunlavey
  • 40,059
  • 14
  • 91
  • 135
0

Solution using reflection and standard data structures.

    Field[] declaredFields = ClassOne.class.getDeclaredFields();
    Field[] declaredFields2 = ClassTwo.class.getDeclaredFields();
    ArrayList<String> one = new ArrayList<String>();
    ArrayList<String> two = new ArrayList<String>();
    for (Field field : declaredFields)
    {
        one.add(field.getName());
    }

    for (Field field : declaredFields2)
    {
        two.add(field.getName());
    }

    List<String> preone = (List<String>)one.clone();

    one.removeAll(two);
    two.removeAll(preone);
    Collections.sort(one);
    Collections.sort(two);

    System.out.println("fields only in One : " + one);
    System.out.println("fields only in Two : " + two);
WizardsOfWor
  • 2,974
  • 29
  • 23