1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.forgerock.i18n.maven;
18
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.InputStreamReader;
24 import java.io.LineNumberReader;
25 import java.io.OutputStreamWriter;
26 import java.util.ArrayList;
27 import java.util.Iterator;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Properties;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.Executors;
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.atomic.AtomicInteger;
37
38 import org.apache.maven.plugin.AbstractMojo;
39 import org.apache.maven.plugin.MojoExecutionException;
40 import org.apache.maven.plugins.annotations.Mojo;
41 import org.apache.maven.plugins.annotations.Parameter;
42
43
44
45
46 @Mojo(name="clean-messages", threadSafe=true)
47 @SuppressWarnings("resource")
48 public final class CleanMessagesMojo extends AbstractMojo {
49
50
51
52
53 private final class SourceFileTask implements Runnable {
54
55 private final File sourceFile;
56
57 private SourceFileTask(final File sourceFile) {
58 this.sourceFile = sourceFile;
59 }
60
61
62
63
64 public void run() {
65
66
67 final List<MessagePropertyKey> keys = new LinkedList<MessagePropertyKey>(
68 unreferencedProperties.keySet());
69
70 try {
71 final FileInputStream s = new FileInputStream(sourceFile);
72 try {
73 final LineNumberReader reader = new LineNumberReader(
74 new InputStreamReader(s, encoding));
75
76 String line;
77 while ((line = reader.readLine()) != null) {
78 final Iterator<MessagePropertyKey> i = keys.iterator();
79 while (i.hasNext()) {
80 final MessagePropertyKey key = i.next();
81 if (key.isPresent(line)) {
82 i.remove();
83 unreferencedProperties.remove(key);
84 referencedProperties.put(key, "");
85
86 }
87 }
88 }
89 } finally {
90 try {
91 s.close();
92 } catch (final Exception ignored) {
93
94 }
95 }
96
97 fileCount.incrementAndGet();
98 } catch (final IOException e) {
99 getLog().error(
100 "An error occurred while reading source file "
101 + sourceFile.getName(), e);
102 }
103 }
104
105 }
106
107
108
109
110 @Parameter(defaultValue="${project.build.sourceDirectory}", required=true)
111 private File sourceDirectory;
112
113
114
115
116 @Parameter(required=true)
117 private File messageFile;
118
119
120
121
122 @Parameter(defaultValue="${project.build.sourceEncoding}", required=true)
123 private String encoding;
124
125 private final Map<MessagePropertyKey, String> unreferencedProperties =
126 new ConcurrentHashMap<MessagePropertyKey, String>();
127 private final Map<MessagePropertyKey, String> referencedProperties =
128 new ConcurrentHashMap<MessagePropertyKey, String>();
129 private final AtomicInteger fileCount = new AtomicInteger();
130
131
132
133
134
135 public void execute() throws MojoExecutionException {
136 if (!sourceDirectory.exists()) {
137 throw new MojoExecutionException("Source directory "
138 + sourceDirectory.getPath() + " does not exist");
139 } else if (!sourceDirectory.isDirectory()) {
140 throw new MojoExecutionException("Source directory "
141 + sourceDirectory.getPath() + " is not a directory");
142 }
143
144 if (!messageFile.exists()) {
145 throw new MojoExecutionException("Message file "
146 + messageFile.getPath() + " does not exist");
147 } else if (!messageFile.isFile()) {
148 throw new MojoExecutionException("Message file "
149 + messageFile.getPath() + " is not a file");
150 }
151
152 final Properties properties = new Properties();
153
154 try {
155 final FileInputStream propertiesFile = new FileInputStream(
156 messageFile);
157 try {
158 properties.load(propertiesFile);
159 } finally {
160 try {
161 propertiesFile.close();
162 } catch (final Exception ignored) {
163
164 }
165 }
166 } catch (final IOException e) {
167 throw new MojoExecutionException(
168 "An IO error occurred while reading the message property file: "
169 + e);
170 }
171
172
173 for (final Map.Entry<Object, Object> property : properties.entrySet()) {
174 final String propKey = property.getKey().toString();
175 final MessagePropertyKey key = MessagePropertyKey.valueOf(propKey);
176 unreferencedProperties.put(key, property.getValue().toString());
177 }
178
179 final int messageCount = unreferencedProperties.size();
180
181
182 final ExecutorService executor = Executors.newFixedThreadPool(Runtime
183 .getRuntime().availableProcessors() * 2);
184
185
186 processSourceDirectory(executor, sourceDirectory);
187
188 executor.shutdown();
189 try {
190 executor.awaitTermination(1, TimeUnit.DAYS);
191 } catch (final InterruptedException e) {
192 Thread.currentThread().interrupt();
193 throw new MojoExecutionException(
194 "Interrupted while processing source files");
195 }
196
197 if ((unreferencedProperties.size() + referencedProperties.size()) != messageCount) {
198 throw new IllegalStateException("Message table sizes are invalid");
199 }
200
201 getLog().info("Processed " + fileCount.get() + " source files");
202 getLog().info(
203 "Found " + unreferencedProperties.size() + " / " + messageCount
204 + " unreferenced properties");
205
206
207
208
209 int cleanedMessageCount = 0;
210 int savedMessageCount = 0;
211
212 final List<String> lines = new ArrayList<String>(10000);
213 try {
214 final FileInputStream propertiesFile = new FileInputStream(
215 messageFile);
216 try {
217 final LineNumberReader reader = new LineNumberReader(
218 new InputStreamReader(propertiesFile, "ISO-8859-1"));
219
220 String line;
221 boolean inValue = false;
222 boolean lineNeedsRemoving = false;
223 boolean foundErrors = false;
224 while ((line = reader.readLine()) != null) {
225 if (!inValue) {
226
227
228 final String trimmedLine = line.trim();
229 if (trimmedLine.length() == 0) {
230 lines.add(line);
231 } else if (trimmedLine.startsWith("#")) {
232 lines.add(line);
233 } else {
234 final String key;
235 final int separator = trimmedLine.indexOf('=');
236 if (separator < 0) {
237 key = trimmedLine;
238 } else {
239 key = trimmedLine.substring(0, separator)
240 .trim();
241 }
242
243 MessagePropertyKey mpk;
244 try {
245 mpk = MessagePropertyKey.valueOf(key);
246 } catch (IllegalArgumentException e) {
247 getLog().error(
248 "Unable to decode line "
249 + reader.getLineNumber() + ": "
250 + line, e);
251 lines.add(line);
252 lineNeedsRemoving = false;
253 inValue = isContinuedOnNextLine(line);
254 foundErrors = true;
255 continue;
256 }
257
258 if (referencedProperties.containsKey(mpk)) {
259 savedMessageCount++;
260 lines.add(line);
261 lineNeedsRemoving = false;
262 } else {
263 if (!unreferencedProperties.containsKey(mpk)) {
264 throw new IllegalStateException(
265 "Unregistered message key");
266 }
267 cleanedMessageCount++;
268 lineNeedsRemoving = true;
269 }
270 inValue = isContinuedOnNextLine(line);
271 }
272 } else {
273
274 if (!lineNeedsRemoving) {
275 lines.add(line);
276 }
277
278 inValue = isContinuedOnNextLine(line);
279 }
280 }
281
282 if (foundErrors) {
283 throw new MojoExecutionException(
284 "Aborting because the message file could not be parsed");
285 }
286 } finally {
287 try {
288 propertiesFile.close();
289 } catch (final Exception ignored) {
290
291 }
292 }
293 } catch (final IOException e) {
294 throw new MojoExecutionException(
295 "An IO error occurred while reading the message property file: "
296 + e);
297 }
298
299
300 if (cleanedMessageCount == 0) {
301
302 getLog().info(
303 "Message file " + messageFile.getName()
304 + " unchanged: no messages were cleaned");
305 } else {
306 try {
307 final FileOutputStream propertiesFile = new FileOutputStream(
308 messageFile);
309 try {
310 final OutputStreamWriter writer = new OutputStreamWriter(
311 propertiesFile, "ISO-8859-1");
312 final String eol = System.getProperty("line.separator");
313 for (final String line : lines) {
314 writer.write(line);
315 writer.write(eol);
316 }
317
318 writer.close();
319 } finally {
320 try {
321 propertiesFile.close();
322 } catch (final Exception ignored) {
323
324 }
325 }
326 } catch (final IOException e) {
327 throw new MojoExecutionException(
328 "An IO error occurred while writing the message property file: "
329 + e);
330 }
331
332 getLog().info(
333 "Message file " + messageFile.getName() + " cleaned: "
334 + cleanedMessageCount + " messages removed and "
335 + savedMessageCount + " kept");
336 }
337 }
338
339
340
341 boolean isContinuedOnNextLine(final String line) {
342 int bsCount = 0;
343 for (int i = line.length() - 1; i >= 0 && line.charAt(i) == '\\'; i--) {
344 bsCount++;
345 }
346
347 return ((bsCount % 2) == 1);
348 }
349
350
351
352 private void processSourceDirectory(final ExecutorService executor,
353 final File s) {
354 for (final File f : s.listFiles()) {
355 if (f.isDirectory()) {
356 processSourceDirectory(executor, f);
357 } else if (f.isFile()) {
358 if (f.getName().endsWith(".java")) {
359
360 executor.execute(new SourceFileTask(f));
361 }
362 }
363 }
364 }
365 }