From 326a85a9a47606de6c6a656f4b30bf480ed58166 Mon Sep 17 00:00:00 2001 From: Roman Leventov Date: Tue, 22 Aug 2017 19:43:29 -0500 Subject: [PATCH] Add Offset.reset() and remove unused Offset implementations (#4706) * Add Offset.reset() and remove unused Offset implementations * Fix BitmapOffset * Address comments --- .../java/io/druid/segment/BitmapOffset.java | 80 +++++----- .../java/io/druid/segment/FilteredOffset.java | 6 + ...ayBasedOffset.java => NoFilterOffset.java} | 57 +++---- .../segment/QueryableIndexStorageAdapter.java | 56 +------ .../segment/data/IntersectingOffset.java | 104 ------------- .../java/io/druid/segment/data/Offset.java | 8 + .../io/druid/segment/data/UnioningOffset.java | 142 ------------------ .../segment/data/IntersectingOffsetTest.java | 101 ------------- .../segment/data/UnioningOffsetTest.java | 119 --------------- 9 files changed, 89 insertions(+), 584 deletions(-) rename processing/src/main/java/io/druid/segment/{data/ArrayBasedOffset.java => NoFilterOffset.java} (57%) delete mode 100644 processing/src/main/java/io/druid/segment/data/IntersectingOffset.java delete mode 100644 processing/src/main/java/io/druid/segment/data/UnioningOffset.java delete mode 100644 processing/src/test/java/io/druid/segment/data/IntersectingOffsetTest.java delete mode 100644 processing/src/test/java/io/druid/segment/data/UnioningOffsetTest.java diff --git a/processing/src/main/java/io/druid/segment/BitmapOffset.java b/processing/src/main/java/io/druid/segment/BitmapOffset.java index 821f4227f02d..68d3ba49215d 100644 --- a/processing/src/main/java/io/druid/segment/BitmapOffset.java +++ b/processing/src/main/java/io/druid/segment/BitmapOffset.java @@ -23,7 +23,6 @@ import io.druid.collections.bitmap.ImmutableBitmap; import io.druid.collections.bitmap.MutableBitmap; import io.druid.collections.bitmap.WrappedImmutableRoaringBitmap; -import io.druid.collections.bitmap.WrappedRoaringBitmap; import io.druid.extendedset.intset.EmptyIntIterator; import io.druid.java.util.common.RE; import io.druid.query.monomorphicprocessing.RuntimeShapeInspector; @@ -123,12 +122,13 @@ private static String factorizeFullness(long bitmapCardinality, long numRows) } } - final IntIterator itr; - final String fullness; + private final String fullness; + private IntIterator iterator; + private final IntIterator iteratorForReset; + private final int valueForReset; + private int value; - int val; - - public static IntIterator getReverseBitmapOffsetIterator(ImmutableBitmap bitmapIndex) + static IntIterator getReverseBitmapOffsetIterator(ImmutableBitmap bitmapIndex) { ImmutableBitmap roaringBitmap = bitmapIndex; if (!(bitmapIndex instanceof WrappedImmutableRoaringBitmap)) { @@ -144,20 +144,18 @@ public static IntIterator getReverseBitmapOffsetIterator(ImmutableBitmap bitmapI public static BitmapOffset of(ImmutableBitmap bitmapIndex, boolean descending, long numRows) { - if (bitmapIndex instanceof WrappedImmutableRoaringBitmap || - bitmapIndex instanceof WrappedRoaringBitmap || - descending) { - return new RoaringBitmapOffset(bitmapIndex, descending, numRows); - } else { - return new BitmapOffset(bitmapIndex, descending, numRows); - } + return new BitmapOffset(bitmapIndex, descending, numRows); } private BitmapOffset(ImmutableBitmap bitmapIndex, boolean descending, long numRows) { - this.itr = newIterator(bitmapIndex, descending); this.fullness = factorizeFullness(bitmapIndex.size(), numRows); + this.iterator = newIterator(bitmapIndex, descending); increment(); + // It's important to set iteratorForReset and valueForReset after calling increment(), because only after that the + // iterator and the value are in proper initial state. + this.iteratorForReset = safeClone(iterator); + this.valueForReset = value; } private IntIterator newIterator(ImmutableBitmap bitmapIndex, boolean descending) @@ -169,65 +167,65 @@ private IntIterator newIterator(ImmutableBitmap bitmapIndex, boolean descending) } } - private BitmapOffset(String fullness, IntIterator itr, int val) + /** + * Constructor for {@link #clone()}. + */ + private BitmapOffset(String fullness, IntIterator iterator, int value) { this.fullness = fullness; - this.itr = itr; - this.val = val; + this.iterator = iterator; + this.iteratorForReset = safeClone(iterator); + this.valueForReset = value; + this.value = value; } @Override public void increment() { - if (itr.hasNext()) { - val = itr.next(); + if (iterator.hasNext()) { + value = iterator.next(); } else { - val = INVALID_VALUE; + value = INVALID_VALUE; } } @Override public boolean withinBounds() { - return val > INVALID_VALUE; + return value > INVALID_VALUE; } + @Override + public void reset() + { + iterator = safeClone(iteratorForReset); + value = valueForReset; + } + + @SuppressWarnings("MethodDoesntCallSuperMethod") @Override public Offset clone() { - return new BitmapOffset(fullness, itr.clone(), val); + return new BitmapOffset(fullness, safeClone(iterator), value); } @Override public int getOffset() { - return val; + return value; } @Override public void inspectRuntimeShape(RuntimeShapeInspector inspector) { - inspector.visit("itr", itr); + inspector.visit("iterator", iterator); inspector.visit("fullness", fullness); } - public static class RoaringBitmapOffset extends BitmapOffset + private static IntIterator safeClone(IntIterator iterator) { - - public RoaringBitmapOffset(ImmutableBitmap bitmapIndex, boolean descending, long numRows) - { - super(bitmapIndex, descending, numRows); - } - - RoaringBitmapOffset(String fullness, IntIterator itr, int val) - { - super(fullness, itr, val); - } - - @Override - public Offset clone() - { - return new RoaringBitmapOffset(fullness, itr.hasNext() ? itr.clone() : EmptyIntIterator.instance(), val); - } + // Calling clone() on empty iterators from RoaringBitmap library sometimes fails with NPE, + // see https://github.com/druid-io/druid/issues/4709, https://github.com/RoaringBitmap/RoaringBitmap/issues/177 + return iterator.hasNext() ? iterator.clone() : EmptyIntIterator.instance(); } } diff --git a/processing/src/main/java/io/druid/segment/FilteredOffset.java b/processing/src/main/java/io/druid/segment/FilteredOffset.java index 74a5389b4753..ca78e8c10448 100644 --- a/processing/src/main/java/io/druid/segment/FilteredOffset.java +++ b/processing/src/main/java/io/druid/segment/FilteredOffset.java @@ -109,6 +109,12 @@ public boolean withinBounds() return baseOffset.withinBounds(); } + @Override + public void reset() + { + baseOffset.reset(); + } + @Override public Offset clone() { diff --git a/processing/src/main/java/io/druid/segment/data/ArrayBasedOffset.java b/processing/src/main/java/io/druid/segment/NoFilterOffset.java similarity index 57% rename from processing/src/main/java/io/druid/segment/data/ArrayBasedOffset.java rename to processing/src/main/java/io/druid/segment/NoFilterOffset.java index 90ce1da5dbee..6f3c5432b608 100644 --- a/processing/src/main/java/io/druid/segment/data/ArrayBasedOffset.java +++ b/processing/src/main/java/io/druid/segment/NoFilterOffset.java @@ -17,62 +17,65 @@ * under the License. */ -package io.druid.segment.data; +package io.druid.segment; import io.druid.query.monomorphicprocessing.RuntimeShapeInspector; +import io.druid.segment.data.Offset; -/** - */ -public class ArrayBasedOffset extends Offset +public class NoFilterOffset extends Offset { - private final int[] ints; - private int currIndex; + private final int rowCount; + private final boolean descending; + private final int initialOffset; + private int currentOffset; - public ArrayBasedOffset( - int[] ints - ) + NoFilterOffset(int initialOffset, int rowCount, boolean descending) { - this(ints, 0); + this.initialOffset = initialOffset; + this.currentOffset = initialOffset; + this.rowCount = rowCount; + this.descending = descending; } - public ArrayBasedOffset( - int[] ints, - int startIndex - ) + @Override + public void increment() { - this.ints = ints; - this.currIndex = startIndex; + currentOffset++; } @Override - public int getOffset() + public boolean withinBounds() { - return ints[currIndex]; + return currentOffset < rowCount; } @Override - public void increment() + public void reset() { - ++currIndex; + currentOffset = initialOffset; } @Override - public boolean withinBounds() + public Offset clone() { - return currIndex < ints.length; + return new NoFilterOffset(currentOffset, rowCount, descending); } @Override - public Offset clone() + public int getOffset() + { + return descending ? rowCount - currentOffset - 1 : currentOffset; + } + + @Override + public String toString() { - final ArrayBasedOffset retVal = new ArrayBasedOffset(ints); - retVal.currIndex = currIndex; - return retVal; + return currentOffset + "/" + rowCount + (descending ? "(DSC)" : ""); } @Override public void inspectRuntimeShape(RuntimeShapeInspector inspector) { - // nothing to inspect + inspector.visit("descending", descending); } } diff --git a/processing/src/main/java/io/druid/segment/QueryableIndexStorageAdapter.java b/processing/src/main/java/io/druid/segment/QueryableIndexStorageAdapter.java index 32a8451fc897..fb0f7eb6fe55 100644 --- a/processing/src/main/java/io/druid/segment/QueryableIndexStorageAdapter.java +++ b/processing/src/main/java/io/druid/segment/QueryableIndexStorageAdapter.java @@ -966,6 +966,12 @@ public boolean withinBounds() return timeInRange(timestamps.getLongSingleValueRow(baseOffset.getOffset())); } + @Override + public void reset() + { + baseOffset.reset(); + } + protected abstract boolean timeInRange(long current); @Override @@ -1054,56 +1060,6 @@ public Offset clone() } } - public static class NoFilterOffset extends Offset - { - private final int rowCount; - private final boolean descending; - private int currentOffset; - - NoFilterOffset(int currentOffset, int rowCount, boolean descending) - { - this.currentOffset = currentOffset; - this.rowCount = rowCount; - this.descending = descending; - } - - @Override - public void increment() - { - currentOffset++; - } - - @Override - public boolean withinBounds() - { - return currentOffset < rowCount; - } - - @Override - public Offset clone() - { - return new NoFilterOffset(currentOffset, rowCount, descending); - } - - @Override - public int getOffset() - { - return descending ? rowCount - currentOffset - 1 : currentOffset; - } - - @Override - public String toString() - { - return currentOffset + "/" + rowCount + (descending ? "(DSC)" : ""); - } - - @Override - public void inspectRuntimeShape(RuntimeShapeInspector inspector) - { - inspector.visit("descending", descending); - } - } - @Override public Metadata getMetadata() { diff --git a/processing/src/main/java/io/druid/segment/data/IntersectingOffset.java b/processing/src/main/java/io/druid/segment/data/IntersectingOffset.java deleted file mode 100644 index a284db4345b8..000000000000 --- a/processing/src/main/java/io/druid/segment/data/IntersectingOffset.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Licensed to Metamarkets Group Inc. (Metamarkets) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Metamarkets 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 io.druid.segment.data; - -import io.druid.query.monomorphicprocessing.RuntimeShapeInspector; - -/** - */ -public class IntersectingOffset extends Offset -{ - private final Offset lhs; - private final Offset rhs; - - public IntersectingOffset(Offset lhs, Offset rhs) - { - this.lhs = lhs; - this.rhs = rhs; - - findIntersection(); - } - - @Override - public int getOffset() - { - return lhs.getOffset(); - } - - @Override - public void increment() - { - lhs.increment(); - rhs.increment(); - - findIntersection(); - } - - private void findIntersection() - { - if (!(lhs.withinBounds() && rhs.withinBounds())) { - return; - } - - int lhsOffset = lhs.getOffset(); - int rhsOffset = rhs.getOffset(); - - while (lhsOffset != rhsOffset) { - while (lhsOffset < rhsOffset) { - lhs.increment(); - if (!lhs.withinBounds()) { - return; - } - - lhsOffset = lhs.getOffset(); - } - - while (rhsOffset < lhsOffset) { - rhs.increment(); - if (!rhs.withinBounds()) { - return; - } - - rhsOffset = rhs.getOffset(); - } - } - } - - @Override - public boolean withinBounds() - { - return lhs.withinBounds() && rhs.withinBounds(); - } - - @Override - public Offset clone() - { - final Offset lhsClone = lhs.clone(); - final Offset rhsClone = rhs.clone(); - return new IntersectingOffset(lhsClone, rhsClone); - } - - @Override - public void inspectRuntimeShape(RuntimeShapeInspector inspector) - { - inspector.visit("lhs", lhs); - inspector.visit("rhs", rhs); - } -} diff --git a/processing/src/main/java/io/druid/segment/data/Offset.java b/processing/src/main/java/io/druid/segment/data/Offset.java index 7eff391e65d8..77f949d7a61e 100644 --- a/processing/src/main/java/io/druid/segment/data/Offset.java +++ b/processing/src/main/java/io/druid/segment/data/Offset.java @@ -25,6 +25,9 @@ /** * The "mutable" version of a ReadableOffset. Introduces "increment()" and "withinBounds()" methods, which are * very similar to "next()" and "hasNext()" on the Iterator interface except increment() does not return a value. + * + * This class is not thread-safe, all it's methods, including {@link #reset()} and {@link #clone()}, must be called + * from a single thread. * * Annotated with {@link SubclassesMustBePublic} because Offset occurrences are replaced with a subclass in {@link * io.druid.query.topn.Historical1SimpleDoubleAggPooledTopNScannerPrototype} and {@link @@ -40,6 +43,11 @@ public abstract class Offset implements ReadableOffset, Cloneable @CalledFromHotLoop public abstract boolean withinBounds(); + /** + * Resets the Offset to the position it was created or cloned with. + */ + public abstract void reset(); + @Override public Offset clone() { diff --git a/processing/src/main/java/io/druid/segment/data/UnioningOffset.java b/processing/src/main/java/io/druid/segment/data/UnioningOffset.java deleted file mode 100644 index 61572b0a64e3..000000000000 --- a/processing/src/main/java/io/druid/segment/data/UnioningOffset.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Licensed to Metamarkets Group Inc. (Metamarkets) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Metamarkets 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 io.druid.segment.data; - -import io.druid.query.monomorphicprocessing.RuntimeShapeInspector; - -/** - */ -public class UnioningOffset extends Offset -{ - private final Offset[] offsets = new Offset[2]; - private final int[] offsetVals = new int[2]; - - private int nextOffsetIndex; - - public UnioningOffset(Offset lhs, Offset rhs) - { - if (lhs.withinBounds()) { - offsets[0] = lhs; - } - - if (rhs.withinBounds()) { - if (offsets[0] == null) { - offsets[0] = rhs; - } else { - offsets[1] = rhs; - } - } - - if (offsets[0] != null) { - offsetVals[0] = offsets[0].getOffset(); - if (offsets[1] != null) { - offsetVals[1] = offsets[1].getOffset(); - } - } - figureOutNextValue(); - } - - private UnioningOffset( - Offset[] offsets, - int[] offsetVals, - int nextOffsetIndex - ) - { - System.arraycopy(offsets, 0, this.offsets, 0, 2); - System.arraycopy(offsetVals, 0, this.offsetVals, 0, 2); - this.nextOffsetIndex = nextOffsetIndex; - } - - private void figureOutNextValue() - { - if (offsets[0] != null) { - if (offsets[1] != null) { - int lhs = offsetVals[0]; - int rhs = offsetVals[1]; - - if (lhs < rhs) { - nextOffsetIndex = 0; - } else if (lhs == rhs) { - nextOffsetIndex = 0; - rollIndexForward(1); - } else { - nextOffsetIndex = 1; - } - } else { - nextOffsetIndex = 0; - } - } - } - - private void rollIndexForward(int i) - { - offsets[i].increment(); - - if (!offsets[i].withinBounds()) { - offsets[i] = null; - if (i == 0) { - offsets[0] = offsets[1]; - offsetVals[0] = offsetVals[1]; - } - } else { - offsetVals[i] = offsets[i].getOffset(); - } - } - - @Override - public int getOffset() - { - return offsetVals[nextOffsetIndex]; - } - - @Override - public void increment() - { - rollIndexForward(nextOffsetIndex); - figureOutNextValue(); - } - - @Override - public boolean withinBounds() - { - return offsets[0] != null; - } - - @Override - public Offset clone() - { - Offset[] newOffsets = new Offset[2]; - int[] newOffsetValues = new int[2]; - - for (int i = 0; i < newOffsets.length; ++i) { - newOffsets[i] = offsets[i] == null ? null : offsets[i].clone(); - newOffsetValues[i] = this.offsetVals[i]; - } - - return new UnioningOffset(newOffsets, newOffsetValues, nextOffsetIndex); - } - - @Override - public void inspectRuntimeShape(RuntimeShapeInspector inspector) - { - inspector.visit("lhs", offsets[0]); - inspector.visit("rhs", offsets[1]); - } -} diff --git a/processing/src/test/java/io/druid/segment/data/IntersectingOffsetTest.java b/processing/src/test/java/io/druid/segment/data/IntersectingOffsetTest.java deleted file mode 100644 index 311f1cd80bbc..000000000000 --- a/processing/src/test/java/io/druid/segment/data/IntersectingOffsetTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Licensed to Metamarkets Group Inc. (Metamarkets) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Metamarkets 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 io.druid.segment.data; - -import com.google.common.collect.Lists; -import org.junit.Assert; -import org.junit.Test; - -import java.util.LinkedList; - -/** - */ -public class IntersectingOffsetTest -{ - @Test - public void testSanity() throws Exception - { - assertExpected( - new int[]{2, 3, 6, 7}, - new IntersectingOffset( - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}), - new ArrayBasedOffset(new int[]{2, 3, 4, 5, 6, 7}) - ) - ); - assertExpected( - new int[]{2, 3, 6, 7}, - new IntersectingOffset( - new ArrayBasedOffset(new int[]{2, 3, 4, 5, 6, 7}), - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}) - ) - ); - - assertExpected( - new int[]{}, - new IntersectingOffset( - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}), - new ArrayBasedOffset(new int[]{4, 5, 9, 10}) - ) - ); - - assertExpected( - new int[]{}, - new IntersectingOffset( - new ArrayBasedOffset(new int[]{4, 5, 9, 10}), - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}) - ) - ); - - assertExpected( - new int[]{}, - new IntersectingOffset( - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}), - new ArrayBasedOffset(new int[]{}) - ) - ); - - assertExpected( - new int[]{}, - new IntersectingOffset( - new ArrayBasedOffset(new int[]{}), - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}) - ) - ); - } - - private static void assertExpected(int[] expectedValues, IntersectingOffset offset) - { - final LinkedList offsets = Lists.newLinkedList(); - offsets.add(offset); - - for (int expectedValue : expectedValues) { - for (Offset aClone : offsets) { - Assert.assertTrue(aClone.withinBounds()); - Assert.assertEquals(expectedValue, aClone.getOffset()); - aClone.increment(); - } - offsets.add(offsets.getFirst().clone()); - } - - for (Offset aClone : offsets) { - Assert.assertFalse(aClone.withinBounds()); - } - } -} diff --git a/processing/src/test/java/io/druid/segment/data/UnioningOffsetTest.java b/processing/src/test/java/io/druid/segment/data/UnioningOffsetTest.java deleted file mode 100644 index 8cca50a01e52..000000000000 --- a/processing/src/test/java/io/druid/segment/data/UnioningOffsetTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Licensed to Metamarkets Group Inc. (Metamarkets) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Metamarkets 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 io.druid.segment.data; - -import com.google.common.collect.Lists; -import io.druid.java.util.common.StringUtils; -import org.junit.Assert; -import org.junit.Test; - -import java.util.ArrayList; - -/** - */ -public class UnioningOffsetTest -{ - @Test - public void testSanity() throws Exception - { - assertExpected( - new int[]{1, 2, 3, 4, 5, 6, 7, 8}, - new UnioningOffset( - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}), - new ArrayBasedOffset(new int[]{2, 3, 4, 5, 6, 7}) - ) - ); - assertExpected( - new int[]{1, 2, 3, 4, 5, 6, 7, 8}, - new UnioningOffset( - new ArrayBasedOffset(new int[]{2, 3, 4, 5, 6, 7}), - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}) - ) - ); - - assertExpected( - new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - new UnioningOffset( - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}), - new ArrayBasedOffset(new int[]{4, 5, 9, 10}) - ) - ); - - assertExpected( - new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - new UnioningOffset( - new ArrayBasedOffset(new int[]{4, 5, 9, 10}), - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}) - ) - ); - - assertExpected( - new int[]{1, 2, 3, 6, 7, 8}, - new UnioningOffset( - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}), - new ArrayBasedOffset(new int[]{}) - ) - ); - - assertExpected( - new int[]{1, 2, 3, 6, 7, 8}, - new UnioningOffset( - new ArrayBasedOffset(new int[]{}), - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}) - ) - ); - - assertExpected( - new int[]{1, 2, 3, 6, 7, 8}, - new UnioningOffset( - new ArrayBasedOffset(new int[]{1}), - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}) - ) - ); - - assertExpected( - new int[]{1, 2, 3, 6, 7, 8}, - new UnioningOffset( - new ArrayBasedOffset(new int[]{1, 2, 3, 6, 7, 8}), - new ArrayBasedOffset(new int[]{1}) - ) - ); - } - - private static void assertExpected(int[] expectedValues, UnioningOffset offset) - { - final ArrayList offsets = Lists.newArrayList(); - offsets.add(offset); - - for (int expectedValue : expectedValues) { - for (int j = 0; j < offsets.size(); ++j) { - Offset aClone = offsets.get(j); - Assert.assertTrue(StringUtils.format("Clone[%d] out of bounds", j), aClone.withinBounds()); - Assert.assertEquals(StringUtils.format("Clone[%d] not right", j), expectedValue, aClone.getOffset()); - aClone.increment(); - } - offsets.add(offsets.get(0).clone()); - } - - for (Offset aClone : offsets) { - Assert.assertFalse(aClone.withinBounds()); - } - } -}