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 implements BufferedLineReader.LineCleaner
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 BufferedReader br = new BufferedReader(new FileReader(f));
216 BufferedLineReader blr = new BufferedLineReader(br, bufferSize, this);
219 String line = blr.read();
223 inspectSourceLines(path, lineNo, line);
231 * Look for calls to MessageManager methods, possibly split over two or more
232 * lines that have been concatenated while parsing the file
238 private void inspectSourceLines(String path, int lineNo, String line)
240 String lineNos = String
241 .format("%d-%d", lineNo, lineNo + bufferSize
243 for (String method : METHODS)
245 int pos = line.indexOf(method);
252 * extract what follows the opening bracket of the method call
254 String methodArgs = line.substring(pos + method.length()).trim();
255 if ("".equals(methodArgs))
258 * arguments are on next line - catch in the next read loop iteration
262 if (methodArgs.indexOf(",") == -1 && methodArgs.indexOf(")") == -1)
265 * arguments continue on next line - catch in the next read loop iteration
270 if (JVINIT == method && methodArgs.indexOf(",") == -1)
273 * not interested in 1-arg calls to jvInitComponent
278 if (METHOD3 == method)
280 System.out.println(String.format("Dynamic key at %s line %s %s",
281 path.substring(sourcePath.length()), lineNos, line));
285 String messageKey = getMessageKey(method, methodArgs);
286 if (messageKey == null)
288 System.out.println(String.format("Trouble parsing %s line %s %s",
289 path.substring(sourcePath.length()), lineNos, line));
293 if (!(STRING_PATTERN.matcher(messageKey).matches()))
295 System.out.println(String.format("Dynamic key at %s line %s %s",
296 path.substring(sourcePath.length()), lineNos, line));
301 * strip leading and trailing quote
303 messageKey = messageKey.substring(1, messageKey.length() - 1);
305 if (!this.messages.containsKey(messageKey))
307 System.out.println(String.format(
308 "Unmatched key '%s' at line %s of %s", messageKey, lineNos,
309 path.substring(sourcePath.length())));
310 if (!invalidKeys.contains(messageKey))
312 invalidKeys.add(messageKey);
315 messageKeys.remove(messageKey);
320 * Helper method to parse out the resource bundle key parameter of a method
325 * the rest of the source line starting with arguments to method
328 private String getMessageKey(String method, String methodArgs)
330 String key = methodArgs;
333 * locate second argument if calling jvInitComponent()
335 if (method == JVINIT)
337 int commaLoc = methodArgs.indexOf(",");
342 key = key.substring(commaLoc + 1).trim();
346 * take up to next comma or ) or end of line
348 int commaPos = key.indexOf(",");
349 int bracePos = key.indexOf(")");
350 int endPos = commaPos == -1 ? bracePos : (bracePos == -1 ? commaPos
351 : Math.min(commaPos, bracePos));
352 if (endPos == -1 && key.length() > 1 && key.endsWith("\""))
354 endPos = key.length();
357 return endPos == -1 ? null : key.substring(0, endPos);
361 * Loads properties from Message.properties
363 * @throws IOException
365 void loadMessages() throws IOException
367 messages = new Properties();
368 FileReader reader = new FileReader(new File(sourcePath,
369 "../resources/lang/Messages.properties"));
370 messages.load(reader);
373 messageKeys = new TreeSet<String>();
374 for (Object key : messages.keySet())
376 messageKeys.add((String) key);
382 * Remove any trailing comments, change tabs to space, and trim
385 public String cleanLine(String l)
389 int pos = l.indexOf("//");
392 l = l.substring(0, pos);
394 l = l.replace("\t", " ").trim();