2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 import java.io.BufferedReader;
23 import java.io.FileReader;
24 import java.io.IOException;
25 import java.util.HashSet;
26 import java.util.Properties;
27 import java.util.TreeSet;
28 import java.util.regex.Pattern;
31 * This class scans Java source files for calls to MessageManager and reports
33 * <li>calls using keys not found in Messages.properties</li>
34 * <li>any unused keys in Messages.properties</li>
36 * It does not handle dynamically constructed keys, these are reported as
37 * possible errors for manual inspection. <br>
38 * For comparing translated bundles with Messages.properties, see i18nAnt.xml
43 public class MessageBundleChecker
47 * opening quote, closing quote, no quotes in between
49 static Pattern STRING_PATTERN = Pattern.compile("^\"[^\"]*\"$");
52 * number of text lines to read at a time in order to parse
53 * code that is split over several lines
55 static int bufferSize = 3;
58 * resource bundle key is arg0 for these methods
60 static final String METHOD1 = "MessageManager.getString(";
62 static final String METHOD2 = "MessageManager.formatMessage(";
64 static final String METHOD3 = "MessageManager.getStringOrReturn(";
67 * resource bundle key is arg1 for this method
69 static final String JVINIT = "JvSwingUtils.jvInitComponent(";
71 static final String[] METHODS = { METHOD1, METHOD2, METHOD3, JVINIT };
74 * root of the Java source folders we want to scan
79 * contents of Messages.properties
81 private Properties messages;
84 * keys from Messages.properties
85 * we remove entries from here as they are found to be used
86 * any left over are unused entries
88 private TreeSet<String> messageKeys;
90 private int javaCount;
92 private HashSet<String> invalidKeys;
95 * Runs the scan given the path to the root of Java source directories
98 * [0] path to the source folder to scan
100 * [1] (optional) read buffer size (default is 3); increasing this
101 * may detect more results but will give higher error counts due to
102 * double counting of the same code
103 * @throws IOException
105 public static void main(String[] args) throws IOException
107 if (args.length != 1 && args.length != 2)
109 System.out.println("Usage: <pathToSourceFolder> [readBufferSize]");
112 if (args.length == 2)
114 bufferSize = Integer.valueOf(args[1]);
116 new MessageBundleChecker().doMain(args[0]);
120 * Main method to perform the work
123 * @throws IOException
125 private void doMain(String srcPath) throws IOException
127 System.out.println("Scanning " + srcPath
128 + " for calls to MessageManager");
129 sourcePath = srcPath;
131 File dir = new File(srcPath);
134 System.out.println(srcPath + " not found");
137 invalidKeys = new HashSet<String>();
138 if (dir.isDirectory())
150 * Prints out counts to sysout
152 private void reportResults()
154 System.out.println("\nScanned " + javaCount + " source files");
155 System.out.println("Message.properties has " + messages.size()
157 System.out.println("Found " + invalidKeys.size()
158 + " possibly invalid parameter calls");
160 System.out.println(messageKeys.size()
161 + " keys not found, either unused or constructed dynamically");
162 for (String key : messageKeys)
164 System.out.println(" " + key);
169 * Scan all files within a directory
172 * @throws IOException
174 private void scanDirectory(File dir) throws IOException
176 File[] files = dir.listFiles();
198 private void scanFile(File f) throws IOException
200 String path = f.getPath();
201 if (!path.endsWith(".java"))
208 * skip class with designed dynamic lookup call
210 if (path.endsWith("gui/JvSwingUtils.java"))
215 String[] lines = new String[bufferSize];
216 BufferedReader br = new BufferedReader(new FileReader(f));
217 for (int i = 0; i < bufferSize; i++)
219 String readLine = br.readLine();
220 lines[i] = stripCommentsAndTrim(readLine);
225 while (lines[bufferSize - 1] != null)
228 inspectSourceLines(path, lineNo, lines);
230 for (int i = 0; i < bufferSize - 1; i++)
232 lines[i] = lines[i + 1];
234 lines[bufferSize - 1] = stripCommentsAndTrim(br.readLine());
241 * removes anything after (and including) '//'
243 private String stripCommentsAndTrim(String line)
247 int pos = line.indexOf("//");
250 line = line.substring(0, pos);
252 line = line.replace("\t", " ").trim();
258 * Look for calls to MessageManager methods, possibly split over two or more
265 private void inspectSourceLines(String path, int lineNo, String[] lines)
267 String lineNos = String.format("%d-%d", lineNo, lineNo + lines.length
269 String combined = combineLines(lines);
270 for (String method : METHODS)
272 int pos = combined.indexOf(method);
279 * extract what follows the opening bracket of the method call
281 String methodArgs = combined.substring(pos + method.length()).trim();
282 if ("".equals(methodArgs))
285 * arguments are on next line - catch in the next read loop iteration
289 if (methodArgs.indexOf(",") == -1 && methodArgs.indexOf(")") == -1)
292 * arguments continue on next line - catch in the next read loop iteration
297 if (JVINIT == method && methodArgs.indexOf(",") == -1)
300 * not interested in 1-arg calls to jvInitComponent
305 if (METHOD3 == method)
307 System.out.println(String.format("Dynamic key at %s line %s %s",
308 path.substring(sourcePath.length()), lineNos, combined));
312 String messageKey = getMessageKey(method, methodArgs);
313 if (messageKey == null)
315 System.out.println(String.format("Trouble parsing %s line %s %s",
316 path.substring(sourcePath.length()), lineNos, combined));
320 if (!(STRING_PATTERN.matcher(messageKey).matches()))
322 System.out.println(String.format("Dynamic key at %s line %s %s",
323 path.substring(sourcePath.length()), lineNos, combined));
328 * strip leading and trailing quote
330 messageKey = messageKey.substring(1, messageKey.length() - 1);
332 if (!this.messages.containsKey(messageKey))
334 System.out.println(String.format(
335 "Unmatched key '%s' at line %s of %s", messageKey, lineNos,
336 path.substring(sourcePath.length())));
337 if (!invalidKeys.contains(messageKey))
339 invalidKeys.add(messageKey);
342 messageKeys.remove(messageKey);
347 * Helper method to parse out the resource bundle key parameter of a method
352 * the rest of the source line starting with arguments to method
355 private String getMessageKey(String method, String methodArgs)
357 String key = methodArgs;
360 * locate second argument if calling jvInitComponent()
362 if (method == JVINIT)
364 int commaLoc = methodArgs.indexOf(",");
369 key = key.substring(commaLoc + 1).trim();
373 * take up to next comma or ) or end of line
375 int commaPos = key.indexOf(",");
376 int bracePos = key.indexOf(")");
377 int endPos = commaPos == -1 ? bracePos : (bracePos == -1 ? commaPos
378 : Math.min(commaPos, bracePos));
379 if (endPos == -1 && key.length() > 1 && key.endsWith("\""))
381 endPos = key.length();
384 return endPos == -1 ? null : key.substring(0, endPos);
387 private String combineLines(String[] lines)
389 String combined = "";
392 for (String line : lines)
404 * Loads properties from Message.properties
406 * @throws IOException
408 void loadMessages() throws IOException
410 messages = new Properties();
411 FileReader reader = new FileReader(new File(sourcePath,
412 "../resources/lang/Messages.properties"));
413 messages.load(reader);
416 messageKeys = new TreeSet<String>();
417 for (Object key : messages.keySet())
419 messageKeys.add((String) key);