public static RollUpBitMask fromTagPositions(final SortedSet<Integer> tagPositions) {
        RollUpBitMask rollUpBitMask = positionListToObjectMap.get(tagPositions);

        if (rollUpBitMask == null) {
            // not in our static cache so cache the perms for this number of
            // tags

            final int maxValInList = getMaxValueInList(tagPositions);

            cacheRollUpPerms(tagPositions.size() == 0
                    ? 0
                    : (maxValInList + 1));

            // try again from the map now it should be in there
            rollUpBitMask = positionListToObjectMap.get(tagPositions);

            if (rollUpBitMask == null) {
                throw new RuntimeException(String.format("Position lists have not been loaded into the cache for " +
                                                         "a position list [%s] of size %s. This should never happen",
                        tagPositions,
                        tagPositions.size()));
            }
        }
        return rollUpBitMask;
    }
Generates a {@link RollUpBitMask} from the passed set of rolled up tag
positions. It uses a cache of tag positions to save it having to build
the objects each time. For that reason the passed set must be sorted to
ensure it can be compared to ones in the cache, i.e. [0,1] == [1,0]

@param tagPositions A sorted set of the array position values at which a tag is
                    rolled up. E.g. if you have Tag1, Tag2 & Tag3 and only Tag2 is
                    rolled up then [1] would be passed as only tag at position 1
                    is rolled up.
@return The {@link RollUpBitMask} object representing the rolled up tag
positions.
stroom-statistics/stroom-statistics-impl-sql/src/main/java/stroom/statistics/impl/sql/rollup/RollUpBitMask.java