1 import java.io.BufferedReader;
3 import java.io.FileReader;
4 import java.io.IOException;
5 import java.util.HashSet;
6 import java.util.Properties;
7 import java.util.TreeSet;
10 * This class scans Java source files for calls to MessageManager and reports
12 * <li>calls using keys not found in Messages.properties</li>
13 * <li>any unused keys in Messages.properties</li>
15 * It does not handle dynamically constructed keys, these are reported as
16 * possible errors for manual inspection. <br>
17 * For comparing translated bundles with Messages.properties, see i18nAnt.xml
22 public class MessageBundleChecker
25 * number of text lines to read at a time in order to parse
26 * code that is split over several lines
28 static int bufferSize = 3;
30 static final String METHOD1 = "MessageManager.getString(";
32 static final String METHOD2 = "MessageManager.getStringOrReturn(";
34 static final String METHOD3 = "MessageManager.formatMessage(";
36 static final String[] METHODS = { METHOD1, METHOD2, METHOD3 };
39 * root of the Java source folders we want to scan
44 * contents of Messages.properties
46 private Properties messages;
49 * keys from Messages.properties
50 * we remove entries from here as they are found to be used
51 * any left over are unused entries
53 private TreeSet<String> messageKeys;
55 private int javaCount;
57 private HashSet<String> invalidKeys;
60 * Runs the scan given the path to the root of Java source directories
63 * [0] path to the source folder to scan
65 * [1] (optional) read buffer size (default is 3); increasing this
66 * may detect more results but will give higher error counts due to
67 * double counting of the same code
70 public static void main(String[] args) throws IOException
72 if (args.length != 1 && args.length != 2)
74 System.out.println("Usage: <pathToSourceFolder> [readBufferSize]");
79 bufferSize = Integer.valueOf(args[1]);
81 new MessageBundleChecker().doMain(args[0]);
85 * Main method to perform the work
90 private void doMain(String srcPath) throws IOException
92 System.out.println("Scanning " + srcPath
93 + " for calls to MessageManager");
96 File dir = new File(srcPath);
99 System.out.println(srcPath + " not found");
102 invalidKeys = new HashSet<String>();
103 if (dir.isDirectory())
115 * Prints out counts to sysout
117 private void reportResults()
119 System.out.println("\nScanned " + javaCount + " source files");
120 System.out.println("Message.properties has " + messages.size()
122 System.out.println("Found " + invalidKeys.size()
123 + " possibly invalid parameter calls");
125 System.out.println(messageKeys.size()
126 + " keys not found, possibly unused");
127 for (String key : messageKeys)
129 System.out.println(" " + key);
134 * Scan all files within a directory
137 * @throws IOException
139 private void scanDirectory(File dir) throws IOException
141 File[] files = dir.listFiles();
163 private void scanFile(File f) throws IOException
165 String path = f.getPath();
166 if (!path.endsWith(".java"))
172 String[] lines = new String[bufferSize];
173 BufferedReader br = new BufferedReader(new FileReader(f));
174 for (int i = 0; i < bufferSize; i++)
176 String readLine = br.readLine();
177 lines[i] = stripCommentsAndTrim(readLine);
182 while (lines[bufferSize - 1] != null)
185 inspectSourceLines(path, lineNo, lines);
187 for (int i = 0; i < bufferSize - 1; i++)
189 lines[i] = lines[i + 1];
191 lines[bufferSize - 1] = stripCommentsAndTrim(br.readLine());
198 * removes anything after (and including) '//'
200 private String stripCommentsAndTrim(String line)
204 int pos = line.indexOf("//");
207 line = line.substring(0, pos);
209 line = line.replace("\t", " ").trim();
215 * Look for calls to MessageManager methods, possibly split over two or more
222 private void inspectSourceLines(String path, int lineNo, String[] lines)
224 String lineNos = String.format("%d-%d", lineNo, lineNo + lines.length
226 String combined = combineLines(lines);
227 for (String method : METHODS)
229 int pos = combined.indexOf(method);
234 String methodArgs = combined.substring(pos + method.length());
235 if ("".equals(methodArgs))
238 * continues on next line - catch in the next read loop iteration
242 if (!methodArgs.startsWith("\""))
244 System.out.println(String.format(
245 "Possible dynamic key at %s line %s %s",
246 path.substring(sourcePath.length()), lineNos, combined));
249 methodArgs = methodArgs.substring(1);
250 int quotePos = methodArgs.indexOf("\"");
253 System.out.println(String.format("Trouble parsing %s line %s %s",
254 path.substring(sourcePath.length()), lineNos, combined));
257 String messageKey = methodArgs.substring(0, quotePos);
258 if (!this.messages.containsKey(messageKey))
260 System.out.println(String.format(
261 "Unmatched key '%s' at line %s of %s", messageKey, lineNos,
262 path.substring(sourcePath.length())));
263 if (!invalidKeys.contains(messageKey))
265 invalidKeys.add(messageKey);
268 messageKeys.remove(messageKey);
272 private String combineLines(String[] lines)
274 String combined = "";
277 for (String line : lines)
289 * Loads properties from Message.properties
291 * @throws IOException
293 void loadMessages() throws IOException
295 messages = new Properties();
296 FileReader reader = new FileReader(new File(sourcePath,
297 "../resources/lang/Messages.properties"));
298 messages.load(reader);
301 messageKeys = new TreeSet<String>();
302 for (Object key : messages.keySet())
304 messageKeys.add((String) key);