package com.tyndalehouse.step.tools.international; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Checks language files all contain the right number of markers * * @author chrisburrell */ public class CheckLanguageFiles { public static final Map<String, Set<String>> MARKERS = new LinkedHashMap<String, Set<String>>(); private static final Logger LOGGER = LoggerFactory.getLogger(CheckLanguageFiles.class); private static final Map<String, Integer> ENTRIES = new LinkedHashMap<String, Integer>(1024); private static final Pattern VALID = Pattern.compile("%[sd]|%%|%\\d+\\$[sd]"); // an invalid marker is a % sign which is not followed by a digit, another % sign, or a d or an i private static final Pattern INVALID = Pattern.compile("(?<!%)%(?![%sd0-9])"); /** * Checks the language files are complete * * @param args */ public static void main(String[] args) throws IOException { readInput(ENTRIES, "/HtmlBundle.properties"); readInput(ENTRIES, "/InteractiveBundle.properties"); readInput(ENTRIES, "/ErrorBundle.properties"); readInput(ENTRIES, "/SetupBundle.properties"); final Collection<File> files = FileUtils.listFiles(new File( CheckLanguageFiles.class.getResource("/HtmlBundle.properties").getPath()).getParentFile(), new String[]{"properties"}, false ); for (File f : files) { if (f.getName().contains("step.core") || !f.getName().contains("_")) { continue; } Map<String, Integer> languageEntries = new LinkedHashMap<String, Integer>(1024); FileInputStream resourceStream = null; resourceStream = new FileInputStream(f); final String name = f.getName(); final String prefix = getPrefix(name); if ("/na/".equals(prefix)) { //skip continue; } final int beginIndex = name.indexOf('_'); String language = null; if (beginIndex != -1) { language = name.substring(beginIndex + 1, name.indexOf('.')); } final LinkedHashMap<String, Set<String>> markers = new LinkedHashMap<String, Set<String>>(); Properties p = getEntriesFromInputStream(markers, language, languageEntries, prefix, resourceStream); IOUtils.closeQuietly(resourceStream); check(name, languageEntries); validate(p, f, markers); } LOGGER.error("Remember to 'MAKE' the module again before running, or Intellij will appear not to have done anything."); } private static void validate(final Properties p, File file, final Map<String, Set<String>> markers) { boolean changed = false; List<String> extras = new ArrayList<String>(4); List<String> missing = new ArrayList<String>(4); for (Map.Entry<String, Set<String>> marker : markers.entrySet()) { final String propertyKey = marker.getKey().substring(2); final Set<String> englishPropertyMarkers = MARKERS.get(marker.getKey()); final Set<String> nonEnglishMarkers = marker.getValue(); //list of markers in non-english that shouldn't be there for (String nonEnglishMarker : nonEnglishMarkers) { if (!englishPropertyMarkers.contains(nonEnglishMarker)) { LOGGER.error("{}:{}:{} should not be present", file.getName(), marker.getKey(), nonEnglishMarker); extras.add(nonEnglishMarker); } } //list of markers in non-english that are missing for (String englishMarker : englishPropertyMarkers) { if (!nonEnglishMarkers.contains(englishMarker)) { LOGGER.error("{}:{} is missing.", marker.getKey(), englishMarker); missing.add(englishMarker); } } while (extras.size() > 0 && missing.size() > 0) { String extraMarker = extras.get(0); String missingMarker = missing.get(0); String property = p.getProperty(propertyKey); String newPropertyValue = property.replace(extraMarker, missingMarker); p.put(propertyKey, newPropertyValue); changed = true; extras.remove(0); missing.remove(0); } if (extras.size() > 0) { for (int ii = 0; ii < extras.size(); ii++) { String property = p.getProperty(propertyKey); String newPropertyValue = property.replace(extras.get(ii), ""); p.put(propertyKey, newPropertyValue); changed = true; } } if (missing.size() > 0) { for (int ii = 0; ii < missing.size(); ii++) { String property = p.getProperty(propertyKey); String newPropertyValue = property + " " + missing.get(ii); p.put(propertyKey, newPropertyValue); changed = true; } } //finally clean up any non-matching percent sign - we've done our best, now is the time to move on! final String property = p.getProperty(propertyKey); final Matcher invalidMatches = INVALID.matcher(property); if (invalidMatches.find()) { LOGGER.error("Was [{}]", property); final String cleansed = invalidMatches.replaceAll(""); p.setProperty(propertyKey, cleansed); LOGGER.error("Was [{}]", cleansed); changed = true; } extras = new ArrayList<String>(4); missing = new ArrayList<String>(4); } if (changed) { FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream("C:\\dev\\projects\\step\\step-core\\src\\main\\resources\\" + file.getName()); p.store(fileOutputStream, "Amended by STEP Language checker"); } catch (IOException e) { LOGGER.error(e.getMessage(), e); } finally { IOUtils.closeQuietly(fileOutputStream); } } } private static void check(final String fileName, final Map<String, Integer> languageEntries) { for (Map.Entry<String, Integer> entry : languageEntries.entrySet()) { final String key = entry.getKey(); Integer value = entry.getValue(); final Integer numOccurrences = ENTRIES.get(key); if (numOccurrences == null) { LOGGER.warn("{}:{} Extra key in file.", fileName, key.substring(2)); continue; // return; } if (value == null) { throw new RuntimeException("Value should never be null"); } if (!value.equals(numOccurrences)) { LOGGER.error("{}:{} original: {}, targetLang: {} ", fileName, key.substring(2), numOccurrences, value); } } } /** * Reads the default data and puts it into the map * * @param classpath */ private static void readInput(Map<String, Integer> entries, final String classpath) throws IOException { final InputStream resourceStream = CheckLanguageFiles.class.getResourceAsStream(classpath); getEntriesFromInputStream(MARKERS, "en", entries, getPrefix(classpath), resourceStream); } private static String getPrefix(final String filename) { if (filename.indexOf("HtmlBundle") != -1) { return "h_"; } if (filename.indexOf("InteractiveBundle") != -1) { return "i_"; } if (filename.indexOf("SetupBundle") != -1) { return "s_"; } if (filename.indexOf("ErrorBundle") != -1) { return "e_"; } return "/na/"; } private static Properties getEntriesFromInputStream(Map<String, Set<String>> markers, final String language, final Map<String, Integer> entries, final String prefix, final InputStream resourceStream) throws IOException { Properties p = new Properties(); p.load(resourceStream); for (Map.Entry<Object, Object> e : p.entrySet()) { entries.put(prefix + e.getKey(), count(markers, prefix + e.getKey(), language, (String) e.getValue())); } return p; } /** * Counts the number of percentage signs * * @param value the value we are trying to count * @return the number of percentage signs */ private static Integer count(final Map<String, Set<String>> markers, final String key, final String language, final String value) { int count = 0; for (int i = 0; i < value.length(); i++) { if (value.charAt(i) == '%') { count++; } } if (count > 0) { int remaining = count; Matcher matcher = VALID.matcher(value); final Set<String> markerSet = new LinkedHashSet<String>(); markers.put(key, markerSet); while (remaining > 0 && matcher.find()) { markerSet.add(matcher.group()); remaining--; } if (value.indexOf("%%") > 0) { remaining--; } if (remaining != 0) { LOGGER.error("{}: Found invalid marker in {}", language, value); } } return count; } }