PRC2

Internationalisation (I18N)

The Date and Time API

In Java, an Instant represents a point on the time line. The origin (called epoch) is arbitrarily set at midnight of the first of January 1970 Greenwich time (UTC). Instant.now() returns the current point on the time line, being the number of seconds since the epoch (stored as long) and the number of nano seconds (stored as int). Each day has 24x60x60 = 86400 seconds. A Duration is the amount of time between two instants. Both Instant and Duration are immutable.

An Instant does not have a human readable format. The two kinds of human time in the java API are local date/time and zoned time. Local date/time has a date and/or time of day, but no associated time zone. Examples:

calendar example LocalDate Local Date example. alarmclock Local Time Example.

Do not use zoned time, unless really necessary. Today’s date can be retrieved using LocalDate.now(), an arbitrary date with LocalDate.of(<year>, <month>, <day>) like LocalDate.of(2021, 5, 1) being first of May 2021. The amount of time between dates is called a Period. Check the java API to read about convenient methods of LocalDate and Period. An interesting one is the datesUntil(…​) method on LocalDate, which returns a Stream<LocalDate>.

LocalTime objects can be created like LocalDates, e.g.: LocalTime endOfLecture = LocalTime.now().plusHours(1);

Zoned times make use of time zomes. Java uses the IANA database: https://www.iana.org/time-zones To make a zoned time out of a LocalDateTime you can use the ZonedDateTime.of() method or do it like this: ZonedDateTime current = LocalDateTime.now().atZone(ZoneId.of("Europe/Amsterdam"));. Now you get a specific Instant in time. The other way around, you can can find the corresponding ZonedDateTime in another time zone using instant.atZone(…​).

To represent dates and times in readable and customizable formats, use the DateTimeFormatter class. It can use predefined standard formatters, Locale specific formatters and formatters with custom patterns.

This paragraph is based on Core Java Volume 2, 11th edition, chapter 6 by Cay Horstmann.

Number Formats

Some of you might have noticed that one of the teacher tests in the FXTriangulate exercise fails on your machine.

This is because on the machine we developed, the locale is set to en_US. This causes the numbers to be formatted in the way that Double.parseDouble(String input) expects it. If you run the same tests on a machine with say a German Locale, the test that reads the length back from the label, use Double.parseDouble(), which is then surprised to find a comma instead of the decimal point, and fails with a format exception.

To solve that, modify the test in method tLength such that the line
double l = Double.parseDouble( ltext.split( ":" )[ 1 ] ); reads
double l = getDoubleConsideringLocale( ltext.split( ":" )[ 1 ] );, so that the whole test method reads

fxtriangulate.GUITest.tLength
@ParameterizedTest
@CsvSource( {
    "a,greenCircle,blueCircle,500.0,400.0,100.0,100.0,500.0",
    "b,redCircle,blueCircle,400.0,100.0,100.0,100.0,500.0",
    "c,greenCircle,redCircle,300.0,100.0,100.0,400.0,100.0", } )
public void tLength( String line, String p1, String p2, double expected,
        double x1, double y1, double x2, double y2 ) throws ParseException {
    double xOrg = stage.getX();
    double yOrg = stage.getY();
    FxRobot rob = new FxRobot();
    rob.drag( '#' + p1 ).dropTo( xOrg + x1, yOrg + y1 );
    rob.drag( '#' + p2 ).dropTo( xOrg + x2, yOrg + y2 );
    String ltext = labelMap.get( line ).apply( triangulator ).getText();
    double l = getDoubleConsideringLocale( ltext.split( ":" )[ 1 ] ); 1
    assertThat( l ).isCloseTo( expected, within( 0.1 ) );
//        fail( "method tLength reached end. You know what to do." );
}
  1. changed line.
Consider the local in which the number is written.
/**
    * Use the default locale to parse a double value from a string.
    * @param input string
    * @return the double
    * @throws ParseException if the string does not parse to double.
    */
static double getDoubleConsideringLocale( String input )
   throws ParseException {
    return DecimalFormat.getNumberInstance().parse( input ).doubleValue(); 1
}

/**
* Use the given locale to parse a double value from a string.
* @param locale to use.
* @param input string.
* @return the double.
* @throws ParseException if the string does not parse to double.
*/
<<<<<<< Updated upstream
static double getDoubleConsideringLocale( Locale locale, String input )
    throws ParseException {
=======
static double getDoubleConsideringLocale( Locale locale, String input )
  throws ParseException {
>>>>>>> Stashed changes
    return DecimalFormat.getNumberInstance(locale).parse( input ).doubleValue();
}
  1. DecimalFormat.getNumberInstance gets a format that understands the default locale.
Set the locale for the execution. Useful for tests.
Locale.setDefault( Locale.GERMANY ); 1
  1. Set the locale to GERMANY if it isn’t already. Similar for other languages.

Testing Localized Exceptions

The standard way of testing exceptions with assertj is explained in week01.

To get to the localized message, which contains the message as translated by the locale framework is a bit more involved.

Luckily, AssertJ allows you to extract information from a Throwable, by using an extractor function. Now the Lambda bells should ring.

To make a long story very short: here is an example:
String[] keys = keyWords.split( "\\|");
assertThatThrownBy( () -> {
            MainSimulation.main( args );
} ).isExactlyInstanceOf( exceptionMap.get( expectionClassName ) )
        .extracting( e -> e.getLocalizedMessage() ) 1
        .asString()           2
        .contains( keys ); 3
  1. extract using Function<? super Throwable,​T>, e → getLocalizedMessage() in this case.
  2. Get the assertion for in String. Do not use toString(), because that produces a String, not an AbstractStringAssert.
  3. And use the assert to check that the string contains the required key information.
If you turn on type hints in NetBeans-IDE (or in intelij) you can see what the type is on which you call contains(keys)

image::assertjtypehints.png

Additional Pointers

  • If you haven’t read the Horstmann book but you need an introduction into Internationalization, read this tutorial from DZONE here. Make sure to read the bit about Resource Bundles, as you use them in the exercise for this week.
  • Jakob Jenkov also has a tutorial on Java Internationalization

Exceptions Summary

catch
Figure 1. catching, the general idea.

Exception-Handling

  • Throw statement (cfr. throw new YourFavoriteException())
  • try block: the code that is executed in normal circumstances, but might throw an exception.
  • catch block: the code that is executed to handle an exception that was thrown in the try block. The so called catch–block-parameter specifies which exception type can be caught by the catch block.
  • Examples:
Quotient with Exception
        // Prompt the user to enter two integers
        System.out.print( "Enter two integers: " );
        int number1 = input.nextInt();
        int number2 = input.nextInt();

        try {
            int result = quotient( number1, number2 );
            System.out.println( number1 + " / " + number2 + " is "
                    + result );
        } catch ( ArithmeticException ex ) {
            System.out.println( "Exception: an integer "
                    + "cannot be divided by zero " );
        }

        System.out.println( "Execution continues ..." );
Circle with Exception
    /**
     * Set a new radius
     */
    public void setRadius( double newRadius )
            throws IllegalArgumentException {
        if ( newRadius >= 0 ) {
            radius = newRadius;
        } else {
            throw new IllegalArgumentException(
                    "Radius cannot be negative" );
        }
    }

Exception types

exceptionhierarchy
Figure 2. Exception hierarchy with Throwable at the top
  • System errors are thrown by JVM and represented in the Error class. The Error class describes internal system errors. Such errors rarely occur. If one does, there is little you can do beyond notifying the user and trying to terminate the program gracefully. Errors are unchecked exceptions. However, often you are too late and the JVM will take over or might crash itself.
  • Exception describes errors caused by your program and external circumstances. These errors can be caught and handled by your program.
    • RuntimeException is caused by programming errors, such as bad casting, accessing an out-of-bounds array, and numeric errors. Typically the programmer (maybe you) is to blame.

Checked Exceptions vs. Unchecked Exceptions

  • RuntimeException, Error and their subclasses are known as unchecked exceptions. All other exceptions are known as checked exceptions, meaning that the compiler forces the programmer to check and deal with the exceptions.
  • In most cases, unchecked exceptions reflect programming logic errors that are not recoverable. For example:
    • A NullPointerException is thrown if you access an object through a reference variable before an object is assigned to it;
    • an IndexOutOfBoundsException is thrown if you access an element in an array outside the bounds of the array.
  • Unchecked exceptions can occur anywhere in the program. To avoid cumbersome overuse of try-catch blocks, Java does not mandate you to write code to catch unchecked exceptions.
  • Often runtime exceptions point to a logic fault in the program, such as forgetting a nullity check before dereferencing a reference result from a method, or violating a precondition.
    • Null values often indicate that an expected value could not be produced, such as in a search. If you are designing an API with such search, better return an empty collection (List, Set, Map) instead. Empty collections are predefined and there for cheap to return. Also, since Java 8, the preferred return type for some method that may of may not produce a result is java.util.Optional<T>. Usage explanation in Guide to Java 8 Optional on Baeldung.

More on exception handling and finally clause

throwandcatch
Figure 3. Exception handling in Java consists of declaring exceptions, throwing exceptions, and catching and processing exceptions.
  • The code in the optional finally block is executed under all circumstances, regardless of whether an exception occurs in the try block or is caught. The code is called before the method is left.

Custom Exception classes

You can roll your own exceptions, in case your "business" has requirements that demand this.

Circle with Radius Exception
        try {
            setRadius(newRadius);
            numberOfObjects++;
        } catch (InvalidRadiusException ex) {
            ex.printStackTrace();
        }
Invalid Radius Exception
public class InvalidRadiusException extends Exception {

    private final double radius;

    /**
     * Construct an exception
     */
    public InvalidRadiusException(double radius) {
        super("Invalid radius " + radius);
        this.radius = radius;
    }

    /**
     * Return the radius
     */
    public double getRadius() {
        return radius;
    }
}
Catch Circle with Radius Exception
        try {
            CircleWithRadiusException c1 = new CircleWithRadiusException(5);
            c1.setRadius(-5);
            CircleWithRadiusException c3 = new CircleWithRadiusException(0);
        } catch (InvalidRadiusException ex) {
            System.out.println(ex);
        }

Try with resources.

File is one of the classes that has operations (methods) that throw exceptions. java.util.Scanner implements AutoCloseable and as such has a close() method. When constructed with file as argument, it will use said file as input and close the file, when the scanner is closed. It is good practice to close a resource once it has been 'consumed', because this releases the resource. The purpose of such a close operation is to signal that the resource should be brought to a state that is safe and can be freed for other use.

Classes that have such properties are known by the generic name resource, something that you need but is potentially scarce.
Since Java 7, we have a feature called try with resources . It adds a new construct to the try-catch block, namely the resources that you are about to use. There can be more than one resource.

try with resources, using a scanner and a file.
        try (
          File f = new File( "data.txt" );
          Scanner scanner = new Scanner(f); 1
        ) {
          String s1 = scanner.next();
        } catch ( FileNotFoundException fnfe ) {
            // deal with exceptions.
        } 2
  1. Scanner is also autoclosable.
  2. No other catch needed, that is only required if you want to deal with the exceptions that could occur. No finally needed to close resources.

Once the try with resources is done with the resources, the resources are automatically closed and freed. For this to work, the resource must implement the java.lang.Autoclosable interface. You also save yourselves the tedious work of closing when an exception occurred. That is also taken care of. So when your Java level is greater or equal to Java 7, use try-with-resources whenever you can, because it will save you the work of closing the resources in the finally block.

You declare the resources in the try-with-resources in the natural order, the earlier resource being used by those further on. They will be closed in the reverse order of declaration. In the above case that would mean:

  1. Scanner scanner closed
  2. InputStream in closed
  3. File f closed

which is the natural order of freeing or closing the resources.

Choose: at least one of catch or finally block

Note that the compiler insists on having at least one of

  • a catch block (multiple allowed) or
  • a finally block (at most one)

So try{…​} is not allowed, but try(AutoCloseable resource){…​} is, because the compiler will construct an implicit finally block for you. It is the same idea of having a default constructor, when you did not write any.
If you decide to not deal with any checked exception, your method can simply pass them on to the caller, and let the caller deal with the situation.

Chaining of exceptions

As can be seen from the class diagram in figure Exception hierarchy with Throwable at the top, all exceptions are descendants of java.lang.Throwable.

Exceptions can be chained, that is, one exception can hold a reference to another exception that is the actual cause of the exception being thrown. (Since Java 1.4 for all Throwables).

If you look at the API doc of java.lang.Throwable, you will see that it provides several constructors.
Three of them allow you to add as parameter of type Throwable, with the formal and aptly chosen name cause. The receiver of the Throwable (or should I say catcher) can inspect any descendant of Throwable about a deeper cause, using the method Throwable.getCause(). This can be seen as chain of Throwables, also called nested exceptions.

Although you cannot simply iterate through these exceptions, getting all in a loop is simple:

Reading all causes of an exception.
Throwable t = ex.getCause();
while ( t != null ) {
  // do something with t like print or getMessage;

  t= t.getCause();
}

Logging

Logging can be a very powerful instrument to squeeze very much or very precise information from a running application. It can be configured without modifying the source code. Use logging instead of system.out.print

Using a supplier suppresses the generation of the log when the log level is not active.
public static void main( String[] args ) {
    Logger logger = Logger.getLogger( Main.class.getName() ); 1
    logger.log( Level.FINE,                                   2
       ()-> String.format("Hello %1$s args length= %2$d ",    3
                          "world", args.length)               4
    );
}
  1. Create a specific logger for this class.
  2. This log statement only does 'work' when the log level is set to FINE or higher (FINER, FINEST)
  3. Note the use of positional parameters in the format string. It can be quite useful. See it as a free and random tip.
  4. This lambda has the shape of a supplier and is only evaluated or computed when the logging level is active.

Replace soutv by using a log macro

When you use a supplier to supply the message that is logged, when logging is active, logging can become cheap. To make this work, you might want to teach your IDE to help a bit here, by adding a keyboard macro that adds logging instead of simple System.out.println(…​).

logv definition for NetBeans IDE
logger.fine( ()->"${EXP instanceof="<any>" default="exp"} = " + ${EXP});

Now logging becomes simpler than ever.

  1. First create a logger with the already define macro logr [tab] and then
  2. inside the methods use logv [tab] instead of [black] soutv wherever you would like to output stuff for debugging.

In your logging.properties file add the proper level for your class or as global and you can turn the logging on or off whenever you need to.

Configure logging

When you copy the file logging.properties from its default location $JAVA_HOME/conf/logging.properties to your projects directory, preferably under a conf directory, you can modify and adapt that file to make logging effective in a very precise way, by specifying what level you want for what class.

conf/logging.properties file excerpt. Set logging to fine for one program.
#.properties
handlers= java.util.logging.ConsoleHandler
.level=FINE 1
java.util.logging.ConsoleHandler.level=FINE
logdem.LogDem.level=FINE 2
  1. specify global level
  2. specify for one class Then in the java startup (for instance in a run script or in one of the IDE settings), make sure this java property specifies the file.

In your IDE add -Djava.util.logging.config.file=conf/logging.properties to the java run actions.

Say the class that needs debugging is called logging.Main. Then in the logging properties file (see above) add logging.Main.level = FINE, and make sure that you start the JVM with the proper login properties file.

Read a bit more about logging in the book and get some additional information using https://sematext.com/blog/java-logging/.