package fi.utu.ville.exercises.testutils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; import org.apache.commons.io.IOUtils; import fi.utu.ville.exercises.stub.TranslationFileParser; /** * Contains a static method that can be used for testing a UIConstant-file - .trl-file pair implemented for localization in a classic ViLLE-way. * * @author Riku Haavisto * */ public final class VilleTranslationsFileTester { private static final String translationFileSuffix = ".trl"; private static final Logger logger = Logger .getLogger(VilleTranslationsFileTester.class.getName()); private VilleTranslationsFileTester() { } public static boolean testLangFiles(Class<?> uiConstantsClass, File trlFile, List<String> languagesToTest) throws IOException { HashMap<String, String> uiconstUsedValueToName = new HashMap<String, String>(); HashMap<String, Boolean> loadedKeysUsed = new HashMap<String, Boolean>(); boolean res = true; Set<String> keysThatHadParametersInValues = new HashSet<String>(); String trlFileName = trlFile.getName(); String prefix = ""; if (!trlFileName.endsWith(translationFileSuffix)) { logger.severe("FAIL: Translation files must have extension .trl"); return false; } else { prefix = trlFileName.substring(0, trlFileName.length() - translationFileSuffix.length()) .toUpperCase(); } List<String> trlFileLines = null; if (!trlFile.exists()) { logger.severe("FAIL: Translation file does not exist!"); return false; } else { FileInputStream inStream = null; try { inStream = new FileInputStream(trlFile); trlFileLines = IOUtils.readLines(inStream, "UTF-8"); } finally { IOUtils.closeQuietly(inStream); } } Map<String, Map<String, String>> translationsByLang = TranslationFileParser.parseTranslationFile(prefix, trlFileLines); for (Map<String, String> aDict : translationsByLang.values()) { for (String aKey : aDict.keySet()) { loadedKeysUsed.put(aKey, Boolean.FALSE); } } for (Field uiConstField : uiConstantsClass.getDeclaredFields()) { // interested in public static String fields if ((Modifier.isStatic(uiConstField.getModifiers()) && Modifier.isPublic(uiConstField.getModifiers()) && uiConstField .getType().equals(String.class))) { try { // should be static and final if (!Modifier.isFinal(uiConstField.getModifiers())) { logger.severe("FAIL: Constant field not declared final: " + uiConstField.getName() + "(=" + ((String) uiConstField.get(null)) + ")"); res = false; } String uiConstValue = (String) uiConstField.get(null); String alreadyUsedByField = null; if (null != (alreadyUsedByField = uiconstUsedValueToName .put(uiConstValue, uiConstField.getName()))) { logger.severe("----------\nFAIL: " + "the same UIConstant-value (" + uiConstValue + ") is declared in two" + " fields:\n" + uiConstField.getName() + " and " + alreadyUsedByField); res = false; } loadedKeysUsed.put(uiConstValue, Boolean.TRUE); // should have a translation in all required languages for (String lang : languagesToTest) { // the key has no translation for this language if (!translationsByLang.containsKey(lang) || !translationsByLang.get(lang).containsKey(uiConstValue)) { logger.severe("----------\nFAIL: " + uiConstValue + " not found for lang " + lang); res = false; } else { logger.fine("Matched: " + uiConstValue + " = \"" + translationsByLang.get(lang).get( uiConstValue) + " \" in lang " + lang); if (translationsByLang.get(lang).get(uiConstValue) .contains("@")) { keysThatHadParametersInValues.add(uiConstValue); } } } } catch (IllegalArgumentException e) { // Should not happen e.printStackTrace(); } catch (IllegalAccessException e) { // Should not happen e.printStackTrace(); } } } for (Entry<String, Boolean> keyUsed : loadedKeysUsed.entrySet()) { if (!keyUsed.getValue()) { logger.warning("----------\nWARNING: a translation-key " + keyUsed.getKey() + " contained in a language file identified by the prefix was " + "not used by any of the tested constant-files"); } } testParametrized(keysThatHadParametersInValues, translationsByLang); return res; } private static void testParametrized(Set<String> keysForParametrized, Map<String, Map<String, String>> translationsByLang) { for (String keyForParametrized : keysForParametrized) { Set<Integer> lastParamSet = null; String lastLang = ""; for (Entry<String, Map<String, String>> aDict : translationsByLang .entrySet()) { Set<Integer> currParamSet = getParamNumbers(aDict.getValue() .get(keyForParametrized)); if (lastParamSet != null) { if (!lastParamSet.equals(currParamSet)) { logger.warning("----------\nWARNING: " + "ui-constant-key: " + keyForParametrized + " contained different number of parameters for lang '" + lastLang + "' than for lang '" + aDict.getKey() + "'"); } } if (currParamSet.isEmpty()) { logger.warning("----------\nWARNING: " + "ui-constant-key: " + keyForParametrized + " had value for lang " + aDict.getKey() + " contained no substitutable parameters"); } else { Integer max = Collections.max(currParamSet); for (int i = 0; i < max; i++) { if (!currParamSet.contains(i)) { logger.warning("----------\nWARNING: " + "ui-constant-key: " + keyForParametrized + " had value for lang '" + aDict.getKey() + "' that contained a parameter-index (max) " + max + " but did not contain parameter-index " + i); } } } lastParamSet = currParamSet; lastLang = aDict.getKey(); } } } private static Set<Integer> getParamNumbers(String parametrized) { if (parametrized == null) { return new HashSet<Integer>(); } int index = parametrized.indexOf('@', 0); Set<Integer> res = new HashSet<Integer>(); while (index >= 0) { if (!(parametrized.charAt(index + 1) == '{')) { try { String indexNumb = ""; for (int i = 1; index + i < parametrized.length() && Character .isDigit(parametrized.charAt(index + i)); i++) { indexNumb += parametrized.charAt(index + i); } int paramNum = Integer.parseInt(indexNumb); res.add(paramNum); } catch (NumberFormatException e) { // should happen only if '@' is followed by zero digit // characters logger.warning("----------\nWARNING: " + "Ui-translation " + parametrized + " contained @ without following index-digit; " + "this @ will be displayed as such and cannot be substituted with parameter"); } } else { // add some tests for parameters with @{name} syntax } index = parametrized.indexOf('@', index + 1); } return res; } }