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 4583 2012-06-03 01:29:11Z schulte2005 $ |
29 | * |
30 | */ |
31 | package org.jomc.tools; |
32 | |
33 | import java.io.ByteArrayOutputStream; |
34 | import java.io.Closeable; |
35 | import java.io.File; |
36 | import java.io.IOException; |
37 | import java.io.RandomAccessFile; |
38 | import java.nio.ByteBuffer; |
39 | import java.nio.channels.FileChannel; |
40 | import java.nio.channels.FileLock; |
41 | import java.text.MessageFormat; |
42 | import java.util.HashMap; |
43 | import java.util.Locale; |
44 | import java.util.Map; |
45 | import java.util.Properties; |
46 | import java.util.ResourceBundle; |
47 | import java.util.logging.Level; |
48 | import org.apache.velocity.VelocityContext; |
49 | import org.jomc.model.Implementation; |
50 | import org.jomc.model.Message; |
51 | import org.jomc.model.Messages; |
52 | import org.jomc.model.Module; |
53 | import org.jomc.model.Specification; |
54 | import org.jomc.model.Text; |
55 | |
56 | /** |
57 | * Processes resource files. |
58 | * |
59 | * <p><b>Use Cases:</b><br/><ul> |
60 | * <li>{@link #writeResourceBundleResourceFiles(File) }</li> |
61 | * <li>{@link #writeResourceBundleResourceFiles(Module, File) }</li> |
62 | * <li>{@link #writeResourceBundleResourceFiles(Specification, File) }</li> |
63 | * <li>{@link #writeResourceBundleResourceFiles(Implementation, File) }</li> |
64 | * </ul></p> |
65 | * |
66 | * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a> |
67 | * @version $JOMC: ResourceFileProcessor.java 4583 2012-06-03 01:29:11Z schulte2005 $ |
68 | * |
69 | * @see #getModules() |
70 | */ |
71 | public class ResourceFileProcessor extends JomcTool |
72 | { |
73 | |
74 | /** The language of the default language properties file of generated resource bundle resources. */ |
75 | private Locale resourceBundleDefaultLocale; |
76 | |
77 | /** Creates a new {@code ResourceFileProcessor} instance. */ |
78 | public ResourceFileProcessor() |
79 | { |
80 | super(); |
81 | } |
82 | |
83 | /** |
84 | * Creates a new {@code ResourceFileProcessor} instance taking a {@code ResourceFileProcessor} instance to |
85 | * initialize the instance with. |
86 | * |
87 | * @param tool The instance to initialize the new instance with. |
88 | * |
89 | * @throws NullPointerException if {@code tool} is {@code null}. |
90 | * @throws IOException if copying {@code tool} fails. |
91 | */ |
92 | public ResourceFileProcessor( final ResourceFileProcessor tool ) throws IOException |
93 | { |
94 | super( tool ); |
95 | this.resourceBundleDefaultLocale = tool.resourceBundleDefaultLocale; |
96 | } |
97 | |
98 | /** |
99 | * Gets the language of the default language properties file of generated resource bundle resource files. |
100 | * |
101 | * @return The language of the default language properties file of generated resource bundle resource files. |
102 | * |
103 | * @see #setResourceBundleDefaultLocale(java.util.Locale) |
104 | */ |
105 | public final Locale getResourceBundleDefaultLocale() |
106 | { |
107 | if ( this.resourceBundleDefaultLocale == null ) |
108 | { |
109 | this.resourceBundleDefaultLocale = Locale.ENGLISH; |
110 | |
111 | if ( this.isLoggable( Level.CONFIG ) ) |
112 | { |
113 | this.log( Level.CONFIG, getMessage( "defaultResourceBundleDefaultLocale", |
114 | this.resourceBundleDefaultLocale ), null ); |
115 | |
116 | } |
117 | } |
118 | |
119 | return this.resourceBundleDefaultLocale; |
120 | } |
121 | |
122 | /** |
123 | * Sets the language of the default language properties file of generated resource bundle resource files. |
124 | * |
125 | * @param value The language of the default language properties file of generated resource bundle resource files. |
126 | * |
127 | * @see #getResourceBundleDefaultLocale() |
128 | */ |
129 | public final void setResourceBundleDefaultLocale( final Locale value ) |
130 | { |
131 | this.resourceBundleDefaultLocale = value; |
132 | } |
133 | |
134 | /** |
135 | * Writes resource bundle resource files of the modules of the instance to a given directory. |
136 | * |
137 | * @param resourcesDirectory The directory to write resource bundle resource files to. |
138 | * |
139 | * @throws NullPointerException if {@code resourcesDirectory} is {@code null}. |
140 | * @throws IOException if writing resource bundle resource files fails. |
141 | * |
142 | * @see #writeResourceBundleResourceFiles(org.jomc.model.Module, java.io.File) |
143 | */ |
144 | public void writeResourceBundleResourceFiles( final File resourcesDirectory ) throws IOException |
145 | { |
146 | if ( resourcesDirectory == null ) |
147 | { |
148 | throw new NullPointerException( "resourcesDirectory" ); |
149 | } |
150 | |
151 | if ( this.getModules() != null ) |
152 | { |
153 | for ( int i = 0, s0 = this.getModules().getModule().size(); i < s0; i++ ) |
154 | { |
155 | this.writeResourceBundleResourceFiles( this.getModules().getModule().get( i ), resourcesDirectory ); |
156 | } |
157 | } |
158 | else if ( this.isLoggable( Level.WARNING ) ) |
159 | { |
160 | this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null ); |
161 | } |
162 | } |
163 | |
164 | /** |
165 | * Writes resource bundle resource files of a given module from the modules of the instance to a given directory. |
166 | * |
167 | * @param module The module to process. |
168 | * @param resourcesDirectory The directory to write resource bundle resource files to. |
169 | * |
170 | * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}. |
171 | * @throws IOException if writing resource bundle resource files fails. |
172 | * |
173 | * @see #writeResourceBundleResourceFiles(org.jomc.model.Specification, java.io.File) |
174 | * @see #writeResourceBundleResourceFiles(org.jomc.model.Implementation, java.io.File) |
175 | */ |
176 | public void writeResourceBundleResourceFiles( final Module module, final File resourcesDirectory ) |
177 | throws IOException |
178 | { |
179 | if ( module == null ) |
180 | { |
181 | throw new NullPointerException( "module" ); |
182 | } |
183 | if ( resourcesDirectory == null ) |
184 | { |
185 | throw new NullPointerException( "resourcesDirectory" ); |
186 | } |
187 | |
188 | if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null ) |
189 | { |
190 | if ( module.getSpecifications() != null ) |
191 | { |
192 | for ( int i = 0, s0 = module.getSpecifications().getSpecification().size(); i < s0; i++ ) |
193 | { |
194 | this.writeResourceBundleResourceFiles( module.getSpecifications().getSpecification().get( i ), |
195 | resourcesDirectory ); |
196 | |
197 | } |
198 | } |
199 | |
200 | if ( module.getImplementations() != null ) |
201 | { |
202 | for ( int i = 0, s0 = module.getImplementations().getImplementation().size(); i < s0; i++ ) |
203 | { |
204 | this.writeResourceBundleResourceFiles( module.getImplementations().getImplementation().get( i ), |
205 | resourcesDirectory ); |
206 | |
207 | } |
208 | } |
209 | } |
210 | else if ( this.isLoggable( Level.WARNING ) ) |
211 | { |
212 | this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null ); |
213 | } |
214 | } |
215 | |
216 | /** |
217 | * Writes resource bundle resource files of a given specification from the modules of the instance to a directory. |
218 | * |
219 | * @param specification The specification to process. |
220 | * @param resourcesDirectory The directory to write resource bundle resource files to. |
221 | * |
222 | * @throws NullPointerException if {@code specification} or {@code resourcesDirectory} is {@code null}. |
223 | * @throws IOException if writing resource bundle resource files fails. |
224 | * |
225 | * @see #getResourceBundleResources(org.jomc.model.Specification) |
226 | */ |
227 | public void writeResourceBundleResourceFiles( final Specification specification, final File resourcesDirectory ) |
228 | throws IOException |
229 | { |
230 | if ( specification == null ) |
231 | { |
232 | throw new NullPointerException( "implementation" ); |
233 | } |
234 | if ( resourcesDirectory == null ) |
235 | { |
236 | throw new NullPointerException( "resourcesDirectory" ); |
237 | } |
238 | |
239 | if ( this.getModules() != null |
240 | && this.getModules().getSpecification( specification.getIdentifier() ) != null ) |
241 | { |
242 | if ( specification.isClassDeclaration() ) |
243 | { |
244 | if ( !resourcesDirectory.isDirectory() ) |
245 | { |
246 | throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) ); |
247 | } |
248 | |
249 | this.assertValidTemplates( specification ); |
250 | |
251 | final String bundlePath = |
252 | this.getJavaTypeName( specification, true ).replace( '.', File.separatorChar ); |
253 | |
254 | this.writeResourceBundleResourceFiles( |
255 | this.getResourceBundleResources( specification ), resourcesDirectory, bundlePath ); |
256 | |
257 | } |
258 | } |
259 | else if ( this.isLoggable( Level.WARNING ) ) |
260 | { |
261 | this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null ); |
262 | } |
263 | } |
264 | |
265 | /** |
266 | * Writes resource bundle resource files of a given implementation from the modules of the instance to a directory. |
267 | * |
268 | * @param implementation The implementation to process. |
269 | * @param resourcesDirectory The directory to write resource bundle resource files to. |
270 | * |
271 | * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}. |
272 | * @throws IOException if writing resource bundle resource files fails. |
273 | * |
274 | * @see #getResourceBundleResources(org.jomc.model.Implementation) |
275 | */ |
276 | public void writeResourceBundleResourceFiles( final Implementation implementation, final File resourcesDirectory ) |
277 | throws IOException |
278 | { |
279 | if ( implementation == null ) |
280 | { |
281 | throw new NullPointerException( "implementation" ); |
282 | } |
283 | if ( resourcesDirectory == null ) |
284 | { |
285 | throw new NullPointerException( "resourcesDirectory" ); |
286 | } |
287 | |
288 | if ( this.getModules() != null |
289 | && this.getModules().getImplementation( implementation.getIdentifier() ) != null ) |
290 | { |
291 | if ( implementation.isClassDeclaration() ) |
292 | { |
293 | if ( !resourcesDirectory.isDirectory() ) |
294 | { |
295 | throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) ); |
296 | } |
297 | |
298 | this.assertValidTemplates( implementation ); |
299 | |
300 | final String bundlePath = |
301 | this.getJavaTypeName( implementation, true ).replace( '.', File.separatorChar ); |
302 | |
303 | this.writeResourceBundleResourceFiles( |
304 | this.getResourceBundleResources( implementation ), resourcesDirectory, bundlePath ); |
305 | |
306 | } |
307 | } |
308 | else if ( this.isLoggable( Level.WARNING ) ) |
309 | { |
310 | this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null ); |
311 | } |
312 | } |
313 | |
314 | /** |
315 | * Gets resource bundle properties resources of a given specification. |
316 | * |
317 | * @param specification The specification to get resource bundle properties resources of. |
318 | * |
319 | * @return Resource bundle properties resources of {@code specification} or {@code null}, if no model objects are |
320 | * found. |
321 | * |
322 | * @throws NullPointerException if {@code specification} is {@code null}. |
323 | * @throws IOException if getting the resource bundle properties resources fails. |
324 | */ |
325 | public Map<Locale, Properties> getResourceBundleResources( final Specification specification ) |
326 | throws IOException |
327 | { |
328 | if ( specification == null ) |
329 | { |
330 | throw new NullPointerException( "specification" ); |
331 | } |
332 | |
333 | Map<Locale, Properties> properties = null; |
334 | |
335 | if ( this.getModules() != null |
336 | && this.getModules().getSpecification( specification.getIdentifier() ) != null ) |
337 | { |
338 | properties = new HashMap<Locale, Properties>(); |
339 | } |
340 | else if ( this.isLoggable( Level.WARNING ) ) |
341 | { |
342 | this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null ); |
343 | } |
344 | |
345 | return properties; |
346 | } |
347 | |
348 | /** |
349 | * Gets resource bundle properties resources of a given implementation. |
350 | * |
351 | * @param implementation The implementation to get resource bundle properties resources of. |
352 | * |
353 | * @return Resource bundle properties resources of {@code implementation} or {@code null}, if no model objects are |
354 | * found. |
355 | * |
356 | * @throws NullPointerException if {@code implementation} is {@code null}. |
357 | * @throws IOException if getting the resource bundle properties resources fails. |
358 | */ |
359 | public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation ) |
360 | throws IOException |
361 | { |
362 | if ( implementation == null ) |
363 | { |
364 | throw new NullPointerException( "implementation" ); |
365 | } |
366 | |
367 | Map<Locale, Properties> properties = null; |
368 | |
369 | if ( this.getModules() != null |
370 | && this.getModules().getImplementation( implementation.getIdentifier() ) != null ) |
371 | { |
372 | properties = new HashMap<Locale, java.util.Properties>( 10 ); |
373 | final Messages messages = this.getModules().getMessages( implementation.getIdentifier() ); |
374 | |
375 | if ( messages != null ) |
376 | { |
377 | for ( int i = 0, s0 = messages.getMessage().size(); i < s0; i++ ) |
378 | { |
379 | final Message message = messages.getMessage().get( i ); |
380 | |
381 | if ( message.getTemplate() != null ) |
382 | { |
383 | for ( int j = 0, s1 = message.getTemplate().getText().size(); j < s1; j++ ) |
384 | { |
385 | final Text text = message.getTemplate().getText().get( j ); |
386 | final Locale locale = new Locale( text.getLanguage().toLowerCase() ); |
387 | Properties bundleProperties = properties.get( locale ); |
388 | |
389 | if ( bundleProperties == null ) |
390 | { |
391 | bundleProperties = new Properties(); |
392 | properties.put( locale, bundleProperties ); |
393 | } |
394 | |
395 | bundleProperties.setProperty( message.getName(), text.getValue() ); |
396 | } |
397 | } |
398 | } |
399 | } |
400 | } |
401 | else if ( this.isLoggable( Level.WARNING ) ) |
402 | { |
403 | this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null ); |
404 | } |
405 | |
406 | return properties; |
407 | } |
408 | |
409 | private void writeResourceBundleResourceFiles( final Map<Locale, Properties> resources, |
410 | final File resourcesDirectory, final String bundlePath ) |
411 | throws IOException |
412 | { |
413 | if ( resources == null ) |
414 | { |
415 | throw new NullPointerException( "resources" ); |
416 | } |
417 | if ( resourcesDirectory == null ) |
418 | { |
419 | throw new NullPointerException( "resourcesDirectory" ); |
420 | } |
421 | if ( bundlePath == null ) |
422 | { |
423 | throw new NullPointerException( "bundlePath" ); |
424 | } |
425 | |
426 | Properties defProperties = null; |
427 | Properties fallbackProperties = null; |
428 | |
429 | final VelocityContext ctx = this.getVelocityContext(); |
430 | final String toolName = ctx.get( "toolName" ).toString(); |
431 | final String toolVersion = ctx.get( "toolVersion" ).toString(); |
432 | final String toolUrl = ctx.get( "toolUrl" ).toString(); |
433 | |
434 | for ( Map.Entry<Locale, Properties> e : resources.entrySet() ) |
435 | { |
436 | final String language = e.getKey().getLanguage().toLowerCase(); |
437 | final Properties p = e.getValue(); |
438 | final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" ); |
439 | |
440 | if ( this.getResourceBundleDefaultLocale().getLanguage().equalsIgnoreCase( language ) ) |
441 | { |
442 | defProperties = p; |
443 | } |
444 | |
445 | fallbackProperties = p; |
446 | |
447 | if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() ) |
448 | { |
449 | throw new IOException( getMessage( "failedCreatingDirectory", |
450 | file.getParentFile().getAbsolutePath() ) ); |
451 | |
452 | } |
453 | |
454 | if ( this.isLoggable( Level.INFO ) ) |
455 | { |
456 | this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null ); |
457 | } |
458 | |
459 | this.writePropertiesFile( p, toolName + ' ' + toolVersion + " - See " + toolUrl, file ); |
460 | } |
461 | |
462 | if ( defProperties == null ) |
463 | { |
464 | defProperties = fallbackProperties; |
465 | } |
466 | |
467 | if ( defProperties != null ) |
468 | { |
469 | final File file = new File( resourcesDirectory, bundlePath + ".properties" ); |
470 | |
471 | if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() ) |
472 | { |
473 | throw new IOException( getMessage( "failedCreatingDirectory", |
474 | file.getParentFile().getAbsolutePath() ) ); |
475 | |
476 | } |
477 | |
478 | if ( this.isLoggable( Level.INFO ) ) |
479 | { |
480 | this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null ); |
481 | } |
482 | |
483 | this.writePropertiesFile( defProperties, toolName + ' ' + toolVersion + " - See " + toolUrl, file ); |
484 | } |
485 | } |
486 | |
487 | private void assertValidTemplates( final Specification specification ) |
488 | { |
489 | if ( specification == null ) |
490 | { |
491 | throw new NullPointerException( "specification" ); |
492 | } |
493 | } |
494 | |
495 | private void assertValidTemplates( final Implementation implementation ) |
496 | { |
497 | if ( implementation == null ) |
498 | { |
499 | throw new NullPointerException( "implementation" ); |
500 | } |
501 | |
502 | final Messages messages = this.getModules().getMessages( implementation.getIdentifier() ); |
503 | |
504 | if ( messages != null ) |
505 | { |
506 | for ( int i = messages.getMessage().size() - 1; i >= 0; i-- ) |
507 | { |
508 | final Message m = messages.getMessage().get( i ); |
509 | |
510 | if ( m.getTemplate() != null ) |
511 | { |
512 | for ( int j = m.getTemplate().getText().size() - 1; j >= 0; j-- ) |
513 | { |
514 | new MessageFormat( m.getTemplate().getText().get( j ).getValue() ); |
515 | } |
516 | } |
517 | } |
518 | } |
519 | } |
520 | |
521 | private void writePropertiesFile( final Properties properties, final String comments, final File propertiesFile ) |
522 | throws IOException |
523 | { |
524 | RandomAccessFile randomAccessFile = null; |
525 | FileChannel fileChannel = null; |
526 | FileLock fileLock = null; |
527 | boolean suppressExceptionOnClose = true; |
528 | |
529 | final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); |
530 | properties.store( byteStream, comments ); |
531 | byteStream.close(); |
532 | |
533 | final byte[] bytes = byteStream.toByteArray(); |
534 | |
535 | try |
536 | { |
537 | randomAccessFile = new RandomAccessFile( propertiesFile, "rw" ); |
538 | fileChannel = randomAccessFile.getChannel(); |
539 | fileLock = fileChannel.lock(); |
540 | fileChannel.truncate( bytes.length ); |
541 | fileChannel.position( 0L ); |
542 | fileChannel.write( ByteBuffer.wrap( bytes ) ); |
543 | fileChannel.force( true ); |
544 | suppressExceptionOnClose = false; |
545 | } |
546 | finally |
547 | { |
548 | this.releaseAndClose( fileLock, fileChannel, randomAccessFile, suppressExceptionOnClose ); |
549 | } |
550 | } |
551 | |
552 | private void releaseAndClose( final FileLock fileLock, final FileChannel fileChannel, |
553 | final Closeable closeable, final boolean suppressExceptions ) |
554 | throws IOException |
555 | { |
556 | try |
557 | { |
558 | if ( fileLock != null ) |
559 | { |
560 | fileLock.release(); |
561 | } |
562 | } |
563 | catch ( final IOException e ) |
564 | { |
565 | if ( suppressExceptions ) |
566 | { |
567 | this.log( Level.SEVERE, null, e ); |
568 | } |
569 | else |
570 | { |
571 | throw e; |
572 | } |
573 | } |
574 | finally |
575 | { |
576 | try |
577 | { |
578 | if ( fileChannel != null ) |
579 | { |
580 | fileChannel.close(); |
581 | } |
582 | } |
583 | catch ( final IOException e ) |
584 | { |
585 | if ( suppressExceptions ) |
586 | { |
587 | this.log( Level.SEVERE, null, e ); |
588 | } |
589 | else |
590 | { |
591 | throw e; |
592 | } |
593 | } |
594 | finally |
595 | { |
596 | try |
597 | { |
598 | if ( closeable != null ) |
599 | { |
600 | closeable.close(); |
601 | } |
602 | } |
603 | catch ( final IOException e ) |
604 | { |
605 | if ( suppressExceptions ) |
606 | { |
607 | this.log( Level.SEVERE, null, e ); |
608 | } |
609 | else |
610 | { |
611 | throw e; |
612 | } |
613 | } |
614 | } |
615 | } |
616 | } |
617 | |
618 | private static String getMessage( final String key, final Object... arguments ) |
619 | { |
620 | if ( key == null ) |
621 | { |
622 | throw new NullPointerException( "key" ); |
623 | } |
624 | |
625 | return MessageFormat.format( ResourceBundle.getBundle( |
626 | ResourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments ); |
627 | |
628 | } |
629 | |
630 | private static String getMessage( final Throwable t ) |
631 | { |
632 | return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null; |
633 | } |
634 | |
635 | } |