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;
+ }
+
+}