Skip to content

Commit 3698896

Browse files
committed
fix: update formatting for consistency, add examples of missing message format
1 parent 030a557 commit 3698896

7 files changed

Lines changed: 116 additions & 59 deletions

File tree

content/docs/week08.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: "08 I18N, exceptions and logging"
33
description: ""
44
date: 2025-04-24T10:31:29+02:00
5-
draft: true
5+
draft: false
66
images: []
77
slug: "internationalisation"
88
menu:

topics/code/exex/src/exex/CircleWithException.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ public class CircleWithException {
1616
* Construct a circle with radius 1
1717
*/
1818
public CircleWithException() {
19-
this( 1.0 );
19+
this(1.0);
2020
}
2121

2222
/**
2323
* Construct a circle with a specified radius
2424
*/
25-
public CircleWithException( double newRadius ) {
26-
setRadius( newRadius );
27-
numberOfObjects ++;
25+
public CircleWithException(double newRadius) {
26+
setRadius(newRadius);
27+
numberOfObjects++;
2828
}
2929

3030
/**
@@ -37,13 +37,13 @@ public double getRadius() {
3737
/**
3838
* Set a new radius
3939
*/
40-
public void setRadius( double newRadius )
40+
public void setRadius(double newRadius)
4141
throws IllegalArgumentException {
42-
if ( newRadius >= 0 ) {
42+
if (newRadius >= 0) {
4343
radius = newRadius;
4444
} else {
4545
throw new IllegalArgumentException(
46-
"Radius cannot be negative" );
46+
"Radius cannot be negative");
4747
}
4848
}
4949

topics/code/exex/src/exex/QuotientWithException.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,31 @@
44

55
public class QuotientWithException {
66

7-
public static int quotient( int number1, int number2 ) {
8-
if ( number2 == 0 ) {
9-
throw new ArithmeticException( "Divisor cannot be zero" );
7+
public static int quotient(int number1, int number2) {
8+
if (number2 == 0) {
9+
throw new ArithmeticException("Divisor cannot be zero");
1010
}
1111

1212
return number1 / number2;
1313
}
1414

15-
public static void main( String[] args ) {
16-
Scanner input = new Scanner( System.in );
15+
public static void main(String[] args) {
16+
Scanner input = new Scanner(System.in);
1717

1818
// Prompt the user to enter two integers
19-
System.out.print( "Enter two integers: " );
19+
System.out.print("Enter two integers: ");
2020
int number1 = input.nextInt();
2121
int number2 = input.nextInt();
2222

2323
try {
24-
int result = quotient( number1, number2 );
25-
System.out.println( number1 + " / " + number2 + " is "
26-
+ result );
27-
} catch ( ArithmeticException ex ) {
28-
System.out.println( "Exception: an integer "
29-
+ "cannot be divided by zero " );
24+
int result = quotient(number1, number2);
25+
System.out.println(number1 + " / " + number2 + " is "
26+
+ result);
27+
} catch (ArithmeticException ex) {
28+
System.out.println("Exception: an integer "
29+
+ "cannot be divided by zero ");
3030
}
3131

32-
System.out.println( "Execution continues ..." );
32+
System.out.println("Execution continues ...");
3333
}
3434
}

topics/code/trywith/src/Trywith.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,21 @@
1111
*/
1212
public class Trywith {
1313

14-
public static void main( String[] args ) throws IOException {
14+
public static void main(String[] args) throws IOException {
1515

1616
try (
17-
File f = new File( "data.txt" );
17+
File f = new File("data.txt");
1818
Scanner scanner = new Scanner(f); //<1>
1919
) {
2020
String s1 = scanner.next();
21-
} catch ( FileNotFoundException fnfe ) {
21+
} catch (FileNotFoundException fnfe) {
2222
// deal with exceptions.
2323
} //<2>
2424
}
2525

2626
interface Ikk{
2727
private static void hello(){
28-
System.out.println( "Hello" );
28+
System.out.println("Hello");
2929
}
3030
}
3131
}

topics/exceptions.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,10 @@ Although you cannot simply iterate through these exceptions, getting all in a lo
148148
[source,java]
149149
----
150150
Throwable t = ex.getCause();
151-
while ( t != null ) {
151+
while (t != null) {
152152
// do something with t like print or getMessage;
153153
154-
t= t.getCause();
154+
t = t.getCause();
155155
}
156156
----
157157

topics/internationalisation.adoc

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,20 @@ so that the whole test method reads
5050
[source,java]
5151
----
5252
@ParameterizedTest
53-
@CsvSource( {
53+
@CsvSource({
5454
"a,greenCircle,blueCircle,500.0,400.0,100.0,100.0,500.0",
5555
"b,redCircle,blueCircle,400.0,100.0,100.0,100.0,500.0",
56-
"c,greenCircle,redCircle,300.0,100.0,100.0,400.0,100.0", } )
57-
public void tLength( String line, String p1, String p2, double expected,
58-
double x1, double y1, double x2, double y2 ) throws ParseException {
56+
"c,greenCircle,redCircle,300.0,100.0,100.0,400.0,100.0", })
57+
public void tLength(String line, String p1, String p2, double expected,
58+
double x1, double y1, double x2, double y2) throws ParseException {
5959
double xOrg = stage.getX();
6060
double yOrg = stage.getY();
6161
FxRobot rob = new FxRobot();
62-
rob.drag( '#' + p1 ).dropTo( xOrg + x1, yOrg + y1 );
63-
rob.drag( '#' + p2 ).dropTo( xOrg + x2, yOrg + y2 );
64-
String ltext = labelMap.get( line ).apply( triangulator ).getText();
65-
double l = getDoubleConsideringLocale( ltext.split( ":" )[ 1 ] ); // <1>
66-
assertThat( l ).isCloseTo( expected, within( 0.1 ) );
62+
rob.drag('#' + p1).dropTo(xOrg + x1, yOrg + y1);
63+
rob.drag('#' + p2).dropTo(xOrg + x2, yOrg + y2);
64+
String ltext = labelMap.get(line).apply(triangulator).getText();
65+
double l = getDoubleConsideringLocale(ltext.split(":")[1]); // <1>
66+
assertThat(l).isCloseTo(expected, within(0.1));
6767
// fail( "method tLength reached end. You know what to do." );
6868
}
6969
----
@@ -79,9 +79,9 @@ public void tLength( String line, String p1, String p2, double expected,
7979
* @return the double
8080
* @throws ParseException if the string does not parse to double.
8181
*/
82-
static double getDoubleConsideringLocale( String input )
82+
static double getDoubleConsideringLocale(String input)
8383
throws ParseException {
84-
return DecimalFormat.getNumberInstance().parse( input ).doubleValue(); // <1>
84+
return DecimalFormat.getNumberInstance().parse(input).doubleValue(); // <1>
8585
}
8686
8787
/**
@@ -91,14 +91,9 @@ static double getDoubleConsideringLocale( String input )
9191
* @return the double.
9292
* @throws ParseException if the string does not parse to double.
9393
*/
94-
<<<<<<< Updated upstream
95-
static double getDoubleConsideringLocale( Locale locale, String input )
94+
static double getDoubleConsideringLocale(Locale locale, String input)
9695
throws ParseException {
97-
=======
98-
static double getDoubleConsideringLocale( Locale locale, String input )
99-
throws ParseException {
100-
>>>>>>> Stashed changes
101-
return DecimalFormat.getNumberInstance(locale).parse( input ).doubleValue();
96+
return DecimalFormat.getNumberInstance(locale).parse(input).doubleValue();
10297
}
10398
----
10499

@@ -107,12 +102,54 @@ static double getDoubleConsideringLocale( Locale locale, String input )
107102
.Set the locale for the execution. Useful for tests.
108103
[source,java]
109104
----
110-
Locale.setDefault( Locale.GERMANY ); // <1>
105+
Locale.setDefault(Locale.GERMANY); // <1>
111106
----
112107

113108
<1> Set the locale to GERMANY if it isn't already. Similar for other languages.
114109

115110

111+
=== ResourceBundle and MessageFormat
112+
113+
Java's `ResourceBundle` is the standard mechanism for externalising locale-specific strings into `.properties` files.
114+
Place one base file plus one file per supported locale under `src/main/resources`:
115+
116+
----
117+
src/main/resources/pub/inthepub.properties ← default (English)
118+
src/main/resources/pub/inthepub_nl_NL.properties ← Dutch
119+
src/main/resources/pub/inthepub_de_DE.properties ← German
120+
----
121+
122+
A `.properties` file is a plain key=value text file.
123+
Keys that contain `{0}`, `{1}`, … are parameterised placeholders:
124+
125+
.greetings.properties
126+
[source,properties]
127+
----
128+
greeting=Hello, {0}!
129+
farewell=Goodbye, {0}. See you on {1}.
130+
----
131+
132+
To load the bundle for the current locale and look up a key at runtime:
133+
134+
.Looking up and formatting a localized message
135+
[source,java]
136+
----
137+
ResourceBundle bundle = ResourceBundle.getBundle(
138+
"greetings", // <1>
139+
Locale.getDefault()); // <2>
140+
if (bundle.containsKey("greeting")) { // <3>
141+
String pattern = bundle.getString("greeting");
142+
String message = MessageFormat.format(pattern, "World"); // <4>
143+
}
144+
----
145+
146+
<1> Base name of the properties file (no extension, no locale suffix) — Java appends `_nl_NL` etc. automatically.
147+
<2> Use the current default locale. Change it with `Locale.setDefault(...)`, e.g. in a test.
148+
<3> `containsKey` guards against `MissingResourceException` when a key is absent.
149+
<4> `MessageFormat.format` substitutes `{0}`, `{1}`, … with the supplied arguments.
150+
151+
The JVM picks the most specific file available: `inthepub_nl_NL.properties` → `inthepub_nl.properties` → `inthepub.properties` (fallback).
152+
116153
=== Testing Localized Exceptions
117154

118155
The standard way of testing exceptions with assertj is explained in link:week01.html#_assert_exceptions[week01].
@@ -121,23 +158,22 @@ To get to the localized message, which contains the message as translated by the
121158

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

124-
.To make a long story very short: here is an example:
161+
.To make a long story very short: here is an example (using the pub exercise domain):
125162
[source,java]
126163
----
127-
String[] keys = keyWords.split( "\\|");
128-
assertThatThrownBy( () -> {
129-
MainSimulation.main( args );
130-
} ).isExactlyInstanceOf( exceptionMap.get( expectionClassName ) )
131-
.extracting( e -> e.getLocalizedMessage() ) // <1>
164+
Locale.setDefault(Locale.forLanguageTag("nl-NL"));
165+
assertThatThrownBy(() -> barkeeper.serve(drinker, volume))
166+
.isExactlyInstanceOf(EmptyStockException.class)
167+
.extracting(e -> e.getLocalizedMessage()) // <1>
132168
.asString() //<2>
133-
.contains( keys ); //<3>
169+
.contains("uitverkocht"); //<3>
134170
----
135171

136172
<1> extract using [blue]`Function<? super Throwable,​T>`, [black]`e -> getLocalizedMessage()` in this case.
137173
<2> Get the assertion for in String. Do [red]*not* use `toString()`, because that produces a _String_, not an [blue]*AbstractStringAssert*.
138174
<3> And use the assert to check that the string contains the required key information.
139175

140-
.If you turn on type hints in NetBeans-IDE (or in vscode/intelij) you can see what the type is on which you call `contains(keys)`
176+
.If you turn on type hints in NetBeans-IDE (or in VS Code/IntelliJ) you can see what the type is on which you call `contains(keys)`
141177
image::assertjtypehints.png
142178

143179

topics/logging.adoc

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ Logging can be a very powerful instrument to squeeze very much or very precise i
55
.Using a supplier suppresses the generation of the log when the log level is not active.
66
[source,java]
77
----
8-
public static void main( String[] args ) {
9-
Logger logger = Logger.getLogger( Main.class.getName() ); // <1>
10-
logger.log( Level.FINE, // <2>
11-
()-> String.format("Hello %1$s args length= %2$d ", //<3>
12-
"world", args.length) //<4>
8+
public static void main(String[] args) {
9+
Logger logger = Logger.getLogger(Main.class.getName()); // <1>
10+
logger.log(Level.FINE, // <2>
11+
()-> String.format("Hello %1$s args length= %2$d ", //<3>
12+
"world", args.length) //<4>
1313
);
1414
}
1515
----
@@ -73,6 +73,27 @@ image::logging-activation.png[title="Project specific logging in NetBeans IDE"]
7373
7474
====
7575

76+
=== Logging with i18n messages
77+
78+
You can combine logging with `ResourceBundle` and `MessageFormat` to produce localised log messages.
79+
This is useful when the locale may change between runs (e.g. per user or per test).
80+
81+
.Log a message using a ResourceBundle key and MessageFormat
82+
[source,java]
83+
----
84+
ResourceBundle bundle = ResourceBundle.getBundle(
85+
"greetings", Locale.getDefault());
86+
String pattern = bundle.getString("farewell"); // <1>
87+
String date = LocalDate.now().format(
88+
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)); // <2>
89+
logger.log(Level.INFO, () ->
90+
MessageFormat.format(pattern, "World", date)); // <3>
91+
----
92+
93+
<1> Look up the localised pattern from the bundle; `farewell=Goodbye, {0}. See you on {1}.` in the default locale.
94+
<2> Format today's date according to the current locale (e.g. `"Monday, 11 May 2026"` in English).
95+
<3> Supply the lambda so the string is only built when the INFO level is actually active.
96+
7697
Read a bit more about logging in the book and get some additional information using https://sematext.com/blog/java-logging/.
7798

7899

0 commit comments

Comments
 (0)