/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.internal.i18n;
import static org.junit.Assert.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import org.junit.After;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.geode.i18n.StringId;
import org.apache.geode.test.junit.categories.IntegrationTest;
/**
* This class tests all basic i18n functionality.
*/
@Category(IntegrationTest.class)
public class BasicI18nJUnitTest {
private static final Locale DEFAULT_LOCALE = Locale.getDefault();
// static final Class DEFAULT_RESOURCEBUNDLE = StringIdResourceBundle_ja.class;
// static final Class JAPAN_RESOURCEBUNDLE = StringIdResourceBundle_ja.class;
private static final StringId messageId =
(StringId) LocalizedStrings.TESTING_THIS_IS_A_TEST_MESSAGE;
private static final String englishMessage = "This is a test message.";
private static final String japaneseMessage = "msgID " + messageId.id + ": " + "これはテストメッセージである。";
private static final Integer messageArg = new Integer(1);
private static final StringId messageIdWithArg =
(StringId) LocalizedStrings.TESTING_THIS_MESSAGE_HAS_0_MEMBERS;
private static final String englishMessageWithArg = "Please ignore: This message has 1 members.";
private static final String japaneseMessageWithArg =
"msgID " + messageIdWithArg.id + ": Please ignore: このメッセージに 1 メンバーがある。";
private static final String englishMessageWithArgMissing =
"Please ignore: This message has {0} members.";
private static final String japaneseMessageWithArgMissing =
"msgID " + messageIdWithArg.id + ": Please ignore: このメッセージに {0} メンバーがある。";
@After
public void tearDown() {
// reset to the original
StringId.setLocale(DEFAULT_LOCALE);
}
@Test
public void testSetLocale() {
// Verify we are starting in a known state
assertTrue(DEFAULT_LOCALE.equals(getCurrentLocale()));
StringId.setLocale(Locale.FRANCE);
assertTrue(Locale.FRANCE.equals(getCurrentLocale()));
StringId.setLocale(Locale.FRANCE);
assertTrue(Locale.FRANCE.equals(getCurrentLocale()));
StringId s = LocalizedStrings.UNSUPPORTED_AT_THIS_TIME;
assertTrue(s.toString().equals(s.toLocalizedString()));
StringId.setLocale(Locale.JAPAN);
assertTrue(Locale.JAPAN.equals(getCurrentLocale()));
if (getActiveResourceBundle().usingRawMode()) {
assertTrue(s.toString().equals(s.toLocalizedString()));
} else {
assertFalse(s.toString().equals(s.toLocalizedString()));
}
}
/**
* Tests {@link StringId#toLocalizedString()} and {@link StringId#toString()} Currently three
* languages are tested: English, French, and Japanese. French is tested under the assumption that
* it will never be implemented and thus tests an unimplemented language. Each Locale is tested
* for these things: 1) toString() no arguments 2) toLocalizedString() no arguments 3)
* toString(new Integer(1)) one argument 4) toLocalizedString(new Integer(1)) one argument 5)
* toString() wrong number or arguments, too many 6) toLocalizedString() wrong number or
* arguments, too many 7) toString() wrong number or arguments, too few 8) toLocalizedString()
* wrong number or arguments, too few
*/
@Test
public void testToLocalizedString() {
Object[] missingArgs = new Object[] {};
Object[] extraArgs = new Object[] {messageArg, "should not print"};
assertEquals(messageId.toString(), englishMessage);
assertEquals(messageId.toLocalizedString(), englishMessage);
assertEquals(messageIdWithArg.toString(messageArg), englishMessageWithArg);
assertEquals(messageIdWithArg.toLocalizedString(messageArg), englishMessageWithArg);
assertEquals(messageIdWithArg.toString(extraArgs), englishMessageWithArg);
assertEquals(messageIdWithArg.toLocalizedString(extraArgs), englishMessageWithArg);
assertEquals(messageIdWithArg.toString(missingArgs), englishMessageWithArgMissing);
assertEquals(messageIdWithArg.toLocalizedString(missingArgs), englishMessageWithArgMissing);
/**
* This is assuming that French isn't implemented and will still get the English resource
* bundle. The second assert will fail if this isn't the case.
**/
StringId.setLocale(Locale.FRANCE);
assertTrue(Locale.FRANCE.equals(getCurrentLocale()));
assertEquals(messageId.toString(), englishMessage);
assertEquals(messageId.toLocalizedString(), englishMessage);
// Now with a message expecting one argument
assertEquals(messageIdWithArg.toString(messageArg), englishMessageWithArg);
assertEquals(messageIdWithArg.toLocalizedString(messageArg), englishMessageWithArg);
assertEquals(messageIdWithArg.toString(extraArgs), englishMessageWithArg);
assertEquals(messageIdWithArg.toLocalizedString(extraArgs), englishMessageWithArg);
assertEquals(messageIdWithArg.toString(missingArgs), englishMessageWithArgMissing);
assertEquals(messageIdWithArg.toLocalizedString(missingArgs), englishMessageWithArgMissing);
/**
* We no longer bundle the JAPAN localized strings with the product so the following now expects
* english msgs.
*/
StringId.setLocale(Locale.JAPAN);
assertTrue(Locale.JAPAN.equals(getCurrentLocale()));
assertEquals(messageId.toString(), englishMessage);
if (getActiveResourceBundle().usingRawMode()) {
assertEquals(messageId.toLocalizedString(), englishMessage);
} else {
assertEquals(messageId.toLocalizedString(), japaneseMessage);
}
// Now with a message expecting one argument
assertEquals(messageIdWithArg.toString(messageArg), englishMessageWithArg);
if (getActiveResourceBundle().usingRawMode()) {
assertEquals(messageIdWithArg.toLocalizedString(messageArg), englishMessageWithArg);
} else {
assertEquals(messageIdWithArg.toLocalizedString(messageArg), japaneseMessageWithArg);
}
assertEquals(messageIdWithArg.toString(extraArgs), englishMessageWithArg);
if (getActiveResourceBundle().usingRawMode()) {
assertEquals(messageIdWithArg.toLocalizedString(extraArgs), englishMessageWithArg);
} else {
assertEquals(messageIdWithArg.toLocalizedString(extraArgs), japaneseMessageWithArg);
}
assertEquals(messageIdWithArg.toString(missingArgs), englishMessageWithArgMissing);
if (getActiveResourceBundle().usingRawMode()) {
assertEquals(messageIdWithArg.toLocalizedString(missingArgs), englishMessageWithArgMissing);
} else {
assertEquals(messageIdWithArg.toLocalizedString(missingArgs), japaneseMessageWithArgMissing);
}
}
@Test
public void testEnglishLanguage() {
StringId.setLocale(Locale.ENGLISH);
assertEquals(messageId.toLocalizedString(), englishMessage);
}
@Test
public void testJapaneseLanguage() {
StringId.setLocale(Locale.JAPANESE);
if (getActiveResourceBundle().usingRawMode()) {
assertEquals(messageId.toLocalizedString(), englishMessage);
} else {
assertEquals(messageId.toLocalizedString(), japaneseMessage);
}
}
@Test
public void testAlternateEnglishCountries() {
StringId.setLocale(Locale.CANADA);
assertEquals(messageId.toLocalizedString(), englishMessage);
StringId.setLocale(Locale.UK);
assertEquals(messageId.toLocalizedString(), englishMessage);
}
/**
* Use reflection to find all instances of StringId defined within the classes listed in
* StringIdDefiningClasses and verify there are not any duplicate indexes used.
*/
@Test
public void testVerifyStringIdsAreUnique() {
final Set<Integer> allStringIds = new HashSet<Integer>(3000);
// Save all duplicate ids and report them at the end
final Set<StringId> duplicates = new HashSet<StringId>();
for (StringId instance : getAllStringIds()) {
boolean isUnique = allStringIds.add(((StringId) instance).id);
// Duplicate ids between 0-1023 are allowed since they are duplicated
// between String bundles to minimize compiler dependencies.
if ((!isUnique) && ((StringId) instance).id >= 1024) {
boolean status = duplicates.add(instance);
assertTrue("Failed to add " + instance + "to the list of"
+ " duplicates because of duplicate duplicates", status);
}
}
if (!duplicates.isEmpty()) {
StringBuilder err = new StringBuilder();
err.append("The following duplicate StringIds were found:");
for (StringId i : duplicates) {
err.append("\n").append(((StringId) i).id).append(" : ").append(i.getRawText());
}
fail(err.toString() + "\nSearched in " + getStringIdDefiningClasses());
}
}
/**
* Verify the "Formated" form of the string matches the raw text form. This test is to catch
* issues like bug #40146 This tests the English version in the US locale.
*/
@Test
public void testVerifyStringsAreProperlyEscaped_US() {
verifyStringsAreProperlyEscaped(Locale.US);
}
/**
* Verify the "Formated" form of the string matches the raw text form. This test is to catch
* issues like bug #40146 This tests the English version in the UK locale.
*/
@Test
public void testVerifyStringsAreProperlyEscaped_UK() {
verifyStringsAreProperlyEscaped(Locale.UK);
}
/**
* Verify the "Formated" form of the string matches the raw text form. This test is to catch
* issues like bug #40146 This tests the English version in the JAPAN locale.
*/
@Test
public void testVerifyStringsAreProperlyEscaped_JAPAN() {
verifyStringsAreProperlyEscaped(Locale.JAPAN);
}
/**
* Get all of the StringId instances via reflection.
*
* @return a set of all StringId declarations within the product
*/
private Set<StringId> getAllStringIds() {
final Set<StringId> allStringIds = new HashSet<StringId>();
for (String className : getStringIdDefiningClasses()) {
try {
Class<?> c = Class.forName(className);
Field[] fields = c.getDeclaredFields();
final String msg = "Found no StringIds in " + className;
assertTrue(msg, fields.length > 0);
for (Field f : fields) {
f.setAccessible(true);
StringId instance = (StringId) f.get(null);
allStringIds.add(instance);
}
} catch (ClassNotFoundException cnfe) {
throw new AssertionError(cnfe.toString(), cnfe);
} catch (Exception e) {
String exMsg =
"Reflection attempt failed while attempting to find all" + " StringId instances. ";
throw new AssertionError(exMsg + e.toString(), e);
}
}
return allStringIds;
}
/**
* Lists all of the classes that define StringId instances
*
* @return a set of all classes that contain StringId declarations
*/
private Set<String> getStringIdDefiningClasses() {
final Set<String> StringIdDefiningClasses = new LinkedHashSet<String>();
final String pkg = "org.apache.geode.internal.i18n.";
// StringIdDefiningClasses.add(pkg + "ParentLocalizedStrings");
StringIdDefiningClasses.add(pkg + "LocalizedStrings");
StringIdDefiningClasses.add("org.apache.geode.management.internal.ManagementStrings");
return StringIdDefiningClasses;
}
/**
* Helper to access a private method via reflection, thus allowing us to not expose them to
* customers.
*/
private Locale getCurrentLocale() {
Class<?> c = StringId.class;
Locale locale = null;
try {
Method m = c.getDeclaredMethod("getCurrentLocale");
m.setAccessible(true);
locale = (Locale) m.invoke(null);
} catch (Exception e) {
String msg = "Reflection attempt failed for StringId.getCurrentLocale ";
throw new AssertionError(msg + e.toString(), e);
}
return locale;
}
/**
* Helper to access a private method via reflection, thus allowing us to not expose them to
* customers.
*/
private AbstractStringIdResourceBundle getActiveResourceBundle() {
Class<?> c = StringId.class;
AbstractStringIdResourceBundle rb = null;
try {
Method m = c.getDeclaredMethod("getActiveResourceBundle");
m.setAccessible(true);
rb = (AbstractStringIdResourceBundle) m.invoke(null);
} catch (Exception e) {
String msg = "Reflection attempt failed for StringId.getActiveResourceBundle ";
throw new AssertionError(msg + e.toString(), e);
}
return rb;
}
/**
* Check that the "raw" string matches the "formatted" string after taking into account known
* changes due to the fomatting process. For example: <code>
* "I''m using a contraction." should become "I'm using a contraction."
* </code>
*/
private void verifyStringsAreProperlyEscaped(Locale loc) {
StringId.setLocale(loc);
final Set<StringId> misquoted = new HashSet<StringId>();
final Object[] identityArgs = new Object[100];
for (int index = 0; index < identityArgs.length; index++) {
identityArgs[index] = index;
}
final AbstractStringIdResourceBundle rb = getActiveResourceBundle();
for (StringId instance : getAllStringIds()) {
String raw = rb.getString(instance);
String altered = raw.replaceAll("''", "'");
altered = altered.replaceAll("\\{([0-9]+)[^\\}]*\\}", "$1");
if (!rb.usingRawMode()) {
altered = "msgID " + ((StringId) instance).id + ": " + altered;
}
String formatted = null;
try {
formatted = instance.toLocalizedString(identityArgs);
} catch (IllegalArgumentException iae) {
String testName = this.getClass().getName().replaceAll("\\.", "/") + ".class";
String exMsg = "Improper message id=" + ((StringId) instance).id + "\n"
+ "Usually this is caused by an unmatched or nested \"{\"\n"
+ "Examples:\t\"{0]\" or \"{ {0} }\"\n"
+ "This is just the first failure, it is in your interest"
+ " to rebuild and run just this one test.\n"
+ "build.sh run-java-tests -Djunit.testcase=" + testName;
throw new AssertionError(exMsg, iae);
}
if (!altered.equals(formatted)) {
System.err.println("altered: " + altered);
System.err.println("formatted: " + formatted);
misquoted.add(instance);
}
}
if (!misquoted.isEmpty()) {
StringBuffer err = new StringBuffer();
err.append("These errors are usually resolved by replacing ");
err.append("\"'\" with \"''\".\n");
err.append("If the error is in the non-english version then ");
err.append("alter the text in StringIdResouceBundle_{lang}.txt.\n");
err.append("The following misquoted StringIds were found:");
for (StringId i : misquoted) {
err.append("\n").append("StringId id=").append(((StringId) i).id).append(" : text=\"")
.append(i.getRawText()).append("\"");
}
fail(err.toString());
}
}
}