Java bean differences

From time to time your application needs to work on certain data objects or entities. Depending on the situation and the complexity, you want to have logging that will help analyzing what the application was doing during a past time interval. Typically you want to log errors, user input, decisions that the application makes, results of whatever the application was computing.

In every step one is dealing with data. Today I would like to give a quick tip on generic logging of data containers, roughly speaking beans: logging the difference of two objects. There are many important use-cases where one wants to have a comparison of two objects. Typically logging both of them via toString() is not so helpful, since the reader of the log file will have to figure out the differences.

The general solution I propose makes use of two powerful libraries, commons-beanutils and guava. The former will figure out the properties of a bean, the later is responsible for determining the differences. Let’s have a look at some code:

/* (C) 2016 Gooby. All rights reserved. */
package plz.gooby;

import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;

import org.apache.commons.beanutils.BeanMap;

import java.util.Map;

/**
 * Differences of beans.
 */
public final class BeanDifferences {

    private BeanDifferences() {
    }

    public static BeanDifference differenceOf(Object o1, Object o2) {
        return new BeanDifference(o1, o2);
    }

    /**
     * Wrapper class for object difference. Describes differences in its {@link BeanDifference#toString()} method.
     */
    private static final class BeanDifference {
        final Object o1;
        final Object o2;

        private BeanDifference(Object o1, Object o2) {
            this.o1 = o1;
            this.o2 = o2;
        }

        @Override
        public String toString() {
            try {
                return describeDifferences();
            } catch (Throwable t) {
                return "ERROR: cannot determine difference " + t;
            }
        }

        private String describeDifferences() {
            BeanMap beanMap1 = new BeanMap(o1);
            BeanMap beanMap2 = new BeanMap(o2);
            MapDifference<Object, Object> difference = Maps.difference(beanMap1, beanMap2);
            StringBuilder sb = new StringBuilder("[");

            for (Map.Entry<Object, Object> differenceEntry : difference.entriesOnlyOnLeft().entrySet()) {
                Object key = differenceEntry.getKey();
                Object value = differenceEntry.getValue();
                sb.append(key).append(": ").append(value).append("<<").append(" ").append(";");
            }
            for (Map.Entry<Object, MapDifference.ValueDifference<Object>> differenceEntry : difference.entriesDiffering().entrySet()) {
                Object key = differenceEntry.getKey();
                MapDifference.ValueDifference<Object> value = differenceEntry.getValue();
                sb.append(key).append(": ").append(value.leftValue()).append("<->").append(value.rightValue()).append(";");
            }
            for (Map.Entry<Object, Object> differenceEntry : difference.entriesOnlyOnRight().entrySet()) {
                Object key = differenceEntry.getKey();
                Object value = differenceEntry.getValue();
                sb.append(key).append(": ").append(value).append(">>").append(" ").append(";");
            }
            sb.append("]");
            return sb.toString();
        }
    }
}

Seeing this code in action can be easily demonstrated with a couple of JUnit tests

/* (C) 2016 Gooby. All rights reserved. */
package plz.gooby;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Point;
import java.net.URL;
import java.time.LocalDate;
import java.util.UUID;

/**
 * Some tests.
 */
public class BeanDifferencesTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(BeanDifferencesTest.class);

    @Test
    public void testDifferenceOfPoints() throws Exception {
        Point p1 = new Point(42, 24);
        Point p2 = new Point(42, 3);
        LOGGER.info("Difference of {} and {} : {}", p1, p2, BeanDifferences.differenceOf(p1, p2));
        LOGGER.info("Difference of {} and {} : {}", p1, p1, BeanDifferences.differenceOf(p1, p1));
    }

    @Test
    public void testDifferenceOfDates() throws Exception {
        LocalDate d1 = LocalDate.ofYearDay(2016, 15);
        LocalDate d2 = LocalDate.ofYearDay(2016, 16);
        LOGGER.info("Difference of {} and {} : {}", d1, d2, BeanDifferences.differenceOf(d1, d2));
    }

    @Test
    public void testDifferenceOfDateAndPoint() throws Exception {
        LocalDate d = LocalDate.ofYearDay(2016, 15);
        Point p = new Point(1, -1);
        LOGGER.info("Difference of {} and {} : {}", d, p, BeanDifferences.differenceOf(d, p));
    }

    @Test
    public void testDifferenceOfUUIDs() throws Exception {
        UUID u1 = UUID.randomUUID();
        UUID u2 = UUID.randomUUID();
        LOGGER.info("Difference of {} and {} : {}", u1, u1, BeanDifferences.differenceOf(u1, u2));
    }

    @Test
    public void testDifferenceOfULS() throws Exception {
        URL u1 = new URL("https://google.com");
        URL u2 = new URL("http://google.ca");
        LOGGER.info("Difference of {} and {} : {}", u1, u1, BeanDifferences.differenceOf(u1, u2));
    }
}

The output:

Difference of java.awt.Point[x=42,y=24] and java.awt.Point[x=42,y=3] : [y: 24.0<->3.0;location: java.awt.Point[x=42,y=24]<->java.awt.Point[x=42,y=3];]
Difference of 2016-01-15 and 2016-01-16 : [dayOfWeek: FRIDAY<->SATURDAY;dayOfMonth: 15<->16;dayOfYear: 15<->16;]
Difference of java.awt.Point[x=42,y=24] and java.awt.Point[x=42,y=24] : []
Difference of 2016-01-15 and java.awt.Point[x=1,y=-1] : [dayOfWeek: FRIDAY<< ;month: JANUARY<< ;dayOfMonth: 15<< ;dayOfYear: 15<< ;era: CE<< ;year: 2016<< ;monthValue: 1<< ;chronology: ISO<< ;leapYear: true<< ;class: class java.time.LocalDate<->class java.awt.Point;x: 1.0>> ;y: -1.0>> ;location: java.awt.Point[x=1,y=-1]>> ;]
Difference of e6b785e1-1ffd-4877-b8f0-03d72a931689 and e6b785e1-1ffd-4877-b8f0-03d72a931689 : [mostSignificantBits: -1821840322297247625<->4456614852182232643;leastSignificantBits: -5120588553653119351<->-7111626394932247653;]
Difference of https://google.com and https://google.com : [defaultPort: 443<->80;protocol: https<->http;authority: google.com<->google.ca;host: google.com<->google.ca;content: sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@7364985f<->sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@5d20e46;]

That’s it. feel free to try it out.

Advertisements

About goobypl5

pizza baker, autodidact, particle physicist
This entry was posted in Programming and tagged , . Bookmark the permalink.

Share your thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s