public static GapEncodedVariableLengthIntegerReader join(GapEncodedVariableLengthIntegerReader[] from) {
        if (from==null) {
            throw new IllegalStateException("Join invoked on a null input array");
        }
        if (from.length<=0 || !((from.length&(from.length-1))==0)) {
            throw new IllegalStateException("Join should only be called with powers of 2, it was called with " + from.length);
        }

        int numSplits = from.length;
        final int fromMask = numSplits - 1;
        final int fromOrdinalShift = 31 - Integer.numberOfLeadingZeros(numSplits);
        int joinedMaxOrdinal = -1;

        HashSet<Integer>[] fromOrdinals = new HashSet[from.length];
        for (int i=0;i<from.length;i++) {
            fromOrdinals[i] = new HashSet<>();
            if (from[i] == null) {
                continue;
            }
            from[i].reset();

            while(from[i].nextElement() != Integer.MAX_VALUE) {
                int splitOrdinal = from[i].nextElement();
                fromOrdinals[i].add(splitOrdinal);
                joinedMaxOrdinal = Math.max(joinedMaxOrdinal, splitOrdinal*numSplits + i);
                from[i].advance();
            }
        }

        ByteDataArray toRemovals = null;
        int previousOrdinal = 0;
        for (int ordinal=0;ordinal<=joinedMaxOrdinal;ordinal++) {
            int fromIndex = ordinal & fromMask;
            int fromOrdinal = ordinal >> fromOrdinalShift;
            if (fromOrdinals[fromIndex].contains(fromOrdinal)) {
                if (toRemovals == null) {
                    toRemovals = new ByteDataArray(WastefulRecycler.DEFAULT_INSTANCE);
                }
                VarInt.writeVInt(toRemovals, ordinal - previousOrdinal);
                previousOrdinal = ordinal;
            }
        }

        if (toRemovals == null) {
            return EMPTY_READER;
        } else {
            return new GapEncodedVariableLengthIntegerReader(toRemovals.getUnderlyingArray(), (int) toRemovals.length());
        }
    }
@Test
    public void testJoin() {
        GapEncodedVariableLengthIntegerReader[] from = new GapEncodedVariableLengthIntegerReader[2];
        from[0] = reader(1, 10, 100, 105, 107, 200);
        from[1] = reader(5, 76, 100, 102, 109, 200, 201);
        GapEncodedVariableLengthIntegerReader joined = GapEncodedVariableLengthIntegerReader.join(from);
        assertValues(joined, 2, 11, 20, 153, 200, 201, 205, 210, 214, 219, 400, 401, 403);

        GapEncodedVariableLengthIntegerReader[] from4 = new GapEncodedVariableLengthIntegerReader[4];
        from4[0] = reader(0, 1, 2, 3);
        from4[1] = null;
        from4[2] = reader(3);
        from4[3] = EMPTY_READER;
        GapEncodedVariableLengthIntegerReader joined4 = GapEncodedVariableLengthIntegerReader.join(from4);
        assertValues(joined4, 0, 4, 8, 12, 14); // splitOrdinal 3, in splitIndex 2 of numSplits 4 => 3*4+2

        GapEncodedVariableLengthIntegerReader[] empties = new GapEncodedVariableLengthIntegerReader[] {EMPTY_READER, EMPTY_READER};
        GapEncodedVariableLengthIntegerReader joinedEmpties = GapEncodedVariableLengthIntegerReader.join(empties);
        assertEquals(EMPTY_READER, joinedEmpties);

        GapEncodedVariableLengthIntegerReader[] nulls = new GapEncodedVariableLengthIntegerReader[] {null, null};
        GapEncodedVariableLengthIntegerReader joinedNulls = GapEncodedVariableLengthIntegerReader.join(nulls);
        assertEquals(EMPTY_READER, joinedNulls);

        GapEncodedVariableLengthIntegerReader[] from1 = new GapEncodedVariableLengthIntegerReader[1];
        from1[0] = reader(1, 10, 100, 105, 107, 200);
        GapEncodedVariableLengthIntegerReader joined1 = GapEncodedVariableLengthIntegerReader.join(from1);
        assertValues(joined1, 1, 10, 100, 105, 107, 200);

        assertIllegalStateException(() -> GapEncodedVariableLengthIntegerReader.join(null));
    }