source: 3thparty/jupload/maven-translation-plugin/src/main/java/wjhk/jupload/translation/TranslationManagerMojo.java @ 3951

Revision 3951, 41.2 KB checked in by alexandrecorreia, 13 years ago (diff)

Ticket #1709 - Adicao de codigo fonte java do componente jupload

Line 
1package wjhk.jupload.translation;
2
3import java.io.Closeable;
4
5import java.io.ByteArrayInputStream;
6import java.io.File;
7import java.io.FileInputStream;
8import java.io.FileOutputStream;
9import java.io.FilenameFilter;
10import java.io.IOException;
11import java.io.InputStream;
12import java.io.InputStreamReader;
13import java.io.OutputStream;
14import java.io.OutputStreamWriter;
15import java.io.PrintWriter;
16import java.io.PushbackReader;
17import java.io.StringWriter;
18import java.util.ArrayList;
19import java.util.HashMap;
20import java.util.List;
21import java.util.Map;
22import java.util.Properties;
23
24import java.util.regex.Matcher;
25import java.util.regex.Pattern;
26
27import org.apache.log4j.BasicConfigurator;
28import org.apache.log4j.Logger;
29import org.apache.maven.plugin.AbstractMojo;
30import org.apache.maven.plugin.MojoExecutionException;
31import org.apache.velocity.Template;
32import org.apache.velocity.VelocityContext;
33import org.apache.velocity.app.VelocityEngine;
34import org.apache.velocity.runtime.RuntimeConstants;
35
36/**
37 * We'll check all files, out of the .svn folder, which exist when we're working
38 * from a SVN checkout.
39 */
40class NoSvnFilenameFilter implements FilenameFilter {
41        public boolean accept(File arg0, String arg1) {
42                // We accept all files and folders, out of the ".svn" folder
43                return !(arg1.equals(".svn"));
44        }
45}
46
47/**
48 * This Maven plugin is manages the JUpload translation. It allows: <DIR> <LI>
49 * translators to manage human readable files. <LI>Encoding of the translations
50 * in unicode, as requested for Java property files.<LI>Back formatting of each
51 * 'human readable' translation file, according to the default translation
52 * (lang.properties). This allows all files to have the same format (including
53 * comment, order of the translation properties...)<LI>Generate the
54 * documentation, describing the existing translations </DIR>
55 *
56 * @phase generate-sources
57 * @goal translate
58 * @author etienne_sf
59 */
60// TODO Document this (in the howto-translate.apt file)
61
62public class TranslationManagerMojo extends AbstractMojo {
63        /** Logger for this class */
64        protected final Logger logger = Logger
65                        .getLogger(TranslationManagerMojo.class);
66
67        final static String DEFAULT_ENCODING = "UTF-8";
68
69        final static String NATIVE_ENCODING = "NATIVE";
70
71        final static String FILENAME_PREFIX = "lang";
72
73        final static String FILENAME_SUFFIX = ".properties";
74
75        final static Pattern PROPERTY_PATTERN = Pattern.compile("^(.*) = (.*)$");
76
77        final static String TEMPLATE_FILENAME = "lang.template.properties";
78
79        /**
80         * The default file is the lang.properties file. It will be unchanged. It
81         * actually acts as the file template to format all other files.
82         */
83        File defaultFile = null;
84
85        /**
86         * The instance of velocityEngine, which will be used to render the velocity
87         * templates.
88         */
89        VelocityEngine velocityEngine = null;
90
91        /**
92         * This files filter, when used in the
93         * {@link File#listFiles(FilenameFilter)} method, allows to get rid of the
94         * '.svn' folder, which we don't care when manipulating translations.
95         */
96        private static FilenameFilter noSvnFilenameFilter = new NoSvnFilenameFilter();
97
98        /*********************************************************************************************************
99         ********************************** LIST OF MOJO PARAMETERS **********************************************
100         *********************************************************************************************************/
101
102        /**
103         * The doc folder is the target folder, which will contain the generated
104         * documentation for each property file, found in the inputFolder. It is
105         * used to generate the documentation about the project translation. <BR>
106         * If the doc folder is a subfolder of the project target folder, then it
107         * may not exist. The plugin will create it. Otherwise, the specified folder
108         * must exist.<BR>
109         * It creates a file for each translation file. It uses the following
110         * properties, reading them from each translation language file:<DIR>
111         * <UL>
112         * <I>generateHtmlFileForTranslators</I>: true if a doc file should be
113         * generated for this file. Default to true: if this parameter is not set,
114         * the documentation is generated.
115         * <UL>
116         * <I>language</I>: The language for this file. This could be 'guessed' from
117         * the file name (lang_de is German...). Perhaps in a future release...
118         * <UL>
119         * <I>contributor</I>: The name of the contributor(s). It's both a thank to
120         * them ... and the possibility to email them, and ask them to complete the
121         * current translation when new text are added.
122         * <UL>
123         * <I></DIR> This parameter is not mandatory. If it is not defined, no
124         * documentation is generated.
125         *
126         * @parameter default-value=null
127         */
128        protected File docFolder = null;
129
130        /**
131         * This parameter is not directly related to the applet. Its aim is to group
132         * translation for the applet itself, and for a plugin for the Coppermine
133         * picture gallery. <BR>
134         * The hope is: that people who translate the applet lang file would also
135         * translate the Coppermine, at the time.<BR>
136         * This parameter is the folder which contain the Coppermine translation
137         * files. These files are of the form english.php or german.php. The
138         * language property contain the language, and thus allows to construct the
139         * filename of the Coppermine file associated with a lang_XX.property file.<BR>
140         * Note: the Coppermine files are supposed to be encoded in UTF-8.
141         *
142         * @parameter default-value=null
143         */
144        protected File coppermineFolder = null;
145
146        /**
147         * This parameter controls the suffix which will be appended to the
148         * generated doc files. For instance, it can be ".apt" for an APT file (used
149         * in Maven generating steps) or ".html" for HTML generated files. It's up
150         * to your templates (see templateXxx parameters) to generate a file which
151         * content is valid according to your file extension.
152         *
153         * @parameter default-value=""
154         */
155        protected String docFileExtension = null;
156
157        /**
158         * The character inputEncoding scheme to be applied when filtering
159         * resources. It may be one of UTF-8 and UTF-16.
160         *
161         * @parameter expression="${inputEncoding}" default-value="UTF-8"
162         */
163        protected String inputEncoding = DEFAULT_ENCODING;
164
165        /**
166         * The source folder, which contains the original lang files. These files
167         * are the original translation files, for translator. They are encoded
168         * according to the inputEncoding parameter value. These files are read,
169         * then transformed within this folder (no target folder).
170         *
171         * @parameter default-value="${project.basedir}/src/main/lang"
172         * @required
173         */
174        protected File inputFolder = null;
175
176        /**
177         * These folder is the target for the files used by Java. They are the
178         * result of the native2ascii transformation of the files that are in the
179         * inputFolder.
180         *
181         * @parameter default-value="${project.basedir}/src/main/resources/lang"
182         * @required
183         */
184        protected File resourceLangFolder = null;
185
186        /**
187         * The template for the file which describes the format of the file, which
188         * will contain the description of each available translation. This template
189         * is a Velocity template, based on 1.6 version of Velocity. This template
190         * should be encoded in UTF-8 format. The generated file will also be
191         * encoded in UTF-8.
192         * <P>
193         * In this parameter, you'll have to put a path which can be find by the
194         * class loader.
195         * <P>
196         * If this parameter is empty, null or not defined, no template will be
197         * applied.
198         * </P>
199         * <P>
200         * The available variables are:
201         * </P>
202         * <DIR>
203         * <UL>
204         * translations: The list of all translation. A foreach loop like the sample
205         * below allows you to go through each item of this list.
206         * <UL>
207         * translation.filename: The filename for the property file which contains
208         * this translation (e.g.: lang_fr.properties)
209         * <UL>
210         * translation.language: The language for this translation (e.g.: French)
211         * <UL>
212         * translation.contributor: The contributor(s), who made this translation.
213         * It's both a thank to them and the possibility to ask them to translate
214         * new stuff. </DIR>
215         * <P>
216         * Here is a sample to generate a table, in an APT file, for instance for a
217         * maven site documentation:
218         * </P>
219         *
220         * <PRE>
221         *            --------------------
222         *            JUpload - File Upload Applet - Applet translation (list of available translations)
223         *            --------------------
224         *            --------------------
225         *            --------------------
226         *
227         *   bla bla bla ...
228         *
229         * *--------------------*--------------------*
230         * | Language | Contributor |
231         * #foreach ($translation in $translations)
232         * | {{{./${translation.filename}.html}${translation.language}}} | ${translation.contributor} |
233         * #end
234         *  * --------------------*--------------------*
235         *
236         * @parameter expression="${templateAvailableTranslation}" default-value=null
237         * </PRE>
238         */
239        protected String templateAvailableTranslation = null;
240
241        /**
242         * The template which will be applied for each available translation. It is
243         * a Velocity template, based on 1.6 version of Velocity. This template
244         * should be encoded in UTF-8 format. The generated file will also be
245         * encoded in UTF-8.
246         * <P>
247         * In this parameter, you'll have to put a path which can be find by the
248         * class loader.
249         * <P>
250         * If this parameter is empty, null or not defined, no template will be
251         * applied.
252         * </P>
253         * <P>
254         * The available variables are:<DIR>
255         * <UL>
256         * language: The language for this file.
257         * <UL>
258         * applet_translation: The applet translation. This text is encoded in
259         * US-ASCII, with all unicode characters escaped in the format \\uNNNN
260         * <UL>
261         * coppermine_filename: null if no Coppermine translation given. If you
262         * manage only the applet, just ignore all Coppermine stuff. Read the plugin
263         * documentation for more information.
264         * <UL>
265         * coppermine_translation: The Coppermine translation in the current
266         * language, or in English, if no translation is available. </DIR>
267         *
268         * <P>
269         * Here is a sample to generate a table, in an APT file, for instance for a
270         * maven site documentation:
271         * </P>
272         *
273         * <PRE>
274         *            --------------------
275         *            JUpload - File Upload Applet - Applet translation (${language})
276         *            --------------------
277         *            --------------------
278         *            --------------------
279         *
280         *
281         *
282         * Translation for the applet part
283         *
284         * --------------
285         *
286         * ${applet_translation}
287         *
288         * --------------
289         *
290         * #if (${coppermine_filename})
291         *
292         * Translation for the CopperminePlugin part
293         *
294         * #if (${coppermine_filename} == "english.php")
295         *    The translation for ${language} doesn't exist. Please create a new one, based on the English translation below...
296         * #else
297         *    Content of the <${coppermine_filename}> file.
298         * #end
299         *
300         * --------------
301         *
302         * ${coppermine_translation}
303         *
304         * --------------
305         *
306         * #end
307         * </PRE>
308         *
309         * @parameter expression="${templateOneTranslation}" default-value=null
310         */
311        protected String templateOneTranslation = null;
312
313        /**
314         * The work folder, used during the translation process. This folder will
315         * contain the generated files. Then, if everything is Ok, these files are
316         * copied back to the {@link #inputFolder} folder..
317         *
318         * @parameter
319         *            default-value="${project.build.directory}/translation-workFolder"
320         * @required
321         */
322        protected File workFolder = null;
323
324        /*************************************************************************************
325         ********************************************* METHODS
326         *************************************************************************************/
327
328        /**
329         * The default constructor initializes the Mojo logging, and the Velocity
330         * engine.
331         *
332         * @throws MojoExecutionException
333         */
334        public TranslationManagerMojo() throws MojoExecutionException {
335                try {
336                        initVelocity();
337                } catch (Exception e) {
338                        throw new MojoExecutionException(e.getClass().getName()
339                                        + " in TranslationManagerMojo constructor ("
340                                        + e.getMessage() + ")");
341                }
342        }
343
344        /*************************************************************************************
345         ********************************************* METHODS
346         *************************************************************************************/
347
348        /**
349         * @see org.apache.maven.plugin.Mojo#execute()
350         */
351        public void execute() throws MojoExecutionException {
352                // inputFolder must be ... a folder!
353                if (inputFolder == null) {
354                        throw new NullPointerException("inputFolder may not be null");
355                } else if (!inputFolder.isDirectory()) {
356                        throw new MojoExecutionException(inputFolder.getAbsolutePath()
357                                        + " must be a directory");
358                }
359                getLog().debug("inputFolder: " + inputFolder.getAbsolutePath());
360                getLog().debug(
361                                "resourceLangFolder: " + resourceLangFolder.getAbsolutePath());
362                getLog().debug("workFolder: " + workFolder.getAbsolutePath());
363
364                defaultFile = new File(inputFolder + File.separator + FILENAME_PREFIX
365                                + FILENAME_SUFFIX);
366                getLog().debug("defaultFile: " + defaultFile.getAbsolutePath());
367
368                // Let's go through all available files, and format each one, out of the
369                // default one.
370                File[] files = inputFolder.listFiles(noSvnFilenameFilter);
371                for (int i = 0; i < files.length; i += 1) {
372                        // We format all files, out of the default one.
373                        if (files[i].getName().startsWith(FILENAME_PREFIX + "_")) {
374                                formatOneLangFile(files[i], inputEncoding);
375                        }
376                }
377
378                // If we succeed in formatting files, let's copy them back to the lang
379                // folder.
380                copyAllLangFilesToInputFolder();
381                // Then we copy the file to the resources/lang/ folder
382                copyAllLangFilesToResourcesLangFolder();
383                // And we finish by generating the documentation, if the docFolder
384                // has been defined.
385                if (docFolder != null) {
386                        generateDocForAvailableTranslations();
387
388                }
389        }
390
391        /**
392         * This method read one lang files, and return it as a loaded Properties
393         * instance.
394         *
395         * @param propertyFile
396         *            The property file to load
397         * @param fileEncoding
398         *            The inputEncoding of this property file
399         * @throws MojoExecutionException
400         */
401        Properties loadOneLangFile(File propertyFile, String fileEncoding)
402                        throws MojoExecutionException {
403                Properties translation = new Properties();
404                InputStream is = null;
405                InputStreamReader isr = null;
406                ByteArrayInputStream bais = null;
407                try {
408                        is = new FileInputStream(propertyFile);
409                        isr = new InputStreamReader(is, fileEncoding);
410                        int i;
411                        StringBuffer sb = new StringBuffer();
412                        while ((i = isr.read()) > 0) {
413                                sb.append((char) i);
414                        }
415                        byte[] byteInUnicode = toUnicode(sb.toString()).getBytes("ASCII");
416                        bais = new ByteArrayInputStream(byteInUnicode);
417                        translation.load(bais);
418                } catch (IOException e) {
419                        throw new MojoExecutionException(e.getClass().getName()
420                                        + " while reading this lang file: "
421                                        + propertyFile.getAbsolutePath() + " (" + e.getMessage()
422                                        + ")");
423                } finally {
424                        closeStream(isr, "isr");
425                        closeStream(is, propertyFile.getAbsolutePath());
426                        closeStream(bais, "bais");
427                }
428
429                return translation;
430        }
431
432        /**
433         * This method goes through the default files. It directly writes all
434         * comment and empty lines. For property lines, it tries to translated the
435         * text, based on the give property file. If this doesn't succeed a
436         * commented line, indicating that this translation is missing is written,
437         * with the English translation to allow an easy translation of missing
438         * texts in each translation properties file.
439         *
440         * @throws MojoExecutionException
441         */
442        // TODO document this method
443        void formatOneLangFile(File file, String fileEncoding)
444                        throws MojoExecutionException {
445                File targetFile = new File(workFolder + File.separator + file.getName());
446                InputStream isDefaultFile = null;
447                InputStreamReader isrDefaultFile = null;
448                PushbackReader pbReader = null;
449                OutputStream osTargetFile = null;
450                OutputStreamWriter osrTargetFile = null;
451
452                getLog().debug("Translating " + file.getAbsolutePath());
453                Properties propTargetTranslation = loadOneLangFile(file, fileEncoding);
454
455                try {
456                        isDefaultFile = new FileInputStream(defaultFile);
457                        isrDefaultFile = new InputStreamReader(isDefaultFile, fileEncoding);
458                        pbReader = new PushbackReader(isrDefaultFile, 1);
459
460                        osTargetFile = new FileOutputStream(targetFile);
461                        osrTargetFile = new OutputStreamWriter(osTargetFile, fileEncoding);
462
463                        // EOL may be the two characters (\r\n), or only one of them.
464                        int lineNumber = 0;
465                        boolean readingProperty = false;
466                        boolean lastCharWasEOL = true;
467                        boolean currentCharIsEOL = true;
468                        boolean eof = false;
469                        int iNextRead;
470                        StringBuffer sbPropertyLine = new StringBuffer();
471                        while (!eof) {
472                                iNextRead = pbReader.read();
473                                eof = (iNextRead < 0);
474                                char c = (char) iNextRead;
475
476                                // Let's manage the '\' character, for property lines, by
477                                // inspecting the next character, looking for special
478                                // combinations.
479                                if (readingProperty && c == '\\') {
480                                        int charForAntiSlashCombination = pbReader.read();
481                                        if (charForAntiSlashCombination < 0) {
482                                                // We finish the stream. It's a standard '\' character.
483                                                // Nothing to do (we go on in current loop).
484                                        } else if (charForAntiSlashCombination == '\\') {
485                                                // We have two '\', so it's actually ... one !
486                                                // Nothing to do (we go on in current loop).
487                                        } else if (charForAntiSlashCombination == '\n'
488                                                        || charForAntiSlashCombination == '\r') {
489                                                // We found an end of line. The current property is
490                                                // going on next line. Let's loop to next character, and
491                                                // ignore this one.
492
493                                                // We must first skip any second end of line character.
494                                                int nextEOLChar = pbReader.read();
495                                                if (nextEOLChar >= 0) {
496                                                        // We found a new character in the input stream. If
497                                                        // it forms a two-char-EOL, with the last read
498                                                        // character, we skip it
499                                                        if (charForAntiSlashCombination == '\n'
500                                                                        && nextEOLChar == '\r') {
501                                                                if (!readingProperty) {
502                                                                        // We want to read the nextEOLChar
503                                                                        // character, in the next loop.
504                                                                        pbReader.unread(nextEOLChar);
505                                                                } else {
506                                                                        // We'reading a property line. We've just
507                                                                        // skipped the end of line characters. We'll
508                                                                        // go on with next char.
509                                                                        // nothing to do
510                                                                }
511                                                        }
512                                                }
513                                                continue;
514                                        } else {
515                                                // It's not a special combination. We'll read next char
516                                                // in the next loop, and go one with the current one.
517                                                pbReader.unread(charForAntiSlashCombination);
518                                        }
519                                }
520
521                                lastCharWasEOL = currentCharIsEOL;
522                                currentCharIsEOL = eof || (c == '\r' || c == '\n');
523
524                                if (lastCharWasEOL && !currentCharIsEOL) {
525                                        // We're starting a new line. Is it a property line ?
526                                        readingProperty = (c != '#');
527                                        lineNumber += 1;
528                                }
529
530                                if (!readingProperty) {
531                                        // We directly write characters that are not belonging to a
532                                        // property line
533                                        if (!eof) {
534                                                osrTargetFile.write(c);
535                                        }
536                                } else if (!currentCharIsEOL) {
537                                        // We're reading the next char of the current property line.
538                                        // Let's store it.
539                                        sbPropertyLine.append(c);
540                                } else {
541                                        // We reached the end of a property line
542                                        // This line should be a property line, looking like:
543                                        // key = bla bla bla
544                                        // where "bla bla bla" is the default translation.
545                                        String line = sbPropertyLine.toString();
546                                        sbPropertyLine = new StringBuffer();
547                                        readingProperty = false;// Preparing management of next
548                                        // line.
549
550                                        Matcher matcher = PROPERTY_PATTERN.matcher(line);
551                                        if (matcher.matches()) {
552                                                // Ok, we found a property line.
553                                                String key = matcher.group(1);
554
555                                                // Do we have this translation available, in the
556                                                // target translation?
557                                                String translation = propTargetTranslation
558                                                                .getProperty(key);
559                                                if (translation == null) {
560                                                        osrTargetFile.write("#MISSING  " + line);
561                                                } else {
562                                                        // We want to keep special characters.
563                                                        translation = translation
564                                                                        .replaceAll("\\n", "\\\\n");
565                                                        osrTargetFile.write(key + " = " + translation);
566                                                }
567                                        } else {
568                                                // Hum, hum. The line is not one of the expected
569                                                // cases...
570                                                throw new MojoExecutionException(
571                                                                "Error while processing the "
572                                                                                + defaultFile.getAbsolutePath()
573                                                                                + " translation. The format of this line "
574                                                                                + lineNumber + " is not recognized: ["
575                                                                                + line + "]");
576                                        }
577                                        // Let's write the current char (an EOL character)
578                                        if (!eof) {
579                                                osrTargetFile.write(c);
580                                        }
581                                }
582                        }// while
583
584                        // The streams are closed in the finally clause.
585                } catch (IOException e) {
586                        throw new MojoExecutionException(e.getClass().getName()
587                                        + " while writing this lang file: "
588                                        + targetFile.getAbsolutePath() + " (" + e.getMessage()
589                                        + ")");
590                } finally {
591                        closeStream(pbReader, "PushBackReader");
592                        closeStream(isDefaultFile, "isrDefaultFile");
593                        closeStream(isDefaultFile, defaultFile.getAbsolutePath());
594                        closeStream(osrTargetFile, targetFile.getAbsolutePath());
595                        closeStream(osTargetFile, targetFile.getAbsolutePath());
596                }
597
598                // TODO Finish this method
599        }
600
601        /**
602         * A utility method, to close any object which implements the Closeable
603         * interface (InputStream, OutputStream, Reader...), without throwing any
604         * Exception. To be used in the finally clauses of try/catch blocs.
605         *
606         * @param closeable
607         *            The object to close. If null, nothing is done. This allows
608         *            this method to be called whether the Stream (or Writer) has
609         *            been created or not.
610         * @param description
611         *            A description of the object to close. This description will be
612         *            logged if an error occured while closing this object.
613         */
614        void closeStream(Closeable closeable, String description) {
615                if (closeable != null) {
616                        try {
617                                closeable.close();
618                        } catch (IOException e) {
619                                getLog().warn(
620                                                e.getClass().getName() + " while closing the "
621                                                                + closeable.getClass().getName() + " ("
622                                                                + description + ") [" + e.getMessage() + "]");
623                        }
624                }
625        }
626
627        /**
628         * Transform a String, into a unicode encoded String, with characters not in
629         * the ASCII chart, transformed in "\\uNNNN" string.
630         *
631         * @param src
632         * @return
633         */
634        String toUnicode(String src) {
635                if (src == null) {
636                        return null;
637                } else {
638                        StringBuilder sb = new StringBuilder(src.length() * 6);
639
640                        // Char by char conversion
641                        for (int i = 0; i < src.length(); i++) {
642                                int c = (int) src.charAt(i);
643
644                                // We keep ASCII characters as is
645                                if (c <= 127) {
646                                        // No transformation for ASCII characters
647                                        sb.append((char) c);
648                                } else {
649                                        // Generates the \\uNNNN string
650                                        sb.append(String.format("\\u%04x", c));
651                                }
652                        }
653
654                        return sb.toString();
655                }
656        }
657
658        /**
659         * Copy a file, byte per byte, into a new one.
660         *
661         * @param source
662         * @param target
663         * @throws IOException
664         */
665        void copyFile(File source, String sourceEncoding, File target,
666                        String targetEncoding) throws IOException {
667                InputStream is = new FileInputStream(source);
668                byte[] bytes = new byte[(int) source.length()];
669                is.read(bytes);
670                String template = new String(bytes, sourceEncoding);
671                OutputStream os = new FileOutputStream(target);
672                if (targetEncoding.equals(NATIVE_ENCODING)) {
673                        os.write(toUnicode(template).getBytes("US-ASCII"));
674                } else {
675                        os.write(template.getBytes(targetEncoding));
676                }
677                closeStream(is, "copyFile, inputStream: " + source.getAbsolutePath());
678                closeStream(os, "copyFile, outputStream: " + target.getAbsolutePath());
679        }
680
681        /**
682         * This method copies all files to the lang folder, once they have all been
683         * transformed successfully.
684         *
685         * @throws MojoExecutionException
686         */
687        void copyAllLangFilesToInputFolder() throws MojoExecutionException {
688                File[] workFiles = getWorkFolder().listFiles(noSvnFilenameFilter);
689                File target;
690                for (int i = 0; i < workFiles.length; i += 1) {
691                        target = new File(getInputFolder(), workFiles[i].getName());
692                        try {
693                                copyFile(workFiles[i], inputEncoding, target, inputEncoding);
694                        } catch (IOException e) {
695                                throw new MojoExecutionException(e.getClass().getName()
696                                                + " while copying this lang file: "
697                                                + workFiles[i].getName() + " from "
698                                                + getWorkFolder().getAbsolutePath() + " to "
699                                                + getInputFolder().getAbsolutePath() + " ("
700                                                + e.getMessage() + ")");
701                        }
702                }
703        }
704
705        /**
706         * This method copies all files to the ./src/main/resources/lang/ folder,
707         * once they have all been transformed successfully. During this copy, the
708         * files are encoded in ASCII characters. All non ASCII characters are
709         * replaced by the relevant \\uNNNN string.
710         *
711         * @throws MojoExecutionException
712         */
713        void copyAllLangFilesToResourcesLangFolder() throws MojoExecutionException {
714                // First step : copy of the lang.properties files
715                try {
716                        copyFile(new File(getInputFolder(), "lang.properties"),
717                                        inputEncoding, new File(getResourceLangFolder(),
718                                                        "lang.properties"), NATIVE_ENCODING);
719                } catch (IOException e1) {
720                        throw new MojoExecutionException(e1.getClass().getName()
721                                        + " while copying this lang.properties file: from "
722                                        + getInputFolder().getAbsolutePath() + " to "
723                                        + getResourceLangFolder().getAbsolutePath() + " ("
724                                        + e1.getMessage() + ")");
725                }
726
727                // Then, copy of all the 'unicoded' files.
728                File[] files = getWorkFolder().listFiles(noSvnFilenameFilter);
729                File target = resourceLangFolder;
730                for (int i = 0; i < files.length; i += 1) {
731                        target = new File(getResourceLangFolder(), files[i].getName());
732                        try {
733                                copyFile(files[i], inputEncoding, target, NATIVE_ENCODING);
734                        } catch (IOException e2) {
735                                throw new MojoExecutionException(e2.getClass().getName()
736                                                + " while copying this lang file: "
737                                                + files[i].getName() + " from "
738                                                + getWorkFolder().getAbsolutePath() + " to "
739                                                + getInputFolder().getAbsolutePath() + " ("
740                                                + e2.getMessage() + ")");
741                        }
742                }
743        }
744
745        /**
746         * Generates the availableTranslation, then one file for each translation,
747         * based on the templateAvailableTranslation and templateOneTranslation
748         * plugin parameters.
749         *
750         * @throws MojoExecutionException
751         */
752        public void generateDocForAvailableTranslations()
753                        throws MojoExecutionException {
754                String action = null;
755                PrintWriter pwAvailableTranslation = null;
756
757                try {
758                        action = "Init of file writer";
759                        File fileAvailableTranslation = new File(getDocFolder(),
760                                        "available_translations" + getDocFileExtension());
761                        action = "Opening Stream for "
762                                        + fileAvailableTranslation.getAbsolutePath();
763                        pwAvailableTranslation = new PrintWriter(fileAvailableTranslation,
764                                        "UTF-8");
765
766                        action = "Velocity: template creation";
767                        Template template = velocityEngine
768                                        .getTemplate(getTemplateAvailableTranslation());
769                        action = "Velocity: context creation";
770                        VelocityContext context = new VelocityContext();
771
772                        // Then, manage each translation (based on the properly unicoded
773                        // files we've just generated)
774                        File[] files = getResourceLangFolder().listFiles(
775                                        noSvnFilenameFilter);
776                        List<Map<String, String>> translations = new ArrayList<Map<String, String>>();
777                        for (int i = 0; i < files.length; i += 1) {
778                                // Let's read these properties.
779                                Properties props = new Properties();
780                                action = "Reading the properties for "
781                                                + files[i].getAbsolutePath();
782                                props.load(new FileInputStream(files[i]));
783
784                                // Adding properties for Velocity template.
785                                Map<String, String> translation = new HashMap<String, String>();
786                                translation.put("filename", files[i].getName());
787                                translation.put("language", props.getProperty("language"));
788                                translation
789                                                .put("contributor", props.getProperty("contributor"));
790                                translations.add(translation);
791
792                                // Let's generate the file
793                                action = "Generating "
794                                                + fileAvailableTranslation.getAbsolutePath();
795                                generateDocForOneTranslation(files[i], props);
796                        }// for
797
798                        // Ok, we fullfill all data for available translations. Let's
799                        // generate the page.
800                        context.put("translations", translations);
801                        template.merge(context, pwAvailableTranslation);
802                } catch (MojoExecutionException e) {
803                        throw e;
804                } catch (Exception e) {
805                        throw new MojoExecutionException(e.getClass().getName()
806                                        + " while: " + action + " (" + e.getMessage() + ")");
807                } finally {
808                        closeStream(pwAvailableTranslation,
809                                        "printwriterAvailableTranslation");
810                }
811        }
812
813        VelocityEngine initVelocity() throws Exception {
814                final String LOGGER_NAME = "velocityLogger";
815
816                // TODO Remove these three test lines.
817                BasicConfigurator.configure();
818
819                velocityEngine = new VelocityEngine();
820                velocityEngine.setProperty(
821                                RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
822                                "org.apache.velocity.runtime.log.Log4JLogChute");
823                velocityEngine.setProperty("runtime.log.logsystem.log4j.logger",
824                                LOGGER_NAME);
825                velocityEngine.init();
826
827                return velocityEngine;
828        }
829
830        /**
831         * @throws MojoExecutionException
832         */
833        void generateDocForOneTranslation(File file, Properties props)
834                        throws MojoExecutionException {
835                InputStream isApplet = null;
836                InputStreamReader isrApplet = null;
837                InputStream isCoppermine = null;
838                InputStreamReader isrCoppermine = null;
839                OutputStream os = null;
840                OutputStreamWriter osw = null;
841                PrintWriter pw = null;
842                String action = null;
843
844                try {
845                        // Now the target file
846                        File target = new File(getDocFolder(), file.getName()
847                                        + getDocFileExtension());
848                        os = new FileOutputStream(target);
849                        // FIXME This should generate unicode escape strings (like \\uNNNN)
850                        osw = new OutputStreamWriter(os, "UTF-8");
851                        pw = new PrintWriter(osw);
852
853                        action = "Velocity: template creation";
854                        Template template = velocityEngine
855                                        .getTemplate(getTemplateOneTranslation());
856                        action = "Velocity: context creation";
857                        VelocityContext context = new VelocityContext();
858
859                        // Let's open the applet input file, with the correct encoding
860                        // charset.
861                        action = "Reading applet translation";
862                        File fileAppletTranslation = new File(getInputFolder(), file
863                                        .getName());
864                        isApplet = new FileInputStream(fileAppletTranslation);
865                        isrApplet = new InputStreamReader(isApplet, getInputEncoding());
866                        StringWriter appletTranslationWriter = new StringWriter(
867                                        (int) fileAppletTranslation.length());
868                        // Let's read the applet translation, by translating the encoding,
869                        // if necessary. This translation is done by the Reader and Writer
870                        // previously opened.
871                        int c;
872                        while ((c = isrApplet.read()) != -1) {
873                                appletTranslationWriter.write(c);
874                        }
875                        context.put("language", props.get("language"));
876                        context.put("applet_translation", appletTranslationWriter
877                                        .toString());
878
879                        // Should we have a Coppermine translation ?
880                        if (getCoppermineFolder() != null) {
881                                // Let's open the Coppermine input file, with the correct
882                                // encoding charset.
883                                String filename = props.getProperty("coppermine.language")
884                                                .toLowerCase()
885                                                + ".php";
886                                File fileCoppermineTranslation = new File(
887                                                getCoppermineFolder(), filename);
888                                if (!fileCoppermineTranslation.exists()) {
889                                        // The translation doesn't exist. We'll display the default
890                                        // language file, to allow contributor to translate.
891                                        fileCoppermineTranslation = new File(getCoppermineFolder(),
892                                                        "english.php");
893                                }
894
895                                // Let's copy the Coppermine translation, by translating the
896                                // encoding, if necessary. This translation is done by the
897                                // Reader and Writer previously opened.
898                                isCoppermine = new FileInputStream(fileCoppermineTranslation);
899                                isrCoppermine = new InputStreamReader(isCoppermine, "UTF-8");
900                                StringWriter coppermineTranslationWriter = new StringWriter(
901                                                (int) fileAppletTranslation.length());
902                                while ((c = isrCoppermine.read()) != -1) {
903                                        coppermineTranslationWriter.write((char) c);
904                                }
905
906                                context.put("coppermine_filename", fileCoppermineTranslation
907                                                .getName());
908                                context.put("coppermine_translation",
909                                                coppermineTranslationWriter.toString());
910                        }// if(getCoppermineFolder()!=null)
911
912                        template.merge(context, pw);
913                } catch (Exception e) {
914                        throw new MojoExecutionException(e.getClass().getName()
915                                        + " while generating file for " + file.getAbsolutePath()
916                                        + " (" + e.getMessage() + ") [action = " + action + "]");
917                } finally {
918                        closeStream(isApplet, "isApplet");
919                        closeStream(isrApplet, "isrApplet");
920                        closeStream(isCoppermine, "isCoppermine");
921                        closeStream(isrCoppermine, "isrCoppermine");
922                        closeStream(pw, "pw");
923                        closeStream(osw, "osw");
924                        closeStream(os, "os");
925                }
926        }
927
928        /**
929         * @return the docFolder
930         */
931        public File getDocFolder() {
932                return docFolder;
933        }
934
935        /**
936         * @param docFolder
937         *            the docFolder to set. docFolder may be null.
938         * @throws MojoExecutionException
939         */
940        public void setDocFolder(File docFolder) throws MojoExecutionException {
941                // It seems like maven set null ... not to the File itself, but to its
942                // name. That is: the folder would be: path/null !
943                if (docFolder != null && docFolder.getName().equals("null")) {
944                        docFolder = null;
945                }
946                if (docFolder != null) {
947                        if (!docFolder.exists()) {
948                                // We accept target folder which doesn't exist, only if they are
949                                // in the target path.
950                                if (docFolder.getAbsolutePath().contains("/target/")
951                                                || docFolder.getAbsolutePath().contains("\\target\\")) {
952                                        // Ok, let's create if.
953                                        docFolder.mkdir();
954                                } else {
955                                        throw new MojoExecutionException("docFolder: "
956                                                        + docFolder.getAbsolutePath() + " must exist");
957                                }
958                        } else if (!docFolder.isDirectory()) {
959                                // docFolder exists, but is not a folder. Forbidden.
960                                throw new MojoExecutionException("docFolder: "
961                                                + docFolder.getAbsolutePath()
962                                                + " exists, but is not a folder");
963                        }
964                }
965                this.docFolder = docFolder;
966        }
967
968        /**
969         * @return the coppermineFolder
970         */
971        public File getCoppermineFolder() {
972                return coppermineFolder;
973        }
974
975        /**
976         * @param coppermineFolder
977         *            the coppermineFolder to set. coppermineFolder may be null. In
978         *            this case, the Coppermine files are ignored.
979         * @throws MojoExecutionException
980         */
981        public void setCoppermineFolder(File coppermineFolder)
982                        throws MojoExecutionException {
983                // It seems like maven set null ... not to the File itself, but to its
984                // name. That is: the folder would be: path/null !
985                if (coppermineFolder != null
986                                && coppermineFolder.getName().equals("null")) {
987                        coppermineFolder = null;
988                }
989                if (coppermineFolder != null) {
990                        if (!coppermineFolder.exists()) {
991                                throw new MojoExecutionException("coppermineFolder: "
992                                                + coppermineFolder.getAbsolutePath() + " must exist");
993                        } else if (!coppermineFolder.isDirectory()) {
994                                // coppermineFolder exists, but is not a folder. Forbidden.
995                                throw new MojoExecutionException("coppermineFolder: "
996                                                + coppermineFolder.getAbsolutePath()
997                                                + " exists, but is not a folder");
998                        }
999                }
1000                this.coppermineFolder = coppermineFolder;
1001        }
1002
1003        /**
1004         * @return the docFileExtension
1005         */
1006        public String getDocFileExtension() {
1007                return (docFileExtension == null) ? "" : docFileExtension;
1008        }
1009
1010        /**
1011         * @param docFileExtension
1012         *            the docFileExtension to set
1013         */
1014        public void setDocFileExtension(String docFileExtension) {
1015                this.docFileExtension = docFileExtension;
1016        }
1017
1018        /**
1019         * @return the inputEncoding
1020         */
1021        public String getInputEncoding() {
1022                return inputEncoding;
1023        }
1024
1025        /**
1026         * @param inputEncoding
1027         *            the inputEncoding to set
1028         * @throws MojoExecutionException
1029         */
1030        public void setInputEncoding(String inputEncoding)
1031                        throws MojoExecutionException {
1032                if (inputEncoding == null) {
1033                        throw new NullPointerException("inputEncoding may noy be null");
1034                } else if (!inputEncoding.equals("UTF-8")
1035                                && !inputEncoding.equals("UTF-16")) {
1036                        throw new MojoExecutionException(
1037                                        "Valid values for inputEncoding are: UTF-8 and UTF-16");
1038                }
1039                this.inputEncoding = inputEncoding;
1040        }
1041
1042        /**
1043         * @param inputFolder
1044         *            the inputFolder to set
1045         * @throws NullPointerException
1046         *             When inputFolder is null
1047         * @throws MojoExecutionException
1048         *             When inputFolder is not a folder.
1049         */
1050        public void setInputFolder(File inputFolder) throws MojoExecutionException {
1051                if (inputFolder == null) {
1052                        throw new NullPointerException("inputFolder may not be set to null");
1053                } else if (!inputFolder.isDirectory()) {
1054                        throw new MojoExecutionException("inputFolder: "
1055                                        + inputFolder.getAbsolutePath() + " must be a directory");
1056                }
1057
1058                this.inputFolder = inputFolder;
1059
1060                // The default file must be set, according to this folder.
1061                defaultFile = new File(inputFolder + File.separator + FILENAME_PREFIX
1062                                + FILENAME_SUFFIX);
1063        }
1064
1065        /**
1066         * @return the inputFolder
1067         */
1068        public File getInputFolder() {
1069                return inputFolder;
1070        }
1071
1072        /**
1073         * @return the resourceLangFolder
1074         */
1075        public File getResourceLangFolder() {
1076                return resourceLangFolder;
1077        }
1078
1079        /**
1080         * @param resourceLangFolder
1081         *            the resourceLangFolder to set. If the folder dosn't exist, it
1082         *            will be created.
1083         * @throws NullPointerException
1084         *             When resourceLangFolder is null
1085         * @throws MojoExecutionException
1086         *             When resourceLangFolder exists, and is not a folder.
1087         */
1088        public void setResourceLangFolder(File resourceLangFolder)
1089                        throws MojoExecutionException {
1090                // resourceLangFolder must be ... a folder!
1091                if (resourceLangFolder == null) {
1092                        throw new NullPointerException("resourceLangFolder may not ne null");
1093                } else if (!resourceLangFolder.isDirectory()) {
1094                        throw new MojoExecutionException("resourceLangFolder: "
1095                                        + resourceLangFolder.getAbsolutePath()
1096                                        + " must be a directory");
1097                }
1098
1099                // Ok !
1100                this.resourceLangFolder = resourceLangFolder;
1101        }
1102
1103        /**
1104         * @return the templateAvailableTranslation
1105         */
1106        public String getTemplateAvailableTranslation() {
1107                return templateAvailableTranslation;
1108        }
1109
1110        /**
1111         * @param templateAvailableTranslation
1112         *            the templateAvailableTranslation to set
1113         * @throws MojoExecutionException
1114         */
1115        public void setTemplateAvailableTranslation(
1116                        String templateAvailableTranslation) throws MojoExecutionException {
1117                if (templateAvailableTranslation != null) {
1118                        // Let's check if the file exist.
1119                        File currFolder = new File(".");
1120                        File f = new File(currFolder, templateAvailableTranslation);
1121                        if (!f.isFile()) {
1122                                throw new MojoExecutionException(
1123                                                "templateAvailableTranslation: "
1124                                                                + templateAvailableTranslation
1125                                                                + " must be a valid file");
1126                        }
1127                }
1128                this.templateAvailableTranslation = templateAvailableTranslation;
1129        }
1130
1131        /**
1132         * @return the templateOneTranslation
1133         */
1134        public String getTemplateOneTranslation() {
1135                return templateOneTranslation;
1136        }
1137
1138        /**
1139         * @param templateOneTranslation
1140         *            the templateOneTranslation to set
1141         * @throws MojoExecutionException
1142         */
1143        public void setTemplateOneTranslation(String templateOneTranslation)
1144                        throws MojoExecutionException {
1145                if (templateOneTranslation != null) {
1146                        // Let's check if the file exist.
1147                        File currFolder = new File(".");
1148                        File f = new File(currFolder, templateOneTranslation);
1149                        if (!f.isFile()) {
1150                                throw new MojoExecutionException("templateOneTranslation: "
1151                                                + templateOneTranslation + " must be a valid file");
1152                        }
1153                }
1154                this.templateOneTranslation = templateOneTranslation;
1155        }
1156
1157        /**
1158         * The work folder may not be null, and should be in the target folder. If
1159         * it doesn't exist, we create it.
1160         *
1161         * @param workFolder
1162         *            the workFolder to set
1163         * @throws NullPointerException
1164         *             When workFolder is null
1165         * @throws MojoExecutionException
1166         *             When workFolder exists, and is not a folder.
1167         */
1168        public void setWorkFolder(File workFolder) throws MojoExecutionException {
1169                if (workFolder == null) {
1170                        throw new MojoExecutionException(
1171                                        "workFolder may not be set to null (" + workFolder + ")");
1172                }
1173
1174                // If this folder doesn't exist, we create it.
1175                if (!workFolder.exists()) {
1176                        workFolder.mkdirs();
1177                }
1178                // It must be a folder (it may exist before as a file...)
1179                if (!workFolder.isDirectory()) {
1180                        throw new MojoExecutionException(
1181                                        "The workFolder must be a folder (" + workFolder + ")");
1182                }
1183
1184                this.workFolder = workFolder;
1185
1186                // The work folder must be empty, before working, or we may get old file
1187                // in the execute process.
1188                emptyFolder(this.workFolder);
1189        }
1190
1191        /**
1192         * @return the workFolder
1193         */
1194        public File getWorkFolder() {
1195                return workFolder;
1196        }
1197
1198        /*******************************************************************************************
1199         ************************** UTILITIES METHODS **********************************************
1200         *******************************************************************************************/
1201        /**
1202         * Remove all files in a given folder. The folder remains.
1203         *
1204         * @param folder
1205         */
1206        public static void emptyFolder(File folder) {
1207                // Let's throw a violent exception if folder is null or is not a folder.
1208                File[] files = folder.listFiles(noSvnFilenameFilter);
1209                for (int i = 0; i < files.length; i += 1) {
1210                        files[i].delete();
1211                }
1212        }
1213}
Note: See TracBrowser for help on using the repository browser.