public record _Ints()

in commons/src/main/java/org/apache/causeway/commons/internal/primitives/_Ints.java [47:409]


public record _Ints() {

    @FunctionalInterface
    public interface BiIntConsumer {
        void accept(int i, int j);
    }

    @FunctionalInterface
    public interface BiIntFunction<R> {
        R apply(int i, int j);
    }

    // -- RANGE

    public record Bound(
            int value,
            boolean isInclusive) {
        public static @NonNull Bound inclusive(final int value) { return new Bound(value, true); }
        public static @NonNull Bound exclusive(final int value) { return new Bound(value, true); }
    }

    public record Range(
            Bound lowerBound,
            Bound upperBound,
            boolean isEmpty) {

        public static Range empty() {
            return new Range(null, null, true);
        }

        public static Range of(
                final @NonNull Bound lowerBound,
                final @NonNull Bound upperBound) {
            return new Range(lowerBound, upperBound, false);
        }

        public boolean contains(final int value) {
            if(isEmpty) return false;
            var isBelowLower = lowerBound.isInclusive()
                    ? value < lowerBound.value()
                    : value <= lowerBound.value();
            if(isBelowLower) return false;

            var isAboveUpper = upperBound.isInclusive()
                    ? value > upperBound.value()
                    : value >= upperBound.value();
            if(isAboveUpper) return false;

            return true;
        }
        /**
         * @param value
         * @return the value or if not within range, the nearest integer to the value, that is within range
         */
        public int bounded(final int value) {
            if(isEmpty) return value; // noop
            if(contains(value)) return value;

            final int nearestToLower = nearestToLower();
            final int nearestToUpper = nearestToUpper();
            final int distanceToLower = value - nearestToLower;
            final int distanceToUpper = value - nearestToUpper;
            return (distanceToLower <= distanceToUpper)
                    ? nearestToLower
                    : nearestToUpper;
        }
        private int nearestToLower() {
            if(isEmpty) throw _Exceptions.unsupportedOperation();
            return lowerBound.isInclusive() ? lowerBound.value() : lowerBound.value()+1;
        }
        private int nearestToUpper() {
            if(isEmpty) throw _Exceptions.unsupportedOperation();
            return upperBound.isInclusive() ? upperBound.value() : upperBound.value()-1;
        }
        public @NonNull Optional<Range> intersect(final @NonNull Range other) {
            if(isEmpty) return Optional.empty();
            final int s1 = this.nearestToLower();
            final int e1 = this.nearestToUpper();
            final int s2 = other.nearestToLower();
            final int e2 = other.nearestToUpper();
            if(s2>e1 || s1>e2) {
                return Optional.empty();
            }
            return Optional.of(of(
                    Bound.inclusive(Math.max(s1, s2)),
                    Bound.inclusive(Math.min(e1, e2))));
        }
        @Override
        public String toString() {
            if(isEmpty) return "[]";
            return String.format("%s%d,%d%S",
                    lowerBound.isInclusive() ? '[' : '(', lowerBound.value(),
                    upperBound.value(), upperBound.isInclusive() ? ']' : ')');
        }
        public IntStream stream() {
            if(isEmpty) return IntStream.empty();
            return IntStream.rangeClosed(nearestToLower(), nearestToUpper());
        }
        public PrimitiveIterator.OfInt iterator() {
            if(isEmpty) return IntStream.empty().iterator();
            return new PrimitiveIterator.OfInt() {
                int next = nearestToLower();
                final int upperIncluded = nearestToUpper();

                @Override
                public int nextInt() {
                    if(!hasNext()) {
                        throw _Exceptions.noSuchElement();
                    }
                    int result = next;
                    next++;
                    return result;
                }

                @Override
                public boolean hasNext() {
                    return next <= upperIncluded;
                }
            };
        }
    }

    // -- RANGE FACTORIES

    /**
     * Range includes a and b.
     */
    public static Range rangeClosed(final int a, final int b) {
        if(a>b) {
            throw _Exceptions.illegalArgument("bounds must be ordered in [%d, %d]", a, b);
        }
        return Range.of(Bound.inclusive(a), Bound.inclusive(b));
    }

    /**
     * Range includes a but not b.
     */
    public static Range rangeOpenEnded(final int a, final int b) {
        if(a==b) {
            return Range.empty();
        }
        if(a>=b) {
            throw _Exceptions.illegalArgument("bounds must be ordered in [%d, %d]", a, b);
        }
        return Range.of(Bound.inclusive(a), Bound.exclusive(b));
    }

    // -- PARSING

    /**
     * Parses the string argument as a signed integer in the radix
     * specified by the second argument. The characters in the string
     * must all be digits of the specified radix (as determined by
     * whether {@link java.lang.Character#digit(char, int)} returns a
     * nonnegative value), except that the first character may be an
     * ASCII minus sign {@code '-'} ({@code '\u005Cu002D'}) to
     * indicate a negative value or an ASCII plus sign {@code '+'}
     * ({@code '\u005Cu002B'}) to indicate a positive value. The
     * resulting integer value is returned.
     *
     * <li>The radix is either smaller than
     * {@link java.lang.Character#MIN_RADIX} or
     * larger than {@link java.lang.Character#MAX_RADIX}.
     *
     * <li>Any character of the string is not a digit of the specified
     * radix, except that the first character may be a minus sign
     * {@code '-'} ({@code '\u005Cu002D'}) or plus sign
     * {@code '+'} ({@code '\u005Cu002B'}) provided that the
     * string is longer than length 1.
     *
     * <li>The value represented by the string is not a value of type
     * {@code int}.
     * </ul>
     *
     * @param      s   the {@code String} containing the integer
     *                  representation to be parsed
     * @param      radix   the radix to be used while parsing {@code s}.
     * @param      onFailure on parsing failure consumes the failure message
     * @return optionally the integer represented by the string argument in the specified radix
     *
     */
    public static OptionalInt parseInt(final String s, final int radix, final Consumer<String> onFailure) {
        final long parseResult = parseIntElseLongMaxValue(s, radix, onFailure);
        if(isParseSuccess(parseResult)) {
            return OptionalInt.of(Math.toIntExact(parseResult));
        }
        return OptionalInt.empty();
    }

    // -- SHORTCUTS

    public static OptionalInt parseInt(final String s, final int radix) {
        return parseInt(s, radix, IGNORE_ERRORS);
    }

    // -- LOW LEVEL HELPER

    private static boolean isParseSuccess(final long value) {
        return value!=Long.MAX_VALUE;
    }

    private static final Consumer<String> IGNORE_ERRORS = t->{};

    /**
     * @implNote Copied over from JDK's {@link Integer#parseInt(String)} to provide a variant
     * with minimum potential heap pollution (does not produce stack-traces on parsing failures)
     */
    private static long parseIntElseLongMaxValue(
            final @Nullable String s,
            final int radix,
            final @NonNull Consumer<String> onFailure) {

        if (s == null) {
            onFailure.accept("null");
            return Long.MAX_VALUE;
        }

        if (radix < Character.MIN_RADIX) {
            onFailure.accept("radix " + radix +
                                            " less than Character.MIN_RADIX");
            return Long.MAX_VALUE;
        }

        if (radix > Character.MAX_RADIX) {
            onFailure.accept("radix " + radix +
                                            " greater than Character.MAX_RADIX");
            return Long.MAX_VALUE;
        }

        boolean negative = false;
        int i = 0, len = s.length();
        int limit = -Integer.MAX_VALUE;

        if (len > 0) {
            char firstChar = s.charAt(0);
            if (firstChar < '0') { // Possible leading "+" or "-"
                if (firstChar == '-') {
                    negative = true;
                    limit = Integer.MIN_VALUE;
                } else if (firstChar != '+') {
                    onFailure.accept(s);
                    return Long.MAX_VALUE;
                }

                if (len == 1) { // Cannot have lone "+" or "-"
                    onFailure.accept(s);
                    return Long.MAX_VALUE;
                }
                i++;
            }
            int multmin = limit / radix;
            int result = 0;
            while (i < len) {
                // Accumulating negatively avoids surprises near MAX_VALUE
                int digit = Character.digit(s.charAt(i++), radix);
                if (digit < 0 || result < multmin) {
                    onFailure.accept(s);
                    return Long.MAX_VALUE;
                }
                result *= radix;
                if (result < limit + digit) {
                    onFailure.accept(s);
                    return Long.MAX_VALUE;
                }
                result -= digit;
            }
            var value = negative ? result : -result;
            return value;
        } else {
            onFailure.accept(s);
            return Long.MAX_VALUE;
        }
    }

    // -- ARRAY FLATTEN

    public static int[] flatten(final @NonNull int[][] nested) {
        final int n = nested.length;
        final int stride = nested[0].length;
        var flattened = new int[n*stride];
        for(int i=0; i<n; ++i) {
            System.arraycopy(nested[i], 0, flattened, i*stride, stride);
        }
        return flattened;
    }

    // -- ARRAY PARTITION

    public static int[][] partition(final @NonNull int[] flattened, final int stride) {
        final int n = flattened.length/stride;
        _Assert.assertEquals(flattened.length, n*stride, ()->"flattened.length must be divisible by stride");
        var nested = new int[n][stride];
        for(int i=0; i<n; ++i) {
            System.arraycopy(flattened, i*stride, nested[i], 0, stride);
        }
        return nested;
    }

    // -- PRINTING

    public static String rowForm(
            final @NonNull int[] array) {
        return rowForm(array, 8, Integer::toString);
    }

    public static String rowForm(
            final @NonNull int[] array,
            final int columnWidth,
            final @NonNull IntFunction<String> cellFormatter) {

        final int m = array.length;
        var sb = new StringBuilder();

        for(int j=0; j<m; ++j) {
            final int cellValue = array[j];
            var cellStringFull = cellFormatter.apply(cellValue);
            var cellStringTrimmed = _Strings.ellipsifyAtEnd(cellStringFull, columnWidth, "..");

            // right align, at column end marker
            final int spacesCount = columnWidth - cellStringTrimmed.length();
            for(int k=0; k<spacesCount; ++k) {
                sb.append(' ');
            }
            sb.append(cellStringTrimmed);
        }
        sb.append("\n");

        return sb.toString();
    }

    public static String tableForm(
            final @NonNull int[][] nested) {
        return tableForm(nested, 8, Integer::toString);
    }

    public static String tableForm(
            final @NonNull int[][] nested,
            final int columnWidth,
            final @NonNull IntFunction<String> cellFormatter) {

        final int n = nested.length;
        var sb = new StringBuilder();
        for(int i=0; i<n; ++i) {
            sb.append(rowForm(nested[i], columnWidth, cellFormatter));
        }
        return sb.toString();
    }

    /**
     * Compares two {@code int} values numerically.
     * @param  x the first {@code int} to compare
     * @param  y the second {@code int} to compare
     * @return the value {@code 0} if {@code x == y};
     *         {@code -1} if {@code x < y}; and
     *         {@code 1} if {@code x > y}
     * @apiNote copy of {@link Integer#compare(int, int)}
     *      because their java-doc states that return values -1, +1 are not guaranteed.
     */
    public static int compare(final int x, final int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

}