From 5e2d592c2a15dd07977a1739fffbc43092eec3e7 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 21 Apr 2014 12:34:51 +1200 Subject: [PATCH] v4 refactor for diff() support --- .../ebeaninternal/server/core/DiffHelp.java | 182 +++++++++--------- .../server/core/TestDiffHelpSimple.java | 147 ++++++++++++++ .../server/core/TestDiffHelpWithEmbedded.java | 129 +++++++++++++ 3 files changed, 363 insertions(+), 95 deletions(-) create mode 100644 src/test/java/com/avaje/ebeaninternal/server/core/TestDiffHelpSimple.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/core/TestDiffHelpWithEmbedded.java diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DiffHelp.java b/src/main/java/com/avaje/ebeaninternal/server/core/DiffHelp.java index 632121642..e775b87be 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DiffHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DiffHelp.java @@ -5,10 +5,10 @@ import java.util.Map; import com.avaje.ebean.ValuePair; import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.util.ValueUtil; /** * Helper to perform a diff given two beans of the same type. @@ -19,49 +19,55 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; public class DiffHelp { - /** - * Return a map of the differences between a and b. - *

- * A and B must be of the same type. B can be null, in which case the - * 'OldValues' of a is used to compare with (as B). - *

- *

- * This intentionally does not include as OneToMany or ManyToMany - * properties. - *

- */ + /** + * Return a map of the differences between a and b. + *

+ * A and B must be of the same type. B can be null, in which case the 'dirty + * values' of a is returned. + *

+ *

+ * This intentionally does not include as OneToMany or ManyToMany properties. + *

+ */ public Map diff(Object a, Object b, BeanDescriptor desc) { - Map map = new LinkedHashMap(); + if (a instanceof EntityBean == false) { + throw new IllegalArgumentException("First bean expected to be an enhanced EntityBean? bean:"+a); + } + if (b != null) { + if (b instanceof EntityBean == false) { + throw new IllegalArgumentException("Second bean expected to be an enhanced EntityBean? bean:"+b); + } + if (!a.getClass().isAssignableFrom(b.getClass())) { + throw new IllegalArgumentException("Second bean not assignable to the first bean?"); + } + } + if (b == null) { - // get the old values from a - if (a instanceof EntityBean) { - return ((EntityBean) a)._ebean_getIntercept().getDirtyValues(); - } - return map; + return ((EntityBean) a)._ebean_getIntercept().getDirtyValues(); } + Map map = new LinkedHashMap(); + diff(null, map, (EntityBean)a, (EntityBean)b, desc); + return map; + } + + public void diff(String prefix, Map map, EntityBean first, EntityBean sec, BeanDescriptor desc) { -// if (b == null) { -// return map; -// } -// -// // check the simple properties -// BeanProperty[] base = desc.propertiesBaseScalar(); -// for (int i = 0; i < base.length; i++) { -// -// Object aval = base[i].getValue(a); -// Object bval = base[i].getValue(b); -// if (!ValueUtil.areEqual(aval, bval)) { -// map.put(base[i].getName(), new ValuePair(aval, bval)); -// } -// } -// -// diffAssocOne(a, b, desc, map); -// diffEmbedded(a, b, desc, map, oldValues); + // check the simple properties + BeanProperty[] base = desc.propertiesBaseScalar(); + for (int i = 0; i < base.length; i++) { + Object aval = base[i].getValue(first); + Object bval = base[i].getValue(sec); + if (!ValueUtil.areEqual(aval, bval)) { + String propName = (prefix == null) ? base[i].getName() : prefix + base[i].getName(); + map.put(propName, new ValuePair(aval, bval)); + } + } - return map; + diffAssocOne(prefix, first, sec, desc, map); + diffEmbedded(prefix, first, sec, desc, map); } /** @@ -71,75 +77,61 @@ public class DiffHelp { * determined to be different as is added to the map. *

*/ - private void diffEmbedded(Object a, Object b, BeanDescriptor desc, Map map, boolean oldValues) { + private void diffEmbedded(String prefix, EntityBean a, EntityBean b, BeanDescriptor desc, Map map) { -// BeanPropertyAssocOne[] emb = desc.propertiesEmbedded(); -// -// for (int i = 0; i < emb.length; i++) { -// Object aval = emb[i].getValue(a); -// Object bval = emb[i].getValue(b); -// if (oldValues) { -// bval = null;//FIXME ((EntityBean) bval)._ebean_getIntercept().getOldValues(); -// if (bval == null) { -// continue; -// } -// } -// -// if (!isBothNull(aval, bval)) { -// if (isDiffNull(aval, bval)) { -// // one of the embedded beans is null -// map.put(emb[i].getName(), new ValuePair(aval, bval)); -// -// } else { -// // if ANY of the properties in an Embedded bean is -// // different, treat the whole bean as being different -// BeanProperty[] props = emb[i].getProperties(); -// for (int j = 0; j < props.length; j++) { -// Object aEmbPropVal = props[j].getValue(aval); -// Object bEmbPropVal = props[j].getValue(bval); -// if (!ValueUtil.areEqual(aEmbPropVal, bEmbPropVal)) { -// -// // if one prop is different put the -// // embedded bean in the map -// map.put(emb[i].getName(), new ValuePair(aval, bval)); -// } -// } -// } -// } -// } + BeanPropertyAssocOne[] emb = desc.propertiesEmbedded(); + + for (int i = 0; i < emb.length; i++) { + EntityBean aval = (EntityBean)emb[i].getValue(a); + EntityBean bval = (EntityBean)emb[i].getValue(b); + + if (!isBothNull(aval, bval)) { + String propName = (prefix == null) ? emb[i].getName() : prefix + emb[i].getName(); + if (isDiffNull(aval, bval)) { + // one of the embedded beans is null + map.put(propName, new ValuePair(aval, bval)); + + } else { + // recursively diff into the embedded bean + BeanDescriptor embDesc = emb[i].getTargetDescriptor(); + diff(emb[i].getName()+".", map, aval, bval, embDesc); + } + } + } } /** * If the properties are different by null OR if the id value is different, * then add the Assoc One bean to the map. */ - private void diffAssocOne(Object a, Object b, BeanDescriptor desc, Map map) { + private void diffAssocOne(String prefix, EntityBean a, EntityBean b, BeanDescriptor desc, Map map) { BeanPropertyAssocOne[] ones = desc.propertiesOne(); -// for (int i = 0; i < ones.length; i++) { -// Object aval = ones[i].getValue(a); -// Object bval = ones[i].getValue(b); -// -// if (!isBothNull(aval, bval)) { -// if (isDiffNull(aval, bval)) { -// // one of them is/was null -// map.put(ones[i].getName(), new ValuePair(aval, bval)); -// -// } else { -// // check to see if the Id properties -// // are different -// BeanDescriptor oneDesc = ones[i].getTargetDescriptor(); -// Object aOneId = oneDesc.getId(aval); -// Object bOneId = oneDesc.getId(bval); -// -// if (!ValueUtil.areEqual(aOneId, bOneId)) { -// // the ids are different -// map.put(ones[i].getName(), new ValuePair(aval, bval)); -// } -// } -// } -// } + for (int i = 0; i < ones.length; i++) { + Object aval = ones[i].getValue(a); + Object bval = ones[i].getValue(b); + + if (!isBothNull(aval, bval)) { + String propName = (prefix == null) ? ones[i].getName() : prefix + ones[i].getName(); + if (isDiffNull(aval, bval)) { + // one of them is/was null + map.put(propName, new ValuePair(aval, bval)); + + } else { + // check to see if the Id properties + // are different + BeanDescriptor oneDesc = ones[i].getTargetDescriptor(); + Object aOneId = oneDesc.getId((EntityBean)aval); + Object bOneId = oneDesc.getId((EntityBean)bval); + + if (!ValueUtil.areEqual(aOneId, bOneId)) { + // the ids are different + map.put(propName, new ValuePair(aval, bval)); + } + } + } + } } private boolean isBothNull(Object aval, Object bval) { diff --git a/src/test/java/com/avaje/ebeaninternal/server/core/TestDiffHelpSimple.java b/src/test/java/com/avaje/ebeaninternal/server/core/TestDiffHelpSimple.java new file mode 100644 index 000000000..07ffbe588 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/core/TestDiffHelpSimple.java @@ -0,0 +1,147 @@ +package com.avaje.ebeaninternal.server.core; + +import java.sql.Date; +import java.sql.Timestamp; +import java.util.Map; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.ValuePair; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Order; +import com.avaje.tests.model.basic.Order.Status; + +public class TestDiffHelpSimple extends BaseTestCase { + + DiffHelp diffHelp = new DiffHelp(); + + long firstTime = System.currentTimeMillis()-10000; + long secondTime = System.currentTimeMillis(); + + EbeanServer server; + BeanDescriptor orderDesc; + + public TestDiffHelpSimple() { + server = Ebean.getServer(null); + SpiEbeanServer spiServer = (SpiEbeanServer)server; + orderDesc = spiServer.getBeanDescriptor(Order.class); + } + + private Order createBaseOrder(EbeanServer server) { + Order order1 = new Order(); + order1.setId(12); + order1.setCretime(new Timestamp(firstTime)); + order1.setCustomer(server.getReference(Customer.class, 1234)); + order1.setStatus(Status.NEW); + order1.setShipDate(new Date(firstTime)); + order1.setOrderDate(new Date(firstTime)); + return order1; + } + + @Test + public void testBasicChanges() { + + + Order order1 = createBaseOrder(server); + + Order order2 = new Order(); + order2.setId(14); + order2.setCretime(new Timestamp(secondTime)); + order2.setCustomer(server.getReference(Customer.class, 2133)); + order2.setStatus(Status.COMPLETE); + order2.setShipDate(new Date(secondTime)); + order2.setOrderDate(new Date(secondTime)); + + Map diff = diffHelp.diff(order1, order2, orderDesc); + + Assert.assertEquals(5, diff.size()); + + Set keySet = diff.keySet(); + Assert.assertTrue(keySet.contains("cretime")); + Assert.assertTrue(keySet.contains("status")); + Assert.assertTrue(keySet.contains("shipDate")); + Assert.assertTrue(keySet.contains("orderDate")); + Assert.assertTrue(keySet.contains("customer")); + } + + + @Test + public void testIdIgnored() { + + Order order1 = createBaseOrder(server); + Order order2 = createBaseOrder(server); + order2.setId(14); + + Map diff = diffHelp.diff(order1, order2, orderDesc); + + Assert.assertEquals(0, diff.size()); + } + + @Test + public void testSecondValueNull() { + + Order order1 = createBaseOrder(server); + + Order order2 = createBaseOrder(server); + order2.setCustomer(server.getReference(Customer.class, 2133)); + order2.setStatus(Status.COMPLETE); + order2.setShipDate(null); + + Map diff = diffHelp.diff(order1, order2, orderDesc); + + Assert.assertEquals(3, diff.size()); + + Set keySet = diff.keySet(); + Assert.assertTrue(keySet.contains("status")); + Assert.assertTrue(keySet.contains("customer")); + Assert.assertTrue(keySet.contains("shipDate")); + + ValuePair shipDatePair = diff.get("shipDate"); + Assert.assertEquals(order1.getShipDate(),shipDatePair.getNewValue()); + Assert.assertEquals(order2.getShipDate(),shipDatePair.getOldValue()); + Assert.assertNull(shipDatePair.getOldValue()); + } + + + @Test + public void testFirstValueNull() { + + Order order1 = createBaseOrder(server); + order1.setShipDate(null); + + Order order2 = createBaseOrder(server); + order2.setShipDate(new Date(secondTime)); + + Map diff = diffHelp.diff(order1, order2, orderDesc); + + Assert.assertEquals(1, diff.size()); + Set keySet = diff.keySet(); + Assert.assertTrue(keySet.contains("shipDate")); + + ValuePair shipDatePair = diff.get("shipDate"); + Assert.assertEquals(order1.getShipDate(),shipDatePair.getNewValue()); + Assert.assertEquals(order2.getShipDate(),shipDatePair.getOldValue()); + Assert.assertNull(shipDatePair.getNewValue()); + } + + @Test + public void testBothNull() { + + Order order1 = createBaseOrder(server); + order1.setShipDate(null); + + Order order2 = createBaseOrder(server); + order2.setShipDate(null); + + Map diff = diffHelp.diff(order1, order2, orderDesc); + + Assert.assertEquals(0, diff.size()); + } +} diff --git a/src/test/java/com/avaje/ebeaninternal/server/core/TestDiffHelpWithEmbedded.java b/src/test/java/com/avaje/ebeaninternal/server/core/TestDiffHelpWithEmbedded.java new file mode 100644 index 000000000..d66e806d8 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/core/TestDiffHelpWithEmbedded.java @@ -0,0 +1,129 @@ +package com.avaje.ebeaninternal.server.core; + +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.ValuePair; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.tests.model.embedded.EMain; +import com.avaje.tests.model.embedded.Eembeddable; + +public class TestDiffHelpWithEmbedded extends BaseTestCase { + + DiffHelp diffHelp = new DiffHelp(); + + EbeanServer server; + BeanDescriptor emainDesc; + + public TestDiffHelpWithEmbedded() { + server = Ebean.getServer(null); + SpiEbeanServer spiServer = (SpiEbeanServer)server; + emainDesc = spiServer.getBeanDescriptor(EMain.class); + } + + @Test + public void testChangeExistingEmbedded() { + + EMain emain1 = createEMain(); + EMain emain2 = createEMain(); + + emain2.getEmbeddable().setDescription("baz"); + + Map diff = diffHelp.diff(emain1, emain2, emainDesc); + Assert.assertEquals(1, diff.size()); + ValuePair valuePair = diff.get("embeddable.description"); + + Assert.assertNotNull(valuePair); + Assert.assertEquals("bar",valuePair.getNewValue()); + Assert.assertEquals("baz",valuePair.getOldValue()); + } + + /** + * Same result as testChangeExistingEmbedded. + */ + @Test + public void testSetViaNewEmbedded() { + + EMain emain1 = createEMain(); + EMain emain2 = createEMain(); + + Eembeddable embeddable = new Eembeddable(); + embeddable.setDescription("baz"); + emain2.setEmbeddable(embeddable); + + + Map diff = diffHelp.diff(emain1, emain2, emainDesc); + Assert.assertEquals(1, diff.size()); + ValuePair valuePair = diff.get("embeddable.description"); + + Assert.assertNotNull(valuePair); + Assert.assertEquals("bar",valuePair.getNewValue()); + Assert.assertEquals("baz",valuePair.getOldValue()); + } + + @Test + public void testFirstEmbeddedIsNull() { + + EMain emain1 = createEMain(); + emain1.setEmbeddable(null); + EMain emain2 = createEMain(); + + Map diff = diffHelp.diff(emain1, emain2, emainDesc); + Assert.assertEquals(1, diff.size()); + ValuePair valuePair = diff.get("embeddable"); + + Assert.assertNotNull(valuePair); + Assert.assertNull(valuePair.getNewValue()); + Assert.assertTrue(valuePair.getOldValue() instanceof Eembeddable); + Assert.assertEquals("bar",((Eembeddable)valuePair.getOldValue()).getDescription()); + } + + @Test + public void testSecondEmbeddedIsNull() { + + EMain emain1 = createEMain(); + EMain emain2 = createEMain(); + emain2.setEmbeddable(null); + + Map diff = diffHelp.diff(emain1, emain2, emainDesc); + Assert.assertEquals(1, diff.size()); + ValuePair valuePair = diff.get("embeddable"); + + Assert.assertNotNull(valuePair); + Assert.assertNull(valuePair.getOldValue()); + Assert.assertTrue(valuePair.getNewValue() instanceof Eembeddable); + Assert.assertEquals("bar",((Eembeddable)valuePair.getNewValue()).getDescription()); + } + + @Test + public void testBothEmbeddedIsNull() { + + EMain emain1 = createEMain(); + emain1.setEmbeddable(null); + EMain emain2 = createEMain(); + emain2.setEmbeddable(null); + + Map diff = diffHelp.diff(emain1, emain2, emainDesc); + Assert.assertEquals(0, diff.size()); + } + + private EMain createEMain() { + + EMain emain = new EMain(); + emain.setName("foo"); + emain.setVersion(13l); + + Eembeddable embeddable = new Eembeddable(); + embeddable.setDescription("bar"); + emain.setEmbeddable(embeddable); + + return emain; + } + +}