1 | /* |
2 | * Copyright (C) Christian Schulte, 2005-206 |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * |
9 | * o Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * |
12 | * o Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer in |
14 | * the documentation and/or other materials provided with the |
15 | * distribution. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY |
19 | * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
20 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, |
21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | * |
28 | * $JOMC: ResourceFileProcessor.java 3868 2011-10-14 13:23:09Z schulte2005 $ |
29 | * |
30 | */ |
31 | package org.jomc.tools; |
32 | |
33 | import java.io.File; |
34 | import java.io.FileOutputStream; |
35 | import java.io.IOException; |
36 | import java.io.OutputStream; |
37 | import java.text.MessageFormat; |
38 | import java.util.HashMap; |
39 | import java.util.Locale; |
40 | import java.util.Map; |
41 | import java.util.Properties; |
42 | import java.util.ResourceBundle; |
43 | import java.util.logging.Level; |
44 | import org.apache.velocity.VelocityContext; |
45 | import org.jomc.model.Implementation; |
46 | import org.jomc.model.Message; |
47 | import org.jomc.model.Messages; |
48 | import org.jomc.model.Module; |
49 | import org.jomc.model.Specification; |
50 | import org.jomc.model.Text; |
51 | |
52 | /** |
53 | * Processes resource files. |
54 | * |
55 | * <p><b>Use Cases:</b><br/><ul> |
56 | * <li>{@link #writeResourceBundleResourceFiles(File) }</li> |
57 | * <li>{@link #writeResourceBundleResourceFiles(Module, File) }</li> |
58 | * <li>{@link #writeResourceBundleResourceFiles(Specification, File) }</li> |
59 | * <li>{@link #writeResourceBundleResourceFiles(Implementation, File) }</li> |
60 | * </ul></p> |
61 | * |
62 | * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a> |
63 | * @version $JOMC: ResourceFileProcessor.java 3868 2011-10-14 13:23:09Z schulte2005 $ |
64 | * |
65 | * @see #getModules() |
66 | */ |
67 | public class ResourceFileProcessor extends JomcTool |
68 | { |
69 | |
70 | /** The language of the default language properties file of generated resource bundle resources. */ |
71 | private Locale resourceBundleDefaultLocale; |
72 | |
73 | /** Creates a new {@code ResourceFileProcessor} instance. */ |
74 | public ResourceFileProcessor() |
75 | { |
76 | super(); |
77 | } |
78 | |
79 | /** |
80 | * Creates a new {@code ResourceFileProcessor} instance taking a {@code ResourceFileProcessor} instance to |
81 | * initialize the instance with. |
82 | * |
83 | * @param tool The instance to initialize the new instance with. |
84 | * |
85 | * @throws NullPointerException if {@code tool} is {@code null}. |
86 | * @throws IOException if copying {@code tool} fails. |
87 | */ |
88 | public ResourceFileProcessor( final ResourceFileProcessor tool ) throws IOException |
89 | { |
90 | super( tool ); |
91 | this.resourceBundleDefaultLocale = tool.resourceBundleDefaultLocale; |
92 | } |
93 | |
94 | /** |
95 | * Gets the language of the default language properties file of generated resource bundle resource files. |
96 | * |
97 | * @return The language of the default language properties file of generated resource bundle resource files. |
98 | * |
99 | * @see #setResourceBundleDefaultLocale(java.util.Locale) |
100 | */ |
101 | public final Locale getResourceBundleDefaultLocale() |
102 | { |
103 | if ( this.resourceBundleDefaultLocale == null ) |
104 | { |
105 | this.resourceBundleDefaultLocale = Locale.ENGLISH; |
106 | |
107 | if ( this.isLoggable( Level.CONFIG ) ) |
108 | { |
109 | this.log( Level.CONFIG, |
110 | getMessage( "defaultResourceBundleDefaultLocale", this.resourceBundleDefaultLocale ), null ); |
111 | |
112 | } |
113 | } |
114 | |
115 | return this.resourceBundleDefaultLocale; |
116 | } |
117 | |
118 | /** |
119 | * Sets the language of the default language properties file of generated resource bundle resource files. |
120 | * |
121 | * @param value The language of the default language properties file of generated resource bundle resource files. |
122 | * |
123 | * @see #getResourceBundleDefaultLocale() |
124 | */ |
125 | public final void setResourceBundleDefaultLocale( final Locale value ) |
126 | { |
127 | this.resourceBundleDefaultLocale = value; |
128 | } |
129 | |
130 | /** |
131 | * Writes resource bundle resource files of the modules of the instance to a given directory. |
132 | * |
133 | * @param resourcesDirectory The directory to write resource bundle resource files to. |
134 | * |
135 | * @throws NullPointerException if {@code resourcesDirectory} is {@code null}. |
136 | * @throws IOException if writing resource bundle resource files fails. |
137 | * |
138 | * @see #writeResourceBundleResourceFiles(org.jomc.model.Module, java.io.File) |
139 | */ |
140 | public void writeResourceBundleResourceFiles( final File resourcesDirectory ) throws IOException |
141 | { |
142 | if ( resourcesDirectory == null ) |
143 | { |
144 | throw new NullPointerException( "resourcesDirectory" ); |
145 | } |
146 | |
147 | for ( int i = 0, s0 = this.getModules().getModule().size(); i < s0; i++ ) |
148 | { |
149 | this.writeResourceBundleResourceFiles( this.getModules().getModule().get( i ), resourcesDirectory ); |
150 | } |
151 | } |
152 | |
153 | /** |
154 | * Writes resource bundle resource files of a given module from the modules of the instance to a given directory. |
155 | * |
156 | * @param module The module to process. |
157 | * @param resourcesDirectory The directory to write resource bundle resource files to. |
158 | * |
159 | * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}. |
160 | * @throws IOException if writing resource bundle resource files fails. |
161 | * |
162 | * @see #writeResourceBundleResourceFiles(org.jomc.model.Specification, java.io.File) |
163 | * @see #writeResourceBundleResourceFiles(org.jomc.model.Implementation, java.io.File) |
164 | */ |
165 | public void writeResourceBundleResourceFiles( final Module module, final File resourcesDirectory ) |
166 | throws IOException |
167 | { |
168 | if ( module == null ) |
169 | { |
170 | throw new NullPointerException( "module" ); |
171 | } |
172 | if ( resourcesDirectory == null ) |
173 | { |
174 | throw new NullPointerException( "resourcesDirectory" ); |
175 | } |
176 | |
177 | assert this.getModules().getModule( module.getName() ) != null : "Module '" + module.getName() + "' not found."; |
178 | |
179 | if ( module.getSpecifications() != null ) |
180 | { |
181 | for ( int i = 0, s0 = module.getSpecifications().getSpecification().size(); i < s0; i++ ) |
182 | { |
183 | this.writeResourceBundleResourceFiles( module.getSpecifications().getSpecification().get( i ), |
184 | resourcesDirectory ); |
185 | |
186 | } |
187 | } |
188 | |
189 | if ( module.getImplementations() != null ) |
190 | { |
191 | for ( int i = 0, s0 = module.getImplementations().getImplementation().size(); i < s0; i++ ) |
192 | { |
193 | this.writeResourceBundleResourceFiles( module.getImplementations().getImplementation().get( i ), |
194 | resourcesDirectory ); |
195 | |
196 | } |
197 | } |
198 | } |
199 | |
200 | /** |
201 | * Writes resource bundle resource files of a given specification from the modules of the instance to a directory. |
202 | * |
203 | * @param specification The specification to process. |
204 | * @param resourcesDirectory The directory to write resource bundle resource files to. |
205 | * |
206 | * @throws NullPointerException if {@code specification} or {@code resourcesDirectory} is {@code null}. |
207 | * @throws IOException if writing resource bundle resource files fails. |
208 | * |
209 | * @see #getResourceBundleResources(org.jomc.model.Specification) |
210 | */ |
211 | public void writeResourceBundleResourceFiles( final Specification specification, final File resourcesDirectory ) |
212 | throws IOException |
213 | { |
214 | if ( specification == null ) |
215 | { |
216 | throw new NullPointerException( "implementation" ); |
217 | } |
218 | if ( resourcesDirectory == null ) |
219 | { |
220 | throw new NullPointerException( "resourcesDirectory" ); |
221 | } |
222 | |
223 | assert this.getModules().getSpecification( specification.getIdentifier() ) != null : |
224 | "Specification '" + specification.getIdentifier() + "' not found."; |
225 | |
226 | if ( specification.isClassDeclaration() ) |
227 | { |
228 | if ( !resourcesDirectory.isDirectory() ) |
229 | { |
230 | throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) ); |
231 | } |
232 | |
233 | this.assertValidTemplates( specification ); |
234 | |
235 | final String bundlePath = |
236 | this.getJavaTypeName( specification, true ).replace( '.', File.separatorChar ); |
237 | |
238 | this.writeResourceBundleResourceFiles( |
239 | this.getResourceBundleResources( specification ), resourcesDirectory, bundlePath ); |
240 | |
241 | } |
242 | } |
243 | |
244 | /** |
245 | * Writes resource bundle resource files of a given implementation from the modules of the instance to a directory. |
246 | * |
247 | * @param implementation The implementation to process. |
248 | * @param resourcesDirectory The directory to write resource bundle resource files to. |
249 | * |
250 | * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}. |
251 | * @throws IOException if writing resource bundle resource files fails. |
252 | * |
253 | * @see #getResourceBundleResources(org.jomc.model.Implementation) |
254 | */ |
255 | public void writeResourceBundleResourceFiles( final Implementation implementation, final File resourcesDirectory ) |
256 | throws IOException |
257 | { |
258 | if ( implementation == null ) |
259 | { |
260 | throw new NullPointerException( "implementation" ); |
261 | } |
262 | if ( resourcesDirectory == null ) |
263 | { |
264 | throw new NullPointerException( "resourcesDirectory" ); |
265 | } |
266 | |
267 | assert this.getModules().getImplementation( implementation.getIdentifier() ) != null : |
268 | "Implementation '" + implementation.getIdentifier() + "' not found."; |
269 | |
270 | if ( implementation.isClassDeclaration() ) |
271 | { |
272 | if ( !resourcesDirectory.isDirectory() ) |
273 | { |
274 | throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) ); |
275 | } |
276 | |
277 | this.assertValidTemplates( implementation ); |
278 | |
279 | final String bundlePath = |
280 | this.getJavaTypeName( implementation, true ).replace( '.', File.separatorChar ); |
281 | |
282 | this.writeResourceBundleResourceFiles( |
283 | this.getResourceBundleResources( implementation ), resourcesDirectory, bundlePath ); |
284 | |
285 | } |
286 | } |
287 | |
288 | /** |
289 | * Gets resource bundle properties resources of a given specification. |
290 | * |
291 | * @param specification The specification to get resource bundle properties resources of. |
292 | * |
293 | * @return Resource bundle properties resources of {@code specification}. |
294 | * |
295 | * @throws NullPointerException if {@code specification} is {@code null}. |
296 | * @throws IOException if getting the resource bundle properties resources fails. |
297 | */ |
298 | public Map<Locale, Properties> getResourceBundleResources( final Specification specification ) |
299 | throws IOException |
300 | { |
301 | if ( specification == null ) |
302 | { |
303 | throw new NullPointerException( "specification" ); |
304 | } |
305 | |
306 | assert this.getModules().getSpecification( specification.getIdentifier() ) != null : |
307 | "Specification '" + specification.getIdentifier() + "' not found."; |
308 | |
309 | return new HashMap<Locale, Properties>(); |
310 | } |
311 | |
312 | /** |
313 | * Gets resource bundle properties resources of a given implementation. |
314 | * |
315 | * @param implementation The implementation to get resource bundle properties resources of. |
316 | * |
317 | * @return Resource bundle properties resources of {@code implementation}. |
318 | * |
319 | * @throws NullPointerException if {@code implementation} is {@code null}. |
320 | * @throws IOException if getting the resource bundle properties resources fails. |
321 | */ |
322 | public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation ) |
323 | throws IOException |
324 | { |
325 | if ( implementation == null ) |
326 | { |
327 | throw new NullPointerException( "implementation" ); |
328 | } |
329 | |
330 | assert this.getModules().getImplementation( implementation.getIdentifier() ) != null : |
331 | "Implementation '" + implementation.getIdentifier() + "' not found."; |
332 | |
333 | final Map<Locale, java.util.Properties> properties = new HashMap<Locale, java.util.Properties>( 10 ); |
334 | final Messages messages = this.getModules().getMessages( implementation.getIdentifier() ); |
335 | |
336 | if ( messages != null ) |
337 | { |
338 | for ( int i = 0, s0 = messages.getMessage().size(); i < s0; i++ ) |
339 | { |
340 | final Message message = messages.getMessage().get( i ); |
341 | |
342 | if ( message.getTemplate() != null ) |
343 | { |
344 | for ( int j = 0, s1 = message.getTemplate().getText().size(); j < s1; j++ ) |
345 | { |
346 | final Text text = message.getTemplate().getText().get( j ); |
347 | final Locale locale = new Locale( text.getLanguage().toLowerCase() ); |
348 | Properties bundleProperties = properties.get( locale ); |
349 | |
350 | if ( bundleProperties == null ) |
351 | { |
352 | bundleProperties = new Properties(); |
353 | properties.put( locale, bundleProperties ); |
354 | } |
355 | |
356 | bundleProperties.setProperty( message.getName(), text.getValue() ); |
357 | } |
358 | } |
359 | } |
360 | } |
361 | |
362 | return properties; |
363 | } |
364 | |
365 | private void writeResourceBundleResourceFiles( final Map<Locale, Properties> resources, |
366 | final File resourcesDirectory, final String bundlePath ) |
367 | throws IOException |
368 | { |
369 | if ( resources == null ) |
370 | { |
371 | throw new NullPointerException( "resources" ); |
372 | } |
373 | if ( resourcesDirectory == null ) |
374 | { |
375 | throw new NullPointerException( "resourcesDirectory" ); |
376 | } |
377 | if ( bundlePath == null ) |
378 | { |
379 | throw new NullPointerException( "bundlePath" ); |
380 | } |
381 | |
382 | Properties defProperties = null; |
383 | Properties fallbackProperties = null; |
384 | |
385 | final VelocityContext ctx = this.getVelocityContext(); |
386 | final String toolName = ctx.get( "toolName" ).toString(); |
387 | final String toolVersion = ctx.get( "toolVersion" ).toString(); |
388 | final String toolUrl = ctx.get( "toolUrl" ).toString(); |
389 | |
390 | for ( Map.Entry<Locale, Properties> e : resources.entrySet() ) |
391 | { |
392 | final String language = e.getKey().getLanguage().toLowerCase(); |
393 | final Properties p = e.getValue(); |
394 | final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" ); |
395 | |
396 | if ( this.getResourceBundleDefaultLocale().getLanguage().equalsIgnoreCase( language ) ) |
397 | { |
398 | defProperties = p; |
399 | } |
400 | |
401 | fallbackProperties = p; |
402 | |
403 | if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() ) |
404 | { |
405 | throw new IOException( getMessage( "failedCreatingDirectory", |
406 | file.getParentFile().getAbsolutePath() ) ); |
407 | |
408 | } |
409 | |
410 | if ( this.isLoggable( Level.INFO ) ) |
411 | { |
412 | this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null ); |
413 | } |
414 | |
415 | OutputStream out = null; |
416 | boolean suppressExceptionOnClose = true; |
417 | try |
418 | { |
419 | out = new FileOutputStream( file ); |
420 | p.store( out, toolName + ' ' + toolVersion + " - See " + toolUrl ); |
421 | suppressExceptionOnClose = false; |
422 | } |
423 | finally |
424 | { |
425 | try |
426 | { |
427 | if ( out != null ) |
428 | { |
429 | out.close(); |
430 | } |
431 | } |
432 | catch ( final IOException ex ) |
433 | { |
434 | if ( suppressExceptionOnClose ) |
435 | { |
436 | this.log( Level.SEVERE, getMessage( ex ), ex ); |
437 | } |
438 | else |
439 | { |
440 | throw ex; |
441 | } |
442 | } |
443 | } |
444 | } |
445 | |
446 | if ( defProperties == null ) |
447 | { |
448 | defProperties = fallbackProperties; |
449 | } |
450 | |
451 | if ( defProperties != null ) |
452 | { |
453 | final File file = new File( resourcesDirectory, bundlePath + ".properties" ); |
454 | if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() ) |
455 | { |
456 | throw new IOException( getMessage( "failedCreatingDirectory", |
457 | file.getParentFile().getAbsolutePath() ) ); |
458 | |
459 | } |
460 | |
461 | if ( this.isLoggable( Level.INFO ) ) |
462 | { |
463 | this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null ); |
464 | } |
465 | |
466 | OutputStream out = null; |
467 | boolean suppressExceptionOnClose = true; |
468 | try |
469 | { |
470 | out = new FileOutputStream( file ); |
471 | defProperties.store( out, toolName + ' ' + toolVersion + " - See " + toolUrl ); |
472 | suppressExceptionOnClose = false; |
473 | } |
474 | finally |
475 | { |
476 | try |
477 | { |
478 | if ( out != null ) |
479 | { |
480 | out.close(); |
481 | } |
482 | } |
483 | catch ( final IOException e ) |
484 | { |
485 | if ( suppressExceptionOnClose ) |
486 | { |
487 | this.log( Level.SEVERE, getMessage( e ), e ); |
488 | } |
489 | else |
490 | { |
491 | throw e; |
492 | } |
493 | } |
494 | } |
495 | } |
496 | } |
497 | |
498 | private void assertValidTemplates( final Specification specification ) |
499 | { |
500 | if ( specification == null ) |
501 | { |
502 | throw new NullPointerException( "specification" ); |
503 | } |
504 | } |
505 | |
506 | private void assertValidTemplates( final Implementation implementation ) |
507 | { |
508 | if ( implementation == null ) |
509 | { |
510 | throw new NullPointerException( "implementation" ); |
511 | } |
512 | |
513 | final Messages messages = this.getModules().getMessages( implementation.getIdentifier() ); |
514 | |
515 | if ( messages != null ) |
516 | { |
517 | for ( int i = messages.getMessage().size() - 1; i >= 0; i-- ) |
518 | { |
519 | final Message m = messages.getMessage().get( i ); |
520 | |
521 | if ( m.getTemplate() != null ) |
522 | { |
523 | for ( int j = m.getTemplate().getText().size() - 1; j >= 0; j-- ) |
524 | { |
525 | new MessageFormat( m.getTemplate().getText().get( j ).getValue() ); |
526 | } |
527 | } |
528 | } |
529 | } |
530 | } |
531 | |
532 | private static String getMessage( final String key, final Object... arguments ) |
533 | { |
534 | if ( key == null ) |
535 | { |
536 | throw new NullPointerException( "key" ); |
537 | } |
538 | |
539 | return MessageFormat.format( ResourceBundle.getBundle( |
540 | ResourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments ); |
541 | |
542 | } |
543 | |
544 | private static String getMessage( final Throwable t ) |
545 | { |
546 | return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null; |
547 | } |
548 | |
549 | } |