Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
a199cc528This file is pinned to the selected release. Download the original to open it in your design tool.
package com.marginallyclever.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import static com.marginallyclever.makelangelo.Translator.WORKING_DIRECTORY;
/**
* Helper utility class to aid in loading of language files.
* @author Peter Colapietro
* @since v7.1.4
*/
public final class MarginallyCleverTranslationXmlFileHelper {
private static final Logger logger = LoggerFactory.getLogger(MarginallyCleverTranslationXmlFileHelper.class);
/**
* NOOP Constructor
* @throws IllegalStateException
*/
private MarginallyCleverTranslationXmlFileHelper() throws IllegalStateException {
throw new IllegalStateException();
}
/**
* Languages folder location relative to the user's working directory.
*/
private static final String LANGUAGES_FOLDER_LOCATION = /*File.separator +*/ "languages";
/**
* The default language file.
*/
private static final String DEFAULT_LANGUAGE_XML_FILE = "english.xml";
@SuppressWarnings("unused")
private static final boolean LOG_MISSING_KEYS = true;
private static final boolean DO_NOT_LOG_MISSING_KEYS = false;
private static final boolean CHECK_ALL_LANGUAGE_FILES = true;
@SuppressWarnings("unused")
private static final boolean DO_NOT_CHECK_ALL_LANGUAGE_FILES = false;
/**
* Used when writing a set to disk.
* See #writeSetObjectToFile
*/
private static final String SET_OBJECT_FILE_NAME = "keys.txt";
/**
* @param args command line arguments.
* See <a href="http://stackoverflow.com/a/14026865">Comparing key and values of two java maps</a>
*/
public static void main(String[] args) {
areLanguageFilesMissingKeys(DO_NOT_LOG_MISSING_KEYS, CHECK_ALL_LANGUAGE_FILES);
}
/**
* Check to see if language translation keys are missing. It can check and log all language files, or fail fast
* on the first missing key.
* @param logMissingKeys log missing keys
* @param checkAllLanguageFiles check all files, if set to false the methods stops on the first missing key
* @return if any language translation keys are missing
*/
public static boolean areLanguageFilesMissingKeys(boolean logMissingKeys, boolean checkAllLanguageFiles) {
final URL languagesFolderUrl = getLanguagesFolderUrl();
if (languagesFolderUrl != null) {
try {
final File[] languageFiles = getLanguageFiles(languagesFolderUrl);
final File defaultLanguageFile = searchForDefaultLanguageInLanguagesFolder(languagesFolderUrl, languageFiles);
final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
final Set<String> defaultLanguageFilesKeys = getKeySet(docBuilder.parse(defaultLanguageFile).getDocumentElement());
Boolean doAllLanguageFilesContainAllTheDefaultKeys = null;
for (final File languageFile : languageFiles) {
final String languageFileName = languageFile.getName();
final boolean isDefaultLanguageFile = languageFileName.equals(DEFAULT_LANGUAGE_XML_FILE);
if (!isDefaultLanguageFile) {
logger.debug(languageFile.getAbsolutePath());
final Document parseXmlLanguageDocument = docBuilder.parse(languageFile);
final Set<String> thisLanguageFilesKeys = getKeySet(parseXmlLanguageDocument.getDocumentElement());
final boolean doesThisLanguageFileContainAllTheDefaultKeys = doesThisLanguageFileContainAllTheDefaultKeys(defaultLanguageFilesKeys, thisLanguageFilesKeys, languageFileName);
if (!doesThisLanguageFileContainAllTheDefaultKeys) {
if (logMissingKeys) {
logMissingKeys(defaultLanguageFilesKeys, thisLanguageFilesKeys);
}
doAllLanguageFilesContainAllTheDefaultKeys = false;
}
if (!checkAllLanguageFiles) {
break;
}
}
}
if (doAllLanguageFilesContainAllTheDefaultKeys != null) {
return true;
}
} catch (SAXException | IOException | URISyntaxException | ParserConfigurationException e) {
logger.error("A language key is missing", e);
}
}
return false;
}
/**
* Logs missing keys.
* @param expected set of expected keys
* @param actual set of actual keys
*/
private static void logMissingKeys(Set<String> expected, Set<String> actual) {
final Set<String> inANotB = getMissingKeys(expected, actual);
logger.error("Missing Keys: {}", inANotB.toString());
}
/**
* Get any missing keys in the actual set from the expected set.
* @param expected set of expected keys
* @param actual set of actual keys
* @return
* See <a href="http://stackoverflow.com/a/14026865">Comparing key and values of two java maps</a>
*/
private static Set<String> getMissingKeys(Set<String> expected, Set<String> actual) {
final Set<String> keysInA = new HashSet<String>(expected);
final Set<String> keysInB = new HashSet<String>(actual);
// Keys in A and not in B
final Set<String> inANotB = new HashSet<String>(keysInA);
inANotB.removeAll(keysInB);
// Keys common to both maps
final Set<String> commonKeys = new HashSet<String>(keysInA);
commonKeys.retainAll(keysInB);
return inANotB;
}
/**
* Write a set object to disk.
* @param set Set you want written to disk.
* @throws IOException
* See #SET_OBJECT_FILE_NAME
*/
@SuppressWarnings("unused")
private static void writeSetObjectToFile(Set<String> set) throws IOException {
try (final OutputStream fos = new FileOutputStream(SET_OBJECT_FILE_NAME);
final ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(set);
}
}
/**
* Search the languages folder for the default language file.
* @param languagesFolderUrl language folder.
* @param languageFiles list of all language files to check for existence of the default
* @return file object representing the default language file.
* See #DEFAULT_LANGUAGE_XML_FILE
* @throws AssertionError
*/
private static File searchForDefaultLanguageInLanguagesFolder(URL languagesFolderUrl, File[] languageFiles) throws AssertionError {
final String defaultLanguagePathName = buildDefaultLanguageFilePathName(languagesFolderUrl);
final int indexOfDefaultLanguageFile = Arrays.binarySearch(languageFiles, new File(defaultLanguagePathName));
if (indexOfDefaultLanguageFile < 0) {
throw new AssertionError();
}
return languageFiles[indexOfDefaultLanguageFile];
}
/**
* @return url object representing the languages folder
* See #getLanguagesFolderUrlRelativeToClasspath()
* See #getLanguagesFolderUrlFromUserDirectory()
*/
private static URL getLanguagesFolderUrl() {
URL languagesFolderUrl = getLanguagesFolderUrlRelativeToClasspath();
if( languagesFolderUrl!=null ) {
logger.debug("languages relative to classpath: {}", languagesFolderUrl.toString());
}
URL languageFolderUsingUserDirectory = getLanguagesFolderUrlFromUserDirectory();
if( languageFolderUsingUserDirectory!=null ) {
logger.debug("languages via user directory: {}", languageFolderUsingUserDirectory.toString());
}
if (languagesFolderUrl == null) {
languagesFolderUrl = languageFolderUsingUserDirectory;
}
return languagesFolderUrl;
}
/**
* @return url object representing the language folder in the user's working directory.
* See com.marginallyclever.makelangelo.Translator#WORKING_DIRECTORY
*/
private static URL getLanguagesFolderUrlFromUserDirectory() {
URL languageFolderUsingUserDirectoryUrl = null;
try {
File f = new File(WORKING_DIRECTORY);
languageFolderUsingUserDirectoryUrl = f.toURI().toURL();
} catch (MalformedURLException e) {
logger.error("url malformed {}", WORKING_DIRECTORY, e );
}
return languageFolderUsingUserDirectoryUrl;
}
/**
* @return url object representing languages folder relative to classpath.
* See #LANGUAGES_FOLDER_LOCATION
* See java.lang.ClassLoader#getResource(String)
*/
public static URL getLanguagesFolderUrlRelativeToClasspath() {
final ClassLoader thisClassesClassLoader = MarginallyCleverTranslationXmlFileHelper.class.getClassLoader();
return thisClassesClassLoader.getResource(LANGUAGES_FOLDER_LOCATION);
}
/**
* recursively logs the names and values of a given xml node
* @param node xml node to recursively log
* See <a href="http://stackoverflow.com/a/5511298">Java: Most efficient method to iterate over all elements in a org.w3c.dom.Document?</a>
* See #logAllNodesNamesAndValues(org.w3c.dom.Node)
*/
@SuppressWarnings("unused")
private static void logAllNodesNamesAndValues(Node node) {
logNodeNameAndValue(node);
final NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
final Node currentNode = nodeList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
//calls this method for all the children which is Element
logAllNodesNamesAndValues(currentNode);
}
}
}
/**
* logs the name and value of a given xml node
* @param node xml node to log
*/
private static void logNodeNameAndValue(Node node) {
final String nodeName = node.getNodeName();
if (nodeName.equals("key")) {
logger.debug("node name: {}, node value: {}", nodeName, node.getTextContent());
}
}
/**
* gets a key set from a given xml node
* @param node node to get all elements with the name {@code "key"}
* See <a href="http://stackoverflow.com/a/5511298">Java: Most efficient method to iterate over all elements in a org.w3c.dom.Document?</a>
*/
private static Set<String> getKeySet(Node node) {
final Set<String> keySet = new HashSet<String>();
if (node.getNodeName().equals("key")) {
keySet.add(node.getTextContent());
}
final NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
final Node currentNode = nodeList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
// calls this method for all the children which is Element
keySet.addAll(getKeySet(currentNode));
}
}
return keySet;
}
/**
* @param languagesFolderUrl - url object representing the language files folder
* @return an array of file objects in the language folder.
* @throws URISyntaxException if the url object parameter is not correct
*/
private static File[] getLanguageFiles(URL languagesFolderUrl) throws URISyntaxException {
final File languagesFolder = new File(languagesFolderUrl.toURI());
return languagesFolder.listFiles();
}
/**
* @param defaultLanguageFilesKeys default language file's keys
* @param thisLanguageFilesKeys this language files keys
* @param thisLanguageFilesName this language files name
* @return does this language file contain all the default keys
*/
private static boolean doesThisLanguageFileContainAllTheDefaultKeys(Set<String> defaultLanguageFilesKeys, Set<String> thisLanguageFilesKeys, String thisLanguageFilesName) {
final boolean doesThisLanguageFileContainAllTheDefaultKeys = thisLanguageFilesKeys.containsAll(defaultLanguageFilesKeys);
if (!doesThisLanguageFileContainAllTheDefaultKeys) {
logger.error("{} does not contain all the default translation keys.", thisLanguageFilesName);
for (String s : defaultLanguageFilesKeys) {
if (!thisLanguageFilesKeys.contains(s)) {
logger.error("missing {}", s);
}
}
} else {
logger.debug("{} contains all the default translation keys.", thisLanguageFilesName);
}
return doesThisLanguageFileContainAllTheDefaultKeys;
}
@SuppressWarnings("unused")
private static File getDefaultLanguageFileFromClasspath() {
return getDefaultLanguageFile(getLanguagesFolderUrlRelativeToClasspath());
}
public static String getDefaultLanguageFilePathNameFromClassPath() {
return buildDefaultLanguageFilePathName(getLanguagesFolderUrlRelativeToClasspath());
}
private static File getDefaultLanguageFile(URL languagesFolderUrl) {
return new File(buildDefaultLanguageFilePathName(languagesFolderUrl));
}
public static String buildDefaultLanguageFilePathName(URL languagesFolderUrl) {
return languagesFolderUrl.getPath() + '/' + DEFAULT_LANGUAGE_XML_FILE;
}
public static String getDefaultLanguageFilePath() {
return LANGUAGES_FOLDER_LOCATION + File.separator + DEFAULT_LANGUAGE_XML_FILE;
}
}