forked from openhab/openhab-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[core] Added basic exception classes which supports internationalizat…
…ion (openhab#2549) * New exception class that incorporates support for internationalization * Add ConnectionException, CommunicationException and ConfigurationException Signed-off-by: Laurent Garnier <lg.hc@free.fr> GitOrigin-RevId: 998ce26
- Loading branch information
Showing
5 changed files
with
448 additions
and
0 deletions.
There are no files selected for viewing
203 changes: 203 additions & 0 deletions
203
...thouse.core.i18n.core/src/test/java/org/openhab/core/internal/i18n/I18nExceptionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
/** | ||
* Copyright (c) 2010-2021 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.core.internal.i18n; | ||
|
||
import static org.hamcrest.CoreMatchers.is; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.junit.jupiter.api.Assertions.*; | ||
|
||
import java.util.Locale; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
import org.openhab.core.i18n.AbstractI18nException; | ||
import org.openhab.core.i18n.CommunicationException; | ||
import org.openhab.core.i18n.TranslationProvider; | ||
import org.osgi.framework.Bundle; | ||
|
||
/** | ||
* The {@link I18nExceptionTest} tests all the functionalities of the {@link AbstractI18nException} class. | ||
* | ||
* @author Laurent Garnier - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
@ExtendWith(MockitoExtension.class) | ||
public class I18nExceptionTest { | ||
|
||
private static final String PARAM1 = "ABC"; | ||
private static final int PARAM2 = 50; | ||
|
||
private static final String MSG = "hardcoded message"; | ||
|
||
private static final String KEY1 = "key1"; | ||
private static final String MSG_KEY1 = "@text/" + KEY1; | ||
private static final String RAW_MSG_KEY1 = MSG_KEY1; | ||
private static final String MSG_KEY1_EN = "This is an exception."; | ||
private static final String MSG_KEY1_FR = "Ceci est une exception."; | ||
|
||
private static final String KEY2 = "key2"; | ||
private static final String MSG_KEY2 = "@text/" + KEY2; | ||
private static final String RAW_MSG_KEY2 = String.format("@text/%s [ \"%s\", \"%d\" ]", KEY2, PARAM1, PARAM2); | ||
private static final String MSG_KEY2_EN = String.format("%s: value %d.", PARAM1, PARAM2); | ||
private static final String MSG_KEY2_FR = String.format("%s: valeur %d.", PARAM1, PARAM2); | ||
|
||
private static final String KEY3 = "key3"; | ||
private static final String MSG_KEY3 = "@text/" + KEY3; | ||
private static final String RAW_MSG_KEY3 = String.format("@text/%s [ \"%d\" ]", KEY3, PARAM2); | ||
private static final String MSG_KEY3_EN = String.format("Value %d.", PARAM2); | ||
private static final String MSG_KEY3_FR = String.format("Valeur %d.", PARAM2); | ||
|
||
private static final String CAUSE = "Here is the root cause."; | ||
|
||
private @Nullable @Mock Bundle bundle; | ||
|
||
TranslationProvider i18nProvider = new TranslationProvider() { | ||
@Override | ||
public @Nullable String getText(@Nullable Bundle bundle, @Nullable String key, @Nullable String defaultText, | ||
@Nullable Locale locale, @Nullable Object @Nullable... arguments) { | ||
if (bundle != null) { | ||
if (KEY1.equals(key)) { | ||
return Locale.FRENCH.equals(locale) ? MSG_KEY1_FR : MSG_KEY1_EN; | ||
} else if (KEY2.equals(key)) { | ||
return Locale.FRENCH.equals(locale) ? MSG_KEY2_FR : MSG_KEY2_EN; | ||
} else if (KEY3.equals(key)) { | ||
return Locale.FRENCH.equals(locale) ? MSG_KEY3_FR : MSG_KEY3_EN; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
public @Nullable String getText(@Nullable Bundle bundle, @Nullable String key, @Nullable String defaultText, | ||
@Nullable Locale locale) { | ||
return null; | ||
} | ||
}; | ||
|
||
@Test | ||
public void testMessageWithoutKey() { | ||
CommunicationException exception = new CommunicationException(MSG); | ||
|
||
assertThat(exception.getMessage(), is(MSG)); | ||
assertThat(exception.getLocalizedMessage(), is(MSG)); | ||
assertThat(exception.getMessage(bundle, i18nProvider), is(MSG)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, null), is(MSG)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, Locale.FRENCH), is(MSG)); | ||
assertThat(exception.getRawMessage(), is(MSG)); | ||
assertNull(exception.getCause()); | ||
} | ||
|
||
@Test | ||
public void testMessageWithoutKeyAndWithCause() { | ||
Exception exception0 = new Exception(CAUSE); | ||
CommunicationException exception = new CommunicationException(MSG, exception0); | ||
|
||
assertThat(exception.getMessage(), is(MSG)); | ||
assertThat(exception.getLocalizedMessage(), is(MSG)); | ||
assertThat(exception.getMessage(bundle, i18nProvider), is(MSG)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, null), is(MSG)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, Locale.FRENCH), is(MSG)); | ||
assertThat(exception.getRawMessage(), is(MSG)); | ||
assertNotNull(exception.getCause()); | ||
assertThat(exception.getCause().getMessage(), is(CAUSE)); | ||
} | ||
|
||
@Test | ||
public void testMessageWithKeyButMissingParams() { | ||
CommunicationException exception = new CommunicationException(MSG_KEY1); | ||
|
||
assertNull(exception.getMessage()); | ||
assertNull(exception.getLocalizedMessage()); | ||
assertNull(exception.getMessage(bundle, null)); | ||
assertNull(exception.getMessage(null, i18nProvider)); | ||
assertNull(exception.getLocalizedMessage(bundle, null, Locale.FRENCH)); | ||
assertNull(exception.getLocalizedMessage(null, i18nProvider, Locale.FRENCH)); | ||
assertThat(exception.getRawMessage(), is(RAW_MSG_KEY1)); | ||
assertNull(exception.getCause()); | ||
} | ||
|
||
@Test | ||
public void testMessageWithKeyNoParam() { | ||
CommunicationException exception = new CommunicationException(MSG_KEY1); | ||
|
||
assertNull(exception.getMessage()); | ||
assertNull(exception.getLocalizedMessage()); | ||
assertThat(exception.getMessage(bundle, i18nProvider), is(MSG_KEY1_EN)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, null), is(MSG_KEY1_EN)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, Locale.FRENCH), is(MSG_KEY1_FR)); | ||
assertThat(exception.getRawMessage(), is(RAW_MSG_KEY1)); | ||
assertNull(exception.getCause()); | ||
} | ||
|
||
@Test | ||
public void testMessageWithKeyTwoParams() { | ||
CommunicationException exception = new CommunicationException(MSG_KEY2, PARAM1, PARAM2); | ||
|
||
assertNull(exception.getMessage()); | ||
assertNull(exception.getLocalizedMessage()); | ||
assertThat(exception.getMessage(bundle, i18nProvider), is(MSG_KEY2_EN)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, null), is(MSG_KEY2_EN)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, Locale.FRENCH), is(MSG_KEY2_FR)); | ||
assertThat(exception.getRawMessage(), is(RAW_MSG_KEY2)); | ||
assertNull(exception.getCause()); | ||
} | ||
|
||
@Test | ||
public void testMessageWithKeyOneParam() { | ||
CommunicationException exception = new CommunicationException(MSG_KEY3, PARAM2); | ||
|
||
assertNull(exception.getMessage()); | ||
assertNull(exception.getLocalizedMessage()); | ||
assertThat(exception.getMessage(bundle, i18nProvider), is(MSG_KEY3_EN)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, null), is(MSG_KEY3_EN)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, Locale.FRENCH), is(MSG_KEY3_FR)); | ||
assertThat(exception.getRawMessage(), is(RAW_MSG_KEY3)); | ||
assertNull(exception.getCause()); | ||
} | ||
|
||
@Test | ||
public void testMessageWithKeyAndWithCause() { | ||
Exception exception0 = new Exception(CAUSE); | ||
CommunicationException exception = new CommunicationException(MSG_KEY1, exception0); | ||
|
||
assertNull(exception.getMessage()); | ||
assertNull(exception.getLocalizedMessage()); | ||
assertThat(exception.getMessage(bundle, i18nProvider), is(MSG_KEY1_EN)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, null), is(MSG_KEY1_EN)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, Locale.FRENCH), is(MSG_KEY1_FR)); | ||
assertThat(exception.getRawMessage(), is(RAW_MSG_KEY1)); | ||
assertNotNull(exception.getCause()); | ||
assertThat(exception.getCause().getMessage(), is(CAUSE)); | ||
} | ||
|
||
@Test | ||
public void testCauseOnly() { | ||
Exception exception0 = new Exception(CAUSE); | ||
CommunicationException exception = new CommunicationException(exception0); | ||
|
||
String expectedMsg = String.format("%s: %s", exception0.getClass().getName(), CAUSE); | ||
|
||
assertThat(exception.getMessage(), is(expectedMsg)); | ||
assertThat(exception.getLocalizedMessage(), is(expectedMsg)); | ||
assertThat(exception.getMessage(bundle, i18nProvider), is(expectedMsg)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, null), is(expectedMsg)); | ||
assertThat(exception.getLocalizedMessage(bundle, i18nProvider, Locale.FRENCH), is(expectedMsg)); | ||
assertThat(exception.getRawMessage(), is(expectedMsg)); | ||
assertNotNull(exception.getCause()); | ||
assertThat(exception.getCause().getMessage(), is(CAUSE)); | ||
} | ||
} |
128 changes: 128 additions & 0 deletions
128
...g.opensmarthouse.core.i18n/src/main/java/org/openhab/core/i18n/AbstractI18nException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/** | ||
* Copyright (c) 2010-2021 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.core.i18n; | ||
|
||
import java.util.Locale; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.osgi.framework.Bundle; | ||
|
||
/** | ||
* Provides an exception class for openHAB that incorporates support for internationalization | ||
* | ||
* @author Laurent Garnier - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public abstract class AbstractI18nException extends RuntimeException { | ||
|
||
private String msgKey; | ||
private @Nullable Object @Nullable [] msgParams; | ||
|
||
/** | ||
* | ||
* @param message the exception message; use "@text/key" to reference "key" entry in the properties file | ||
* @param msgParams the optional arguments of the message defined in the properties file | ||
*/ | ||
public AbstractI18nException(String message, @Nullable Object @Nullable... msgParams) { | ||
this(message, null, msgParams); | ||
} | ||
|
||
/** | ||
* | ||
* @param message the exception message; use "@text/key" to reference "key" entry in the properties file | ||
* @param cause the cause (which is saved for later retrieval by the getCause() method). A null value is permitted, | ||
* and indicates that the cause is nonexistent or unknown. | ||
* @param msgParams the optional arguments of the message defined in the properties file | ||
*/ | ||
public AbstractI18nException(String message, @Nullable Throwable cause, @Nullable Object @Nullable... msgParams) { | ||
super(I18nUtil.isConstant(message) ? null : message, cause); | ||
if (I18nUtil.isConstant(message)) { | ||
this.msgKey = I18nUtil.stripConstant(message); | ||
this.msgParams = msgParams; | ||
} else { | ||
this.msgKey = ""; | ||
} | ||
} | ||
|
||
/** | ||
* | ||
* @param cause the cause (which is saved for later retrieval by the getCause() method). | ||
*/ | ||
public AbstractI18nException(Throwable cause) { | ||
super(cause); | ||
this.msgKey = ""; | ||
} | ||
|
||
/** | ||
* Returns the detail message string of this exception. | ||
* | ||
* In case the message starts with "@text/" and the parameters bundle and i18nProvider are not null, the translation | ||
* provider is used to look for the message key in the English properties file of the provided bundle. | ||
* | ||
* @param bundle the bundle containing the i18n properties | ||
* @param i18nProvider the translation provider | ||
* @return the detail message string of this exception instance (which may be null) | ||
*/ | ||
public @Nullable String getMessage(@Nullable Bundle bundle, @Nullable TranslationProvider i18nProvider) { | ||
return getLocalizedMessage(bundle, i18nProvider, Locale.ENGLISH); | ||
} | ||
|
||
/** | ||
* Returns a localized description of this exception. | ||
* | ||
* In case the message starts with "@text/" and the parameters bundle and i18nProvider are not null, the translation | ||
* provider is used to look for the message key in the properties file of the provided bundle containing strings for | ||
* the requested language. | ||
* English language is considered if no language is provided. | ||
* | ||
* @param bundle the bundle containing the i18n properties | ||
* @param i18nProvider the translation provider | ||
* @param locale the language to use for localizing the message | ||
* @return the localized description of this exception instance (which may be null) | ||
*/ | ||
public @Nullable String getLocalizedMessage(@Nullable Bundle bundle, @Nullable TranslationProvider i18nProvider, | ||
@Nullable Locale locale) { | ||
if (msgKey.isBlank() || bundle == null || i18nProvider == null) { | ||
return super.getMessage(); | ||
} else { | ||
return i18nProvider.getText(bundle, msgKey, null, locale != null ? locale : Locale.ENGLISH, msgParams); | ||
} | ||
} | ||
|
||
/** | ||
* Provides the raw message | ||
* | ||
* If the message does not start with "@text/", it returns the same as the getMessage() method. | ||
* If the message starts with "@text/" and no optional arguments are set, it returns a string of this | ||
* kind: @text/key | ||
* If the message starts with "@text/" and optional arguments are set, it returns a string of this kind: @text/key [ | ||
* "param1", "param2" ] | ||
* | ||
* @return the raw message or null if the message is undefined | ||
*/ | ||
public @Nullable String getRawMessage() { | ||
if (msgKey.isBlank()) { | ||
return super.getMessage(); | ||
} | ||
String result = "@text/" + msgKey; | ||
Object @Nullable [] params = msgParams; | ||
if (params != null && params.length > 0) { | ||
result += Stream.of(params).map(param -> String.format("\"%s\"", param == null ? "" : param.toString())) | ||
.collect(Collectors.joining(", ", " [ ", " ]")); | ||
} | ||
return result; | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
....opensmarthouse.core.i18n/src/main/java/org/openhab/core/i18n/CommunicationException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/** | ||
* Copyright (c) 2010-2021 Contributors to the openHAB project | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.openhab.core.i18n; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
|
||
/** | ||
* Provides an exception class for openHAB to be used in case of communication issues with a device | ||
* | ||
* @author Laurent Garnier - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public class CommunicationException extends AbstractI18nException { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
public CommunicationException(String message, @Nullable Object @Nullable... msgParams) { | ||
super(message, msgParams); | ||
} | ||
|
||
public CommunicationException(String message, @Nullable Throwable cause, @Nullable Object @Nullable... msgParams) { | ||
super(message, cause, msgParams); | ||
} | ||
|
||
public CommunicationException(Throwable cause) { | ||
super(cause); | ||
} | ||
} |
Oops, something went wrong.