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