Saturday, November 28, 2020

JDK 16: Checking Indexes and Ranges of Longs

In my last post, I described the day period support added with JDK 16 Early Access Build 25. That same build also added methods for checking indexes and ranges of long values, which is the subject of this post.

JDK-8255150 ("Add utility methods to check long indexes and ranges") is the Enhancement used to add utility methods for checking long indexes and ranges similar to what JDK-8135248 ("Add utility methods to check indexes and ranges") added for integers with JDK 9. JDK-8255150 states, "The goal is to add a similar set of methods [as JDK-8135248] but rather than operate on int arguments, the new methods operate on long arguments."

JDK-8255150 lists the method signatures for the three new methods being added to the Objects class (descriptions provided by JDK-8135248):

  • Checking whether an index is within bounds:
    public static long checkIndex(long index, long length)
  • Checking whether an absolute range is within bounds:
    public static long checkFromToIndex(long fromIndex, long toIndex, long length)
  • Checking whether a relative range is within bounds:
    public static long checkFromIndexSize(long fromIndex, long size, long length)

Because these new methods "mirror the int utility methods," it is useful to look at JDK-8135248 to see more historical context for the justification for the introduction of these methods. That Enhancement states, "There are numerous methods in the JDK that check if an index or an absolute/relative range is valid before accessing the contents of an array (or in general a memory region for the case of a direct java.nio.ByteBuffer). ... Such checks, while not difficult, are often easy to get wrong and optimize correctly, thus there is a risk to the integrity and security of the runtime."

JDK-8135248 also talks about possibilities for optimization, "A further desire for such methods is some or all can be made intrinsic (see JDK-8042997), thus hinting to the HotSpot runtime compiler to use unsigned comparisons and better optimize array access (via aaload/store or Unsafe) in loops (especially those that are unrolled)."

A class that demonstrates these newly added methods, LongIndexRangeChecksDemo, is available on GitHub. All examples in this class demonstrate the various checks throwing IndexOutOfBoundsExceptions to indicate that the proposed index and/or size values do not fall within the allowed range. The main(String[]) function executes all the example methods and its output is divided into described sections below.

checkIndex Example Output

The message associated with this example clearly describes the index that is out of bounds and how that index is out of bounds.

==========================
== checkIndex Exception ==
==========================
java.lang.IndexOutOfBoundsException: Index 7 out of bounds for length 5
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:88)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:412)
	at java.base/java.util.Objects.checkIndex(Objects.java:435)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckIndexException$0(LongIndexRangeChecksDemo.java:34)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckIndexException(LongIndexRangeChecksDemo.java:33)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:115)

checkFromToIndex Example Output

The message clearly indicates that the range specified by the "from" and "to" indexes is too large for the expected length capacity. Note that the "[" opening the range description indicates "inclusive" and the ")" ending the range description indicates "exclusive".

================================
== checkFromToIndex Exception ==
================================
java.lang.IndexOutOfBoundsException: Range [2, 6) out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromToIndex(Preconditions.java:94)
	at java.base/jdk.internal.util.Preconditions.checkFromToIndex(Preconditions.java:459)
	at java.base/java.util.Objects.checkFromToIndex(Objects.java:461)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckFromToIndexException$1(LongIndexRangeChecksDemo.java:48)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckFromToIndexException(LongIndexRangeChecksDemo.java:47)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:116)

checkFromIndexSize Example Output

This example indicates that the range formed by a "from" index and range size is out of bounds for the specified length capacity.

==================================
== checkFromIndexSize Exception ==
==================================
java.lang.IndexOutOfBoundsException: Range [2, 2 + 6) out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromIndexSize(Preconditions.java:100)
	at java.base/jdk.internal.util.Preconditions.checkFromIndexSize(Preconditions.java:507)
	at java.base/java.util.Objects.checkFromIndexSize(Objects.java:487)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckFromIndexSizeException$2(LongIndexRangeChecksDemo.java:62)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckFromIndexSizeException(LongIndexRangeChecksDemo.java:61)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:117)

checkFromIndexSize Overflow Example Output

This example indicates that the range formed by a "from" index and range size are out of bounds because a numeric overflow occurred when adding the size to the initial index. This is a nice catch because an overly simplistic homegrown approach that checked that the supplied initial index and supplied size are both positive and then checked the sum of the index and size against the allowed length would be faulty logic due to the overflow possibility.

=============================================
== checkFromIndexSize (Overflow) Exception ==
=============================================
java.lang.IndexOutOfBoundsException: Range [2, 2 + 9223372036854775807) out of bounds for length 3
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckFromIndexSize(Preconditions.java:100)
	at java.base/jdk.internal.util.Preconditions.checkFromIndexSize(Preconditions.java:507)
	at java.base/java.util.Objects.checkFromIndexSize(Objects.java:487)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.lambda$demoCheckFromIndexSizeExceptionOnOverflow$3(LongIndexRangeChecksDemo.java:77)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.executeDemonstration(LongIndexRangeChecksDemo.java:96)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.demoCheckFromIndexSizeExceptionOnOverflow(LongIndexRangeChecksDemo.java:76)
	at dustin.examples.jdk16.check.LongIndexRangeChecksDemo.main(LongIndexRangeChecksDemo.java:118)

Common Uses

The greatest beneficiary of these newly added long-supporting methods may be the authors, maintainers, and users of the foreign memory access API as described in this mailing list message: "We have to jump through quite a few hoops in the implementation of the foreign memory access API in order to leverage the intrinsification of int-based index checks, and even then we are not covering the cases where the numbers are larger than ints. Looking forward to being able to remove those hacks!"

A common use of these methods is likely to be as method guards for checking method parameters against expected preconditions similar to how other Objects' methods such as checkIndex(int, int), checkFromToIndex(int, int, int), checkFromIndexSize(int, int, int), requireNonNull(T), and requireNonNull(T, String) are used.

Monday, November 23, 2020

Day Period Support in JDK 16

JDK 16 Early Access Build 25 (2020/11/18) includes changes for JDK-8247781 ("Day periods support"). As stated in the JDK 16 Early Access Build 25 Release Notes ("Day period support added to java.time formats"), the new functionality "translates day periods defined in Unicode Consortium's CLDR."

In most English-language situations using a "12-hour clock", the "day periods" might be used instead of the AM (ante meridiem) and PM (post meridiem) designators. The Unicode documentation "Day Period Rule Sets" describes day period rules like this: "Each locale can have a set of day period rules, which determine the periods during a day for use in time formats like '10:00 at night', or to select statements like 'Your email arrived last night.' If locales do not have dayPeriodRules, the computation of dayPeriods falls back to AM/PM."

As with most things, it's perhaps easiest to see this new functionality via code examples and their associated output. The first example shown here is adapted from the JDK 16 Early Access Build 25 Release Notes. Note that the "B" is used in the pattern to specify that a day period is to be used.

/**
 * Writes the current day period out to standard output.
 *
 * This is based on the example included with the Release Notes
 * (https://jdk.java.net/16/release-notes).
 */
public void printCurrentDayPeriod()
{
   final String currentDayPeriodStr
      = DateTimeFormatter.ofPattern("B").format(LocalTime.now());
   out.println("Pattern 'B' (time now): \"" + currentDayPeriodStr + "\"");
}

The output for the above code sample when run in evening hours is now shown:

Pattern 'B' (time now): "in the evening"

The next code sample contains two methods, showing variations of date/time formats using the "B" format pattern letter.

/**
 * Prints representation of supplied {@link ZonedDateTime} with hour,
 * day period, and time zone details to standard output.
 *
 * @param zonedDateTime Date/time that will be written to standard output
 *    and will include hour, day period, and zone details.
 */
public void printHourDayPeriodAndZone(final ZonedDateTime zonedDateTime)
{
   final String dateTimeStr
      = DateTimeFormatter.ofPattern("hh B, zzzz").format(zonedDateTime);
   out.println("Hour/Day Period/Zone: \"" + dateTimeStr + "\"");
}

/**
 * Prints representation of supplied {@link ZonedDateTime} with hour,
 * minutes, day period, and time zone details to standard output.
 *
 * @param zonedDateTime Date/time that will be written to standard output
 *    and will include hour, minutes, day period, and zone details.
 */
public void printHourDayMinutePeriodAndZone(final ZonedDateTime zonedDateTime)
{
   final String dateTimeStr
      = DateTimeFormatter.ofPattern("K:mm B z").format(zonedDateTime);
   out.println("Hour/Minute/Day Period/Zone: \"" + dateTimeStr + "\"");
}

The following two lines of output are shown when the two methods above are executed during evening hours:

Hour/Day Period/Zone: "08 in the evening, Mountain Standard Time"
Hour/Minute/Day Period/Zone: "8:07 in the evening MST"

Because the output for the examples to this point were all executed in evening hours, the day period ("in the evening") has been the same for all examples executed above.

The next code listing iterates over each hour of the day to indicate different day period expressions for the different hours when Locale.US is specified. Note that the dates/times constructed in this example have non-zero fractional hours (non-zero minutes, seconds, and nanoseconds).

/**
 * Prints Day Period phraseology for each of 24 hours of day with
 * arbitrary minutes, seconds, and nanoseconds to standard output.
 */
public void printDayPeriodsByHour()
{
   out.println("===== Hours With Non-Zero Minutes/Seconds/Nanoseconds =====");
   final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("hh B");
   for (int hour = 0; hour < 24; hour++)
   {
      final OffsetDateTime dateTime
         = Instant.now().atOffset(ZoneOffset.UTC).withHour(hour);
      out.println("Hour " + hour + ": \"" + dateTimeFormat.format(dateTime) + "\"");
   }
}

The output from running the code above shows how different hours correspond to different "day periods" for US.Locale. Note that this output is based on a date/time with non-zero fractional hours (times are not exact hours).

===== Hours With Non-Zero Minutes/Seconds/Nanoseconds =====
Hour 0: "12 at night"
Hour 1: "01 at night"
Hour 2: "02 at night"
Hour 3: "03 at night"
Hour 4: "04 at night"
Hour 5: "05 at night"
Hour 6: "06 in the morning"
Hour 7: "07 in the morning"
Hour 8: "08 in the morning"
Hour 9: "09 in the morning"
Hour 10: "10 in the morning"
Hour 11: "11 in the morning"
Hour 12: "12 in the afternoon"
Hour 13: "01 in the afternoon"
Hour 14: "02 in the afternoon"
Hour 15: "03 in the afternoon"
Hour 16: "04 in the afternoon"
Hour 17: "05 in the afternoon"
Hour 18: "06 in the evening"
Hour 19: "07 in the evening"
Hour 20: "08 in the evening"
Hour 21: "09 at night"
Hour 22: "10 at night"
Hour 23: "11 at night"

Because the date/time used in the example above has non-zero fractional hours, the en_US day period expressions are "at night", "in the morning", "in the afternoon", and "in the evening".

The next code snippet shows a method that ensures the formatted date/time has "exact hours" (its minutes, seconds, and nanoseconds are all zero).

/**
 * Prints Day Period phraseology for each of 24 hours of day with
 * zero minutes, zero seconds, and zero nanoseconds to standard output.
 */
public void printDayPeriodsByWholeHours()
{
   out.println("===== Exact Hours =====");
   final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("hh B");
   for (int hour = 0; hour < 24; hour++)
   {
      final OffsetDateTime dateTime = OffsetDateTime.of(
         2020, 11, 23, hour, 0, 0, 0, ZoneOffset.UTC);
      out.println("Hour " + hour + ": \"" + dateTimeFormat.format(dateTime) + "\"");
   }
}

When the above code is executed, the following output is seen:

===== Exact Hours =====
Hour 0: "12 midnight"
Hour 1: "01 at night"
Hour 2: "02 at night"
Hour 3: "03 at night"
Hour 4: "04 at night"
Hour 5: "05 at night"
Hour 6: "06 in the morning"
Hour 7: "07 in the morning"
Hour 8: "08 in the morning"
Hour 9: "09 in the morning"
Hour 10: "10 in the morning"
Hour 11: "11 in the morning"
Hour 12: "12 noon"
Hour 13: "01 in the afternoon"
Hour 14: "02 in the afternoon"
Hour 15: "03 in the afternoon"
Hour 16: "04 in the afternoon"
Hour 17: "05 in the afternoon"
Hour 18: "06 in the evening"
Hour 19: "07 in the evening"
Hour 20: "08 in the evening"
Hour 21: "09 at night"
Hour 22: "10 at night"
Hour 23: "11 at night"

The output above is mostly the same as for the output associated with dates/times that had fractional hours, but the whole hours example is different for hour 0 (day period of "midnight") and hour 12 (day period of "noon").

For my last example, I'm going to use Dominican Republic/Spanish ("es DO") for the Locale with the same code just demonstrated. Here is that output:

Hour 0: "12 de la madrugada"
Hour 1: "01 de la madrugada"
Hour 2: "02 de la madrugada"
Hour 3: "03 de la madrugada"
Hour 4: "04 de la madrugada"
Hour 5: "05 de la madrugada"
Hour 6: "06 de la mañana"
Hour 7: "07 de la mañana"
Hour 8: "08 de la mañana"
Hour 9: "09 de la mañana"
Hour 10: "10 de la mañana"
Hour 11: "11 de la mañana"
Hour 12: "12 del mediodía"
Hour 13: "01 de la tarde"
Hour 14: "02 de la tarde"
Hour 15: "03 de la tarde"
Hour 16: "04 de la tarde"
Hour 17: "05 de la tarde"
Hour 18: "06 de la tarde"
Hour 19: "07 de la tarde"
Hour 20: "08 de la noche"
Hour 21: "09 de la noche"
Hour 22: "10 de la noche"
Hour 23: "11 de la noche"

Support for "day period" presentation in formatted dates/times provides Java developers with more flexibility in expressing day period details than simply using AM and PM. All code listings shown in this post are available on GitHub.