X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=utils%2FMessageBundleChecker.java;h=b1927e11d6c0c6264b6c30c942dc7cdae0a903f1;hb=99bfb1a08e5ceb3a3c990bb49dcd7433a0880558;hp=7850eb5b3bfd135d3a8a07b507494292dc6c687c;hpb=a748d9a23455e0eefc62ed64cd550a1819864528;p=jalview.git diff --git a/utils/MessageBundleChecker.java b/utils/MessageBundleChecker.java index 7850eb5..b1927e1 100644 --- a/utils/MessageBundleChecker.java +++ b/utils/MessageBundleChecker.java @@ -1,10 +1,34 @@ + +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.HashSet; +import java.util.Iterator; import java.util.Properties; +import java.util.Set; import java.util.TreeSet; +import java.util.regex.Pattern; /** * This class scans Java source files for calls to MessageManager and reports @@ -19,21 +43,35 @@ import java.util.TreeSet; * @author gmcarstairs * */ -public class MessageBundleChecker +public class MessageBundleChecker implements BufferedLineReader.LineCleaner { /* + * regex ^"[^"]*"$ + * opening quote, closing quote, no quotes in between + */ + static Pattern STRING_PATTERN = Pattern.compile("^\"[^\"]*\"$"); + + /* * number of text lines to read at a time in order to parse * code that is split over several lines */ static int bufferSize = 3; + /* + * resource bundle key is arg0 for these methods + */ static final String METHOD1 = "MessageManager.getString("; - static final String METHOD2 = "MessageManager.getStringOrReturn("; + static final String METHOD2 = "MessageManager.formatMessage("; - static final String METHOD3 = "MessageManager.formatMessage("; + static final String METHOD3 = "MessageManager.getStringOrReturn("; - static final String[] METHODS = { METHOD1, METHOD2, METHOD3 }; + /* + * resource bundle key is arg1 for this method + */ + static final String JVINIT = "JvSwingUtils.jvInitComponent("; + + static final String[] METHODS = { METHOD1, METHOD2, METHOD3, JVINIT }; /* * root of the Java source folders we want to scan @@ -54,7 +92,9 @@ public class MessageBundleChecker private int javaCount; - private HashSet invalidKeys; + private Set invalidKeys; + + private Set dynamicKeys; /** * Runs the scan given the path to the root of Java source directories @@ -89,8 +129,9 @@ public class MessageBundleChecker */ private void doMain(String srcPath) throws IOException { - System.out.println("Scanning " + srcPath - + " for calls to MessageManager"); + System.out.println( + "Scanning " + srcPath + " for calls to MessageManager\n"); + sourcePath = srcPath; loadMessages(); File dir = new File(srcPath); @@ -99,7 +140,10 @@ public class MessageBundleChecker System.out.println(srcPath + " not found"); return; } - invalidKeys = new HashSet(); + + invalidKeys = new HashSet<>(); + dynamicKeys = new HashSet<>(); + if (dir.isDirectory()) { scanDirectory(dir); @@ -116,18 +160,62 @@ public class MessageBundleChecker */ private void reportResults() { - System.out.println("\nScanned " + javaCount + " source files"); - System.out.println("Message.properties has " + messages.size() - + " keys"); - System.out.println("Found " + invalidKeys.size() - + " possibly invalid parameter calls"); - - System.out.println(messageKeys.size() - + " keys not found, possibly unused"); + System.out.println( + "\nMessages.properties has " + messages.size() + " keys"); + System.out.println("Scanned " + javaCount + " source files\n"); + + if (!invalidKeys.isEmpty()) + { + System.out.println("Found " + invalidKeys.size() + + " possibly invalid unmatched key(s) in source code" + + (invalidKeys.size() > 1 ? "s" : "")); + } + + System.out.println( + "Keys not found in source code, assumed constructed dynamically:"); + int dynamicCount = 0; for (String key : messageKeys) { - System.out.println(" " + key); + if (isDynamic(key)) + { + System.out.println(" " + key); + dynamicCount++; + } + } + + if (dynamicCount < messageKeys.size()) + { + System.out.println((messageKeys.size() - dynamicCount) + + " key(s) not found, possibly unused, or used indirectly (check code manually!)"); + for (String key : messageKeys) + { + if (!isDynamic(key)) + { + System.out.println(" " + key); + } + } + } + System.out + .println("\nRun i18nAnt.xml to compare other message bundles"); + } + + /** + * Answers true if the key starts with one of the recorded dynamic key stubs, + * else false + * + * @param key + * @return + */ + private boolean isDynamic(String key) + { + for (String dynamic : dynamicKeys) + { + if (key.startsWith(dynamic)) + { + return true; + } } + return false; } /** @@ -169,120 +257,181 @@ public class MessageBundleChecker } javaCount++; - String[] lines = new String[bufferSize]; - BufferedReader br = new BufferedReader(new FileReader(f)); - for (int i = 0; i < bufferSize; i++) + /* + * skip class with designed dynamic lookup call + */ + if (path.endsWith("gui/JvSwingUtils.java")) { - String readLine = br.readLine(); - lines[i] = stripCommentsAndTrim(readLine); + return; } - int lineNo = 0; + BufferedReader br = new BufferedReader(new FileReader(f)); + BufferedLineReader blr = new BufferedLineReader(br, bufferSize, this); - while (lines[bufferSize - 1] != null) + int lineNo = 0; + String line = blr.read(); + while (line != null) { lineNo++; - inspectSourceLines(path, lineNo, lines); - - for (int i = 0; i < bufferSize - 1; i++) - { - lines[i] = lines[i + 1]; - } - lines[bufferSize - 1] = stripCommentsAndTrim(br.readLine()); + inspectSourceLines(path, lineNo, line); + line = blr.read(); } br.close(); } - /* - * removes anything after (and including) '//' - */ - private String stripCommentsAndTrim(String line) - { - if (line != null) - { - int pos = line.indexOf("//"); - if (pos != -1) - { - line = line.substring(0, pos); - } - line = line.replace("\t", " ").trim(); - } - return line; - } - /** * Look for calls to MessageManager methods, possibly split over two or more - * lines + * lines that have been concatenated while parsing the file * * @param path * @param lineNo - * @param lines + * @param line */ - private void inspectSourceLines(String path, int lineNo, String[] lines) + private void inspectSourceLines(String path, int lineNo, + final String line) { - String lineNos = String.format("%d-%d", lineNo, lineNo + lines.length - - 1); - String combined = combineLines(lines); + String lineNos = String.format("%d-%d", lineNo, + lineNo + bufferSize - 1); for (String method : METHODS) { - int pos = combined.indexOf(method); + int pos = line.indexOf(method); if (pos == -1) { continue; } - String methodArgs = combined.substring(pos + method.length()); + + /* + * extract what follows the opening bracket of the method call + */ + String methodArgs = line.substring(pos + method.length()).trim(); if ("".equals(methodArgs)) { /* - * continues on next line - catch in the next read loop iteration + * arguments are on next line - catch in the next read loop iteration */ continue; } - if (!methodArgs.startsWith("\"")) + if (methodArgs.indexOf(",") == -1 && methodArgs.indexOf(")") == -1) { - System.out.println(String.format( - "Possible dynamic key at %s line %s %s", - path.substring(sourcePath.length()), lineNos, combined)); + /* + * arguments continue on next line - catch in the next read loop iteration + */ continue; } - methodArgs = methodArgs.substring(1); - int quotePos = methodArgs.indexOf("\""); - if (quotePos == -1) + + if (JVINIT == method && methodArgs.indexOf(",") == -1) + { + /* + * not interested in 1-arg calls to jvInitComponent + */ + continue; + } + + String messageKey = getMessageKey(method, methodArgs); + + if (METHOD3 == method) + { + String key = messageKey.substring(1, messageKey.length() - 1); + if (!dynamicKeys.contains(key)) + { +// System.out.println(String.format( +// "Dynamic key \"" + key + "\" at %s line %s %s", +// path.substring(sourcePath.length()), lineNos, line)); + dynamicKeys.add(key); + } + continue; + } + + if (messageKey == null) { System.out.println(String.format("Trouble parsing %s line %s %s", - path.substring(sourcePath.length()), lineNos, combined)); + path.substring(sourcePath.length()), lineNos, line)); continue; } - String messageKey = methodArgs.substring(0, quotePos); + + if (!(STRING_PATTERN.matcher(messageKey).matches())) + { +// System.out.println(String.format("Dynamic key at %s line %s %s", +// path.substring(sourcePath.length()), lineNos, line)); + continue; + } + + /* + * strip leading and trailing quote + */ + messageKey = messageKey.substring(1, messageKey.length() - 1); + if (!this.messages.containsKey(messageKey)) { - System.out.println(String.format( - "Unmatched key '%s' at line %s of %s", messageKey, lineNos, - path.substring(sourcePath.length()))); if (!invalidKeys.contains(messageKey)) { + // report each key the first time found only + System.out.println(String.format( + "Unmatched key '%s' at line %s of %s", messageKey, + lineNos, path.substring(sourcePath.length()))); invalidKeys.add(messageKey); } } messageKeys.remove(messageKey); } - } - private String combineLines(String[] lines) - { - String combined = ""; - if (lines != null) + /* + * and a brute force scan for _any_ as yet unseen message key, to catch the + * cases where it is in a variable or a condition + */ + if (line.contains("\"")) { - for (String line : lines) + Iterator it = messageKeys.iterator(); + while (it.hasNext()) { - if (line != null) + if (line.contains(it.next())) { - combined += line; + it.remove(); // remove as 'seen' } } } - return combined; + } + + /** + * Helper method to parse out the resource bundle key parameter of a method + * call + * + * @param method + * @param methodArgs + * the rest of the source line starting with arguments to method + * @return + */ + private String getMessageKey(String method, String methodArgs) + { + String key = methodArgs; + + /* + * locate second argument if calling jvInitComponent() + */ + if (method == JVINIT) + { + int commaLoc = methodArgs.indexOf(","); + if (commaLoc == -1) + { + return null; + } + key = key.substring(commaLoc + 1).trim(); + } + + /* + * take up to next comma or ) or end of line + */ + int commaPos = key.indexOf(","); + int bracePos = key.indexOf(")"); + int endPos = commaPos == -1 ? bracePos + : (bracePos == -1 ? commaPos : Math.min(commaPos, bracePos)); + if (endPos == -1 && key.length() > 1 && key.endsWith("\"")) + { + endPos = key.length(); + } + + return endPos == -1 ? null : key.substring(0, endPos); } /** @@ -293,12 +442,12 @@ public class MessageBundleChecker void loadMessages() throws IOException { messages = new Properties(); - FileReader reader = new FileReader(new File(sourcePath, - "../resources/lang/Messages.properties")); + FileReader reader = new FileReader( + new File(sourcePath, "../resources/lang/Messages.properties")); messages.load(reader); reader.close(); - messageKeys = new TreeSet(); + messageKeys = new TreeSet<>(); for (Object key : messages.keySet()) { messageKeys.add((String) key); @@ -306,4 +455,22 @@ public class MessageBundleChecker } + /** + * Remove any trailing comments, change tabs to space, and trim + */ + @Override + public String cleanLine(String l) + { + if (l != null) + { + int pos = l.indexOf("//"); + if (pos != -1) + { + l = l.substring(0, pos); + } + l = l.replace("\t", " ").trim(); + } + return l; + } + }