killbill-aplcache

Fix parent pointer in invoice tree node Fix issue in rebalance Initial

2/25/2014 10:16:28 PM

Details

diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
index 791f358..d18724f 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
@@ -134,6 +134,7 @@ public class NodeInterval {
                 return false;
             } else {
                 // Proposed item is the first child of an existing item with the same product info.
+                newNode.parent = this;
                 leftChild = newNode;
                 return true;
 
@@ -157,6 +158,7 @@ public class NodeInterval {
             }
 
             if (newNodeItem.getStartDate().compareTo(curChild.getStart()) < 0) {
+                newNode.parent = this;
                 newNode.rightSibling = curChild;
                 if (prevChild == null) {
                     leftChild = newNode;
@@ -174,6 +176,7 @@ public class NodeInterval {
             // The new proposed item spans over a new interval, nothing to add in the merge tree
             return false;
         } else {
+            newNode.parent = this;
             prevChild.rightSibling = newNode;
             return true;
         }
@@ -247,6 +250,17 @@ public class NodeInterval {
         return rightSibling;
     }
 
+
+    public int getNbChildren() {
+        int result = 0;
+        NodeInterval curChild = leftChild;
+        while (curChild != null) {
+            result++;
+            curChild = curChild.rightSibling;
+        }
+        return result;
+    }
+
     public List<Item> getItems() {
         return items.getItems();
     }
@@ -255,8 +269,9 @@ public class NodeInterval {
         return items.containsItem(targetId);
     }
 
-    // STEPH TODO are parents correctly maintained and/or do we need them?
     private void addNode(final NodeInterval newNode) {
+
+        newNode.parent = this;
         final Item item = newNode.getItems().get(0);
         if (leftChild == null) {
             leftChild = newNode;
@@ -293,7 +308,7 @@ public class NodeInterval {
     }
 
     /**
-     * Since items may be added out of order, there is no guarantee that we don't suddenly had a new node
+     * Since items may be added out of order, there is no guarantee that we don't suddenly have a new node
      * whose interval emcompasses cuurent node(s). In which case we need to rebalance the tree.
      *
      * @param newNode node that triggered a rebalance operation
@@ -317,7 +332,10 @@ public class NodeInterval {
             curChild = curChild.rightSibling;
         } while (curChild != null);
 
-        newNode.rightSibling = toBeRebalanced.get(toBeRebalanced.size() - 1).rightSibling;
+        newNode.parent = this;
+        final NodeInterval lastNodeToRebalance = toBeRebalanced.get(toBeRebalanced.size() - 1);
+        newNode.rightSibling = lastNodeToRebalance.rightSibling;
+        lastNodeToRebalance.rightSibling = null;
         if (prevRebalanced == null) {
             leftChild = newNode;
         } else {
@@ -326,6 +344,7 @@ public class NodeInterval {
 
         NodeInterval prev = null;
         for (NodeInterval cur : toBeRebalanced) {
+            cur.parent = newNode;
             if (prev == null) {
                 newNode.leftChild = cur;
             } else {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java b/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java
new file mode 100644
index 0000000..530f12f
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2010-2014 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.tree;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.tree.Item.ItemAction;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestNodeInterval /* extends InvoiceTestSuiteNoDB  */ {
+
+    private final UUID invoiceId = UUID.randomUUID();
+    private final UUID accountId = UUID.randomUUID();
+    private final UUID subscriptionId = UUID.randomUUID();
+    private final UUID bundleId = UUID.randomUUID();
+    private final String planName = "my-plan";
+    private final String phaseName = "my-phase";
+    private final Currency currency = Currency.USD;
+
+    @Test(groups = "fast")
+    public void testAddExistingItemSimple() {
+        final NodeInterval root = new NodeInterval();
+
+        final NodeInterval top = createNodeInterval("2014-01-01", "2014-02-01");
+        root.addExistingItem(top);
+
+        final NodeInterval firstChildLevel1 = createNodeInterval("2014-01-01", "2014-01-07");
+        final NodeInterval secondChildLevel1 = createNodeInterval("2014-01-08", "2014-01-15");
+        final NodeInterval thirdChildLevel1 = createNodeInterval("2014-01-16", "2014-02-01");
+        root.addExistingItem(firstChildLevel1);
+        root.addExistingItem(secondChildLevel1);
+        root.addExistingItem(thirdChildLevel1);
+
+        final NodeInterval firstChildLevel2 = createNodeInterval("2014-01-01", "2014-01-03");
+        final NodeInterval secondChildLevel2 = createNodeInterval("2014-01-03", "2014-01-5");
+        final NodeInterval thirdChildLevel2 = createNodeInterval("2014-01-16", "2014-01-17");
+        root.addExistingItem(firstChildLevel2);
+        root.addExistingItem(secondChildLevel2);
+        root.addExistingItem(thirdChildLevel2);
+
+        checkNode(top, 3, root, firstChildLevel1, null);
+        checkNode(firstChildLevel1, 2, top, firstChildLevel2, secondChildLevel1);
+        checkNode(secondChildLevel1, 0, top, null, thirdChildLevel1);
+        checkNode(thirdChildLevel1, 1, top, thirdChildLevel2, null);
+
+        checkNode(firstChildLevel2, 0, firstChildLevel1, null, secondChildLevel2);
+        checkNode(secondChildLevel2, 0, firstChildLevel1, null, null);
+        checkNode(thirdChildLevel2, 0, thirdChildLevel1, null, null);
+    }
+
+
+    @Test(groups = "fast")
+    public void testAddExistingItemWithRebalance() {
+        final NodeInterval root = new NodeInterval();
+
+        final NodeInterval top = createNodeInterval("2014-01-01", "2014-02-01");
+        root.addExistingItem(top);
+
+        final NodeInterval firstChildLevel2 = createNodeInterval("2014-01-01", "2014-01-03");
+        final NodeInterval secondChildLevel2 = createNodeInterval("2014-01-03", "2014-01-5");
+        final NodeInterval thirdChildLevel2 = createNodeInterval("2014-01-16", "2014-01-17");
+        root.addExistingItem(firstChildLevel2);
+        root.addExistingItem(secondChildLevel2);
+        root.addExistingItem(thirdChildLevel2);
+
+        final NodeInterval firstChildLevel1 = createNodeInterval("2014-01-01", "2014-01-07");
+        final NodeInterval secondChildLevel1 = createNodeInterval("2014-01-08", "2014-01-15");
+        final NodeInterval thirdChildLevel1 = createNodeInterval("2014-01-16", "2014-02-01");
+        root.addExistingItem(firstChildLevel1);
+        root.addExistingItem(secondChildLevel1);
+        root.addExistingItem(thirdChildLevel1);
+
+        checkNode(top, 3, root, firstChildLevel1, null);
+        checkNode(firstChildLevel1, 2, top, firstChildLevel2, secondChildLevel1);
+        checkNode(secondChildLevel1, 0, top, null, thirdChildLevel1);
+        checkNode(thirdChildLevel1, 1, top, thirdChildLevel2, null);
+
+        checkNode(firstChildLevel2, 0, firstChildLevel1, null, secondChildLevel2);
+        checkNode(secondChildLevel2, 0, firstChildLevel1, null, null);
+        checkNode(thirdChildLevel2, 0, thirdChildLevel1, null, null);
+    }
+
+
+
+    private void checkNode(final NodeInterval node, final int expectedChildren, final NodeInterval expectedParent, final NodeInterval expectedLeftChild, final NodeInterval expectedRightSibling) {
+        assertEquals(node.getNbChildren(), expectedChildren);
+        assertEquals(node.getParent(), expectedParent);
+        assertEquals(node.getRightSibling(), expectedRightSibling);
+        assertEquals(node.getLeftChild(), expectedLeftChild);
+        assertEquals(node.getLeftChild(), expectedLeftChild);
+    }
+
+    private NodeInterval createNodeInterval(final String startDate, final String endDate) {
+        return new NodeInterval(null, createItem(startDate, endDate));
+    }
+
+    private Item createItem(final String startDate, final String endDate) {
+        final BigDecimal amount = BigDecimal.TEN;
+        final BigDecimal rate = BigDecimal.TEN;
+        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, new LocalDate(startDate), new LocalDate(endDate), amount, rate, currency);
+        Item item = new Item(invoiceItem, ItemAction.ADD);
+        return item;
+    }
+}