killbill-aplcache

fixes #170 (Hopefully this is time this is for real) Modified

5/8/2014 12:45:02 PM

Details

diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsInterval.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsInterval.java
index 672045b..ff15213 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsInterval.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsInterval.java
@@ -17,17 +17,16 @@
 package org.killbill.billing.invoice.tree;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.ListIterator;
-import java.util.Set;
+import java.util.Map;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.invoice.tree.Item.ItemAction;
 
 import com.google.common.base.Preconditions;
@@ -92,68 +91,83 @@ public class ItemsInterval {
      * @param mergeMode
      * @return whether or not the parent should ignore the interval covered by the child interval
      */
-    public boolean buildFromItems(final List<Item> output, final boolean mergeMode) {
-        final ItemWithResult itemWithResult  = getResultingItem(mergeMode);
-        if (itemWithResult.getItem() != null) {
-            output.add(itemWithResult.getItem());
+    public void buildFromItems(final List<Item> output, final boolean mergeMode) {
+        final Item item = getResultingItem(mergeMode);
+        if (item != null) {
+            output.add(item);
+        }
+    }
+
+    /**
+     * Remove all the cancelling pairs (ADD/CANCEL) for which CANCEL linkedId points to ADD id.
+     *
+     * @return true if there is no more items
+     */
+    public boolean mergeCancellingPairs() {
+
+        final Map<UUID, List<Item>> tmp = new HashMap<UUID, List<Item>>();
+        for (Item cur : items) {
+            final UUID idToConsider = (cur.getAction() == ItemAction.ADD) ? cur.getId() : cur.getLinkedId();
+            List<Item> listForItem = tmp.get(idToConsider);
+            if (listForItem == null) {
+                listForItem = new ArrayList<Item>(2);
+                tmp.put(idToConsider, listForItem);
+            }
+            listForItem.add(cur);
+        }
+
+        for (List<Item> listForIds : tmp.values()) {
+            if (listForIds.size() == 2) {
+                items.remove(listForIds.get(0));
+                items.remove(listForIds.get(1));
+            }
         }
-        return itemWithResult.isIgnorePeriod();
+        return items.size() == 0;
     }
 
-    private ItemWithResult getResultingItem(final boolean mergeMode) {
+    public Iterable<Item> get_ADD_items() {
+        return Iterables.filter(items, new Predicate<Item>() {
+            @Override
+            public boolean apply(final Item input) {
+                return input.getAction() == ItemAction.ADD;
+            }
+        });
+    }
+
+    public NodeInterval getNodeInterval() {
+        return interval;
+    }
+
+    private Item getResultingItem(final boolean mergeMode) {
         return mergeMode ? getResulting_CANCEL_Item() : getResulting_ADD_Item();
     }
 
-    private ItemWithResult getResulting_CANCEL_Item() {
+    private Item getResulting_CANCEL_Item() {
         Preconditions.checkState(items.size() == 0 || items.size() == 1);
-        return new ItemWithResult(Iterables.tryFind(items, new Predicate<Item>() {
+        return Iterables.tryFind(items, new Predicate<Item>() {
             @Override
             public boolean apply(final Item input) {
                 return input.getAction() == ItemAction.CANCEL;
             }
-        }).orNull(), true);
+        }).orNull();
     }
 
-
-    private ItemWithResult getResulting_ADD_Item() {
-
-        final Set<UUID> repairedIds = new HashSet<UUID>();
-        final ListIterator<Item> it = items.listIterator(items.size());
+    private Item getResulting_ADD_Item() {
 
         //
-        // We can have an {0,n} pairs of ADD/CANCEL (cancelling each other), and in addition to that we could have:
-        // a - One ADD that has not been cancelled => We want to return that item and let the parent (NodeInterval) know that it should ignore
-        //   the period as this is accounted for with the item returned
-        // b - One CANCEL => We return NO item but we also want the parent to know that it should ignore
-        //   the period as this is accounted -- period should remain unbilled.
-        // c - nothing => The parent should NOT ignore the period as there is no child element that can account for it.
+        // At this point we pruned the items so that we can have either:
+        // - 2 items (ADD + CANCEL, where CANCEL does NOT point to ADD item-- otherwise this is a cancelling pair that
+        //            would have been removed in mergeCancellingPairs logic)
+        // - 1 ADD item, simple enough we return it
+        // - 1 CANCEL, there is nothing to return but the period will be ignored by the parent
+        // - Nothing at all; this valid, this just means its original items got removed during mergeCancellingPairs logic,
+        //   but its NodeInterval has children so it could not be deleted.
         //
-        while (it.hasPrevious()) {
-            final Item cur = it.previous();
-            switch (cur.getAction()) {
-                case ADD:
-                    // If we found a CANCEL item pointing to that item then don't return it as it was repair (full repair scenario)
-                    if (!repairedIds.contains(cur.getId())) {
-                        // Case a
-                        return new ItemWithResult(cur, true);
-                    } else {
-                        // Remove from the list so we know if there is anything else (case b or c)
-                        repairedIds.remove(cur.getId());
-                    }
-
-                case CANCEL:
-                    // In all cases populate the set with the id of target item being repaired
-                    if (cur.getLinkedId() != null) {
-                        repairedIds.add(cur.getLinkedId());
-                    }
-                    break;
-            }
-        }
-        return repairedIds.size() > 0 ?
-               new ItemWithResult(null, true) :  /* case b */
-               new ItemWithResult(null, false); /* case c */
-    }
+        Preconditions.checkState(items.size() <= 2);
 
+        final Item item = items.size() > 0 && items.get(0).getAction() == ItemAction.ADD ? items.get(0) : null;
+        return item;
+    }
 
     // Just ensure that ADD items precedes CANCEL items
     public void insertSortedItem(final Item item) {
@@ -179,6 +193,24 @@ public class ItemsInterval {
         items.clear();
     }
 
+    public void remove(final Item item) {
+        items.remove(item);
+    }
+
+    public Item getCancelledItemIfExists(final UUID targetId) {
+        final Item item = Iterables.tryFind(items, new Predicate<Item>() {
+            @Override
+            public boolean apply(final Item input) {
+                return input.getAction() == ItemAction.CANCEL && input.getLinkedId().equals(targetId);
+            }
+        }).orNull();
+        return item;
+    }
+
+    public int size() {
+        return items.size();
+    }
+
     /**
      * Creates a new item.
      * <p/>
@@ -195,8 +227,7 @@ public class ItemsInterval {
      */
     private Item createNewItem(LocalDate startDate, LocalDate endDate, final boolean mergeMode) {
 
-        final ItemWithResult itemWithResult  = getResultingItem(mergeMode);
-        final Item item = itemWithResult.getItem();
+        final Item item = getResultingItem(mergeMode);
         if (item == null) {
             return null;
         }
@@ -208,19 +239,4 @@ public class ItemsInterval {
         return result;
     }
 
-    private final class ItemWithResult {
-        private final Item item;
-        private final boolean ignorePeriod;
-
-        private ItemWithResult(final Item item, final boolean ignorePeriod) {
-            this.item = item;
-            this.ignorePeriod = ignorePeriod;
-        }
-        public Item getItem() {
-            return item;
-        }
-        public boolean isIgnorePeriod() {
-            return ignorePeriod;
-        }
-    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java
index a43de4b..c215a5c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java
@@ -19,14 +19,15 @@ package org.killbill.billing.invoice.tree;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.util.jackson.ObjectMapper;
 
-import com.fasterxml.jackson.annotation.JsonIdentityInfo;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.google.common.base.Preconditions;
@@ -75,6 +76,10 @@ public class ItemsNodeInterval extends NodeInterval {
      * @param output result list of built items
      */
     public void buildForExistingItems(final List<Item> output) {
+
+        // We start by pruning useless entries to simplify the build phase.
+        pruneTree();
+
         build(new BuildNodeCallback() {
             @Override
             public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
@@ -83,13 +88,14 @@ public class ItemsNodeInterval extends NodeInterval {
             }
 
             @Override
-            public boolean onLastNode(final NodeInterval curNode) {
+            public void onLastNode(final NodeInterval curNode) {
                 final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
-                return items.buildFromItems(output, false);
+                items.buildFromItems(output, false);
             }
         });
     }
 
+
     /**
      * The merge tree is initially constructed by flattening all the existing items and reversing them (CANCEL node).
      * <p/>
@@ -113,19 +119,21 @@ public class ItemsNodeInterval extends NodeInterval {
      * @param output result list of built items
      */
     public void mergeExistingAndProposed(final List<Item> output) {
-        build(new BuildNodeCallback() {
-            @Override
-            public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
-                final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
-                items.buildForMissingInterval(startDate, endDate, output, true);
-            }
 
-            @Override
-            public boolean onLastNode(final NodeInterval curNode) {
-                final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
-                return items.buildFromItems(output, true);
-            }
-        });
+        build(new BuildNodeCallback() {
+                  @Override
+                  public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
+                      final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
+                      items.buildForMissingInterval(startDate, endDate, output, true);
+                  }
+
+                  @Override
+                  public void onLastNode(final NodeInterval curNode) {
+                      final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
+                      items.buildFromItems(output, true);
+                  }
+              }
+             );
     }
 
     /**
@@ -160,7 +168,7 @@ public class ItemsNodeInterval extends NodeInterval {
      *
      * @param newNode a new proposed item
      * @return true if the item was merged and will trigger a repair or false if the proposed item should be kept as such
-     *         and no repair generated.
+     * and no repair generated.
      */
     public boolean addProposedItem(final ItemsNodeInterval newNode) {
 
@@ -262,4 +270,66 @@ public class ItemsNodeInterval extends NodeInterval {
         items.setAdjustment(amount, linkedId);
     }
 
+    //
+    // Before we build the tree, we make a first pass at removing full repaired items; those can come in two shapes:
+    // Case A - The first one, is the mergeCancellingPairs logics which simply look for one CANCEL pointing to one ADD item in the same
+    //   NodeInterval; this is fairly simple, and *only* requires removing those items and remove the interval from the tree when
+    //   it has no more leaves and no more items.
+    // Case B - This is a bit more involved: We look for full repair that happened in pieces; this will translate to an ADD element of a NodeInterval,
+    // whose children completely map the interval (isPartitionedByChildren) and where each child will have a CANCEL item pointing to the ADD.
+    // When we detect such nodes, we delete both the ADD in the parent interval and the CANCEL in the children
+    //
+    private void pruneTree() {
+        walkTree(new WalkCallback() {
+            @Override
+            public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent) {
+
+                if(curNode.isRoot()) {
+                    return;
+                }
+
+                final ItemsInterval curNodeItems = ((ItemsNodeInterval) curNode).getItemsInterval();
+                // Case A:
+                final boolean isEmpty = curNodeItems.mergeCancellingPairs();
+                if (isEmpty && curNode.getLeftChild() == null) {
+                    curNode.getParent().removeChild(curNode);
+                }
+
+                if (!curNode.isPartitionedByChildren()) {
+                    return;
+                }
+
+                // Case B -- look for such case, and if found (foundFullRepairByParts) we fix them below.
+                final Iterator<Item> it =  curNodeItems.get_ADD_items().iterator();
+                while (it.hasNext()) {
+
+                    final Item curAddItem = it.next();
+
+                    NodeInterval curChild = curNode.getLeftChild();
+                    Map<ItemsInterval, Item> toBeRemoved = new HashMap<ItemsInterval, Item>();
+                    boolean foundFullRepairByParts = true;
+                    while (curChild != null) {
+                        final ItemsInterval curChildItems = ((ItemsNodeInterval) curChild).getItemsInterval();
+                        Item cancellingItem = curChildItems.getCancelledItemIfExists(curAddItem.getId());
+                        if (cancellingItem == null) {
+                            foundFullRepairByParts = false;
+                            break;
+                        }
+                        toBeRemoved.put(curChildItems, cancellingItem);
+                        curChild = curChild.getRightSibling();
+                    }
+
+                    if (foundFullRepairByParts) {
+                        for (ItemsInterval curItemsInterval : toBeRemoved.keySet()) {
+                            curItemsInterval.remove(toBeRemoved.get(curItemsInterval));
+                            if (curItemsInterval.size() == 0) {
+                                curNode.removeChild(curItemsInterval.getNodeInterval());
+                            }
+                        }
+                        curNodeItems.remove(curAddItem);
+                    }
+                }
+            }
+        });
+    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/NodeInterval.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/NodeInterval.java
index f96b256..884e31d 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/NodeInterval.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/NodeInterval.java
@@ -51,12 +51,13 @@ public class NodeInterval {
      * @param callback the callback which perform the build logic.
      * @return whether or not the parent NodeInterval should ignore the period covered by the child (NodeInterval)
      */
-    public boolean build(final BuildNodeCallback callback) {
+    public void build(final BuildNodeCallback callback) {
 
         Preconditions.checkNotNull(callback);
 
         if (leftChild == null) {
-            return callback.onLastNode(this);
+            callback.onLastNode(this);
+            return;
         }
 
         LocalDate curDate = start;
@@ -65,10 +66,9 @@ public class NodeInterval {
             if (curChild.getStart().compareTo(curDate) > 0) {
                 callback.onMissingInterval(this, curDate, curChild.getStart());
             }
-            boolean ignorePeriod = curChild.build(callback);
-            if (ignorePeriod) {
-                curDate = curChild.getEnd();
-            }
+            curChild.build(callback);
+            // Note that skip to child endDate, meaning that we always consider the child [start end]
+            curDate = curChild.getEnd();
             curChild = curChild.getRightSibling();
         }
 
@@ -76,7 +76,7 @@ public class NodeInterval {
         if (curDate.compareTo(end) < 0) {
             callback.onMissingInterval(this, curDate, end);
         }
-        return true;
+        return;
     }
 
     /**
@@ -85,7 +85,7 @@ public class NodeInterval {
      * @param newNode  the node to be added
      * @param callback the callback that will allow to specify insertion and return behavior.
      * @return true if node was inserted. Note that this is driven by the callback, this method is generic
-     *         and specific behavior can be tuned through specific callbacks.
+     * and specific behavior can be tuned through specific callbacks.
      */
     public boolean addNode(final NodeInterval newNode, final AddNodeCallback callback) {
 
@@ -149,6 +149,44 @@ public class NodeInterval {
         }
     }
 
+    public void removeChild(final NodeInterval toBeRemoved) {
+
+        NodeInterval prevChild = null;
+        NodeInterval curChild = leftChild;
+        while (curChild != null) {
+            if (curChild.isSame(toBeRemoved)) {
+                if (prevChild == null) {
+                    leftChild = curChild.getRightSibling();
+                } else {
+                    prevChild.rightSibling = curChild.getRightSibling();
+                }
+                break;
+            }
+            prevChild = curChild;
+            curChild = curChild.getRightSibling();
+        }
+
+    }
+
+    @JsonIgnore
+    public boolean isPartitionedByChildren() {
+
+        if (leftChild == null) {
+            return false;
+        }
+
+        LocalDate curDate = start;
+        NodeInterval curChild = leftChild;
+        while (curChild != null) {
+            if (curChild.getStart().compareTo(curDate) > 0) {
+                return false;
+            }
+            curDate = curChild.getEnd();
+            curChild = curChild.getRightSibling();
+        }
+        return (curDate.compareTo(end) == 0);
+    }
+
     /**
      * Return the first node satisfying the date and match callback.
      *
@@ -227,7 +265,6 @@ public class NodeInterval {
         }
     }
 
-
     public boolean isItemContained(final NodeInterval newNode) {
         return (newNode.getStart().compareTo(start) >= 0 &&
                 newNode.getStart().compareTo(end) <= 0 &&
@@ -243,6 +280,13 @@ public class NodeInterval {
     }
 
     @JsonIgnore
+    public boolean isSame(final NodeInterval otherNode) {
+        return ((otherNode.getStart().compareTo(start) == 0 &&
+                 otherNode.getEnd().compareTo(end) == 0) &&
+                otherNode.getParent().equals(parent));
+    }
+
+    @JsonIgnore
     public boolean isRoot() {
         return parent == null;
     }
@@ -338,6 +382,7 @@ public class NodeInterval {
      * Provides callback for walking the tree.
      */
     public interface WalkCallback {
+
         public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent);
     }
 
@@ -345,6 +390,7 @@ public class NodeInterval {
      * Provides custom logic for the search.
      */
     public interface SearchCallback {
+
         /**
          * Custom logic to decide which node to return.
          *
@@ -372,10 +418,8 @@ public class NodeInterval {
          * Called when we hit a node with no children
          *
          * @param curNode current node
-         * @return true if the curNode's parent should ignore that interval -- accounted for by curChild.
-         *
          */
-        public boolean onLastNode(NodeInterval curNode);
+        public void onLastNode(NodeInterval curNode);
     }
 
     /**
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index 9b35daf..9008613 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -47,6 +47,7 @@ import org.killbill.billing.invoice.MockBillingEventSet;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.invoice.model.DefaultInvoicePayment;
@@ -913,7 +914,9 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         final MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
         final Plan plan = new MockPlan(phase);
 
-        final Invoice existingInvoice = new DefaultInvoice(UUID.randomUUID(), accountId, null, clock.getUTCToday(), new LocalDate(2013, 7, 15), currency, false);
+        final LocalDate targetDate = new LocalDate(2013, 10, 30);
+
+        final Invoice existingInvoice = new DefaultInvoice(UUID.randomUUID(), accountId, null, clock.getUTCToday(), targetDate, currency, false);
 
         // Set the existing recurring invoice item 2013/06/15 - 2013/07/15
         final LocalDate startDate = new LocalDate(2013, 06, 15);
@@ -933,6 +936,9 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         existingInvoice.addInvoiceItem(repairItem);
 
         // Create the billing event associated with the subscription creation
+        //
+        // Note : this is the interesting part of the test; it does not provide the blocking billing events, which force invoice
+        // to un repair what was previously repaired.
         final BillingEventSet events = new MockBillingEventSet();
         final BillingEvent event = invoiceUtil.createMockBillingEvent(null, subscription, new DateTime("2013-06-15", DateTimeZone.UTC),
                                                                       plan, phase,
@@ -945,20 +951,34 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         existingInvoices.add(existingInvoice);
 
         // Generate a new invoice
-        final Invoice invoice = generator.generateInvoice(accountId, events, existingInvoices, new LocalDate(2013, 10, 30), currency, internalCallContext);
+
+        final Invoice invoice = generator.generateInvoice(accountId, events, existingInvoices, targetDate, currency, internalCallContext);
         assertEquals(invoice.getNumberOfItems(), 7);
+        assertEquals(invoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
         assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), new LocalDate(2013, 6, 15));
         assertEquals(invoice.getInvoiceItems().get(0).getEndDate(), new LocalDate(2013, 7, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(1).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
         assertEquals(invoice.getInvoiceItems().get(1).getStartDate(), new LocalDate(2013, 6, 15));
         assertEquals(invoice.getInvoiceItems().get(1).getEndDate(), new LocalDate(2013, 6, 21));
+
+        assertEquals(invoice.getInvoiceItems().get(2).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
         assertEquals(invoice.getInvoiceItems().get(2).getStartDate(), new LocalDate(2013, 6, 26));
         assertEquals(invoice.getInvoiceItems().get(2).getEndDate(), new LocalDate(2013, 7, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(3).getInvoiceItemType(), InvoiceItemType.RECURRING);
         assertEquals(invoice.getInvoiceItems().get(3).getStartDate(), new LocalDate(2013, 7, 15));
         assertEquals(invoice.getInvoiceItems().get(3).getEndDate(), new LocalDate(2013, 8, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(4).getInvoiceItemType(), InvoiceItemType.RECURRING);
         assertEquals(invoice.getInvoiceItems().get(4).getStartDate(), new LocalDate(2013, 8, 15));
         assertEquals(invoice.getInvoiceItems().get(4).getEndDate(), new LocalDate(2013, 9, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(5).getInvoiceItemType(), InvoiceItemType.RECURRING);
         assertEquals(invoice.getInvoiceItems().get(5).getStartDate(), new LocalDate(2013, 9, 15));
         assertEquals(invoice.getInvoiceItems().get(5).getEndDate(), new LocalDate(2013, 10, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(6).getInvoiceItemType(), InvoiceItemType.RECURRING);
         assertEquals(invoice.getInvoiceItems().get(6).getStartDate(), new LocalDate(2013, 10, 15));
         assertEquals(invoice.getInvoiceItems().get(6).getEndDate(), new LocalDate(2013, 11, 15));
 
@@ -966,10 +986,8 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         existingInvoices.add(invoice);
 
         // Generate next invoice (no-op)
-        final Invoice newInvoice = generator.generateInvoice(accountId, events, existingInvoices, new LocalDate(2013, 10, 30), currency, internalCallContext);
-        assertEquals(newInvoice.getNumberOfItems(), 1);
-        assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), new LocalDate(2013, 6, 15));
-        assertEquals(invoice.getInvoiceItems().get(0).getEndDate(), new LocalDate(2013, 7, 15));
+        final Invoice newInvoice = generator.generateInvoice(accountId, events, existingInvoices, targetDate, currency, internalCallContext);
+        assertNull(newInvoice);
     }
 
     private void distributeItems(final List<Invoice> invoices) {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestNodeInterval.java b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestNodeInterval.java
index bbfeafd..ba35fff 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestNodeInterval.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestNodeInterval.java
@@ -160,9 +160,9 @@ public class TestNodeInterval /* extends InvoiceTestSuiteNoDB  */ {
             }
 
             @Override
-            public boolean onLastNode(final NodeInterval curNode) {
+            public void onLastNode(final NodeInterval curNode) {
                 // Nothing
-                return true;
+                return;
             }
         });
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
index b02c83b..1a12fdb 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -218,7 +218,6 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB  */ {
         expectedResult.add(expected1);
         expectedResult.add(expected2);
 
-        // First test with items in order
         SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
         tree.addItem(first);
         tree.addItem(second);