KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > taskdefs > optional > i18n > Translate


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18 package org.apache.tools.ant.taskdefs.optional.i18n;
19
20 import java.io.BufferedReader JavaDoc;
21 import java.io.BufferedWriter JavaDoc;
22 import java.io.File JavaDoc;
23 import java.io.FileInputStream JavaDoc;
24 import java.io.FileOutputStream JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.InputStreamReader JavaDoc;
27 import java.io.OutputStreamWriter JavaDoc;
28 import java.util.Hashtable JavaDoc;
29 import java.util.Locale JavaDoc;
30 import java.util.Vector JavaDoc;
31 import org.apache.tools.ant.BuildException;
32 import org.apache.tools.ant.DirectoryScanner;
33 import org.apache.tools.ant.Project;
34 import org.apache.tools.ant.taskdefs.MatchingTask;
35 import org.apache.tools.ant.types.FileSet;
36 import org.apache.tools.ant.util.FileUtils;
37 import org.apache.tools.ant.util.LineTokenizer;
38
39 /**
40  * Translates text embedded in files using Resource Bundle files.
41  * Since ant 1.6 preserves line endings
42  *
43  */

44 public class Translate extends MatchingTask {
45     /**
46      * search a bundle matching the specified language, the country and the variant
47      */

48     private static final int BUNDLE_SPECIFIED_LANGUAGE_COUNTRY_VARIANT = 0;
49     /**
50      * search a bundle matching the specified language, and the country
51      */

52     private static final int BUNDLE_SPECIFIED_LANGUAGE_COUNTRY = 1;
53     /**
54      * search a bundle matching the specified language only
55      */

56     private static final int BUNDLE_SPECIFIED_LANGUAGE = 2;
57     /**
58      * search a bundle matching nothing special
59      */

60     private static final int BUNDLE_NOMATCH = 3;
61     /**
62      * search a bundle matching the language, the country and the variant
63      * of the current locale of the computer
64      */

65     private static final int BUNDLE_DEFAULT_LANGUAGE_COUNTRY_VARIANT = 4;
66     /**
67      * search a bundle matching the language, and the country
68      * of the current locale of the computer
69      */

70     private static final int BUNDLE_DEFAULT_LANGUAGE_COUNTRY = 5;
71     /**
72      * search a bundle matching the language only
73      * of the current locale of the computer
74      */

75     private static final int BUNDLE_DEFAULT_LANGUAGE = 6;
76     /**
77      * number of possibilities for the search
78      */

79      private static final int BUNDLE_MAX_ALTERNATIVES = BUNDLE_DEFAULT_LANGUAGE + 1;
80     /**
81      * Family name of resource bundle
82      */

83     private String JavaDoc bundle;
84
85     /**
86      * Locale specific language of the resource bundle
87      */

88     private String JavaDoc bundleLanguage;
89
90     /**
91      * Locale specific country of the resource bundle
92      */

93     private String JavaDoc bundleCountry;
94
95     /**
96      * Locale specific variant of the resource bundle
97      */

98     private String JavaDoc bundleVariant;
99
100     /**
101      * Destination directory
102      */

103     private File JavaDoc toDir;
104
105     /**
106      * Source file encoding scheme
107      */

108     private String JavaDoc srcEncoding;
109
110     /**
111      * Destination file encoding scheme
112      */

113     private String JavaDoc destEncoding;
114
115     /**
116      * Resource Bundle file encoding scheme, defaults to srcEncoding
117      */

118     private String JavaDoc bundleEncoding;
119
120     /**
121      * Starting token to identify keys
122      */

123     private String JavaDoc startToken;
124
125     /**
126      * Ending token to identify keys
127      */

128     private String JavaDoc endToken;
129
130     /**
131      * Whether or not to create a new destination file.
132      * Defaults to <code>false</code>.
133      */

134     private boolean forceOverwrite;
135
136     /**
137      * Vector to hold source file sets.
138      */

139     private Vector JavaDoc filesets = new Vector JavaDoc();
140
141     /**
142      * Holds key value pairs loaded from resource bundle file
143      */

144     private Hashtable JavaDoc resourceMap = new Hashtable JavaDoc();
145     /**
146
147      * Used to resolve file names.
148      */

149     private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
150
151     /**
152      * Last Modified Timestamp of resource bundle file being used.
153      */

154     private long[] bundleLastModified = new long[BUNDLE_MAX_ALTERNATIVES];
155
156     /**
157      * Last Modified Timestamp of source file being used.
158      */

159     private long srcLastModified;
160
161     /**
162      * Last Modified Timestamp of destination file being used.
163      */

164     private long destLastModified;
165
166     /**
167      * Has at least one file from the bundle been loaded?
168      */

169     private boolean loaded = false;
170
171     /**
172      * Sets Family name of resource bundle; required.
173      * @param bundle family name of resource bundle
174      */

175     public void setBundle(String JavaDoc bundle) {
176         this.bundle = bundle;
177     }
178
179     /**
180      * Sets locale specific language of resource bundle; optional.
181      * @param bundleLanguage langage of the bundle
182      */

183     public void setBundleLanguage(String JavaDoc bundleLanguage) {
184         this.bundleLanguage = bundleLanguage;
185     }
186
187     /**
188      * Sets locale specific country of resource bundle; optional.
189      * @param bundleCountry country of the bundle
190      */

191     public void setBundleCountry(String JavaDoc bundleCountry) {
192         this.bundleCountry = bundleCountry;
193     }
194
195     /**
196      * Sets locale specific variant of resource bundle; optional.
197      * @param bundleVariant locale variant of resource bundle
198      */

199     public void setBundleVariant(String JavaDoc bundleVariant) {
200         this.bundleVariant = bundleVariant;
201     }
202
203     /**
204      * Sets Destination directory; required.
205      * @param toDir destination directory
206      */

207     public void setToDir(File JavaDoc toDir) {
208         this.toDir = toDir;
209     }
210
211     /**
212      * Sets starting token to identify keys; required.
213      * @param startToken starting token to identify keys
214      */

215     public void setStartToken(String JavaDoc startToken) {
216         this.startToken = startToken;
217     }
218
219     /**
220      * Sets ending token to identify keys; required.
221      * @param endToken ending token to identify keys
222      */

223     public void setEndToken(String JavaDoc endToken) {
224         this.endToken = endToken;
225     }
226
227     /**
228      * Sets source file encoding scheme; optional,
229      * defaults to encoding of local system.
230      * @param srcEncoding source file encoding
231      */

232     public void setSrcEncoding(String JavaDoc srcEncoding) {
233         this.srcEncoding = srcEncoding;
234     }
235
236     /**
237      * Sets destination file encoding scheme; optional. Defaults to source file
238      * encoding
239      * @param destEncoding destination file encoding scheme
240      */

241     public void setDestEncoding(String JavaDoc destEncoding) {
242         this.destEncoding = destEncoding;
243     }
244
245     /**
246      * Sets Resource Bundle file encoding scheme; optional. Defaults to source file
247      * encoding
248      * @param bundleEncoding bundle file encoding scheme
249      */

250     public void setBundleEncoding(String JavaDoc bundleEncoding) {
251         this.bundleEncoding = bundleEncoding;
252     }
253
254     /**
255      * Whether or not to overwrite existing file irrespective of
256      * whether it is newer than the source file as well as the
257      * resource bundle file.
258      * Defaults to false.
259      * @param forceOverwrite whether or not to overwrite existing files
260      */

261     public void setForceOverwrite(boolean forceOverwrite) {
262         this.forceOverwrite = forceOverwrite;
263     }
264
265     /**
266      * Adds a set of files to translate as a nested fileset element.
267      * @param set the fileset to be added
268      */

269     public void addFileset(FileSet set) {
270         filesets.addElement(set);
271     }
272
273     /**
274      * Check attributes values, load resource map and translate
275      * @throws BuildException if the required attributes are not set
276      * Required : <ul>
277      * <li>bundle</li>
278      * <li>starttoken</li>
279      * <li>endtoken</li>
280      * </ul>
281      */

282     public void execute() throws BuildException {
283         if (bundle == null) {
284             throw new BuildException("The bundle attribute must be set.",
285                                      getLocation());
286         }
287
288         if (startToken == null) {
289             throw new BuildException("The starttoken attribute must be set.",
290                                      getLocation());
291         }
292
293         if (endToken == null) {
294             throw new BuildException("The endtoken attribute must be set.",
295                                      getLocation());
296         }
297
298         if (bundleLanguage == null) {
299             Locale JavaDoc l = Locale.getDefault();
300             bundleLanguage = l.getLanguage();
301         }
302
303         if (bundleCountry == null) {
304             bundleCountry = Locale.getDefault().getCountry();
305         }
306
307         if (bundleVariant == null) {
308             Locale JavaDoc l = new Locale JavaDoc(bundleLanguage, bundleCountry);
309             bundleVariant = l.getVariant();
310         }
311
312         if (toDir == null) {
313             throw new BuildException("The todir attribute must be set.",
314                                      getLocation());
315         }
316
317         if (!toDir.exists()) {
318             toDir.mkdirs();
319         } else if (toDir.isFile()) {
320             throw new BuildException(toDir + " is not a directory");
321         }
322
323         if (srcEncoding == null) {
324             srcEncoding = System.getProperty("file.encoding");
325         }
326
327         if (destEncoding == null) {
328             destEncoding = srcEncoding;
329         }
330
331         if (bundleEncoding == null) {
332             bundleEncoding = srcEncoding;
333         }
334
335         loadResourceMaps();
336
337         translate();
338     }
339
340     /**
341      * Load resource maps based on resource bundle encoding scheme.
342      * The resource bundle lookup searches for resource files with various
343      * suffixes on the basis of (1) the desired locale and (2) the default
344      * locale (basebundlename), in the following order from lower-level
345      * (more specific) to parent-level (less specific):
346      *
347      * basebundlename + "_" + language1 + "_" + country1 + "_" + variant1
348      * basebundlename + "_" + language1 + "_" + country1
349      * basebundlename + "_" + language1
350      * basebundlename
351      * basebundlename + "_" + language2 + "_" + country2 + "_" + variant2
352      * basebundlename + "_" + language2 + "_" + country2
353      * basebundlename + "_" + language2
354      *
355      * To the generated name, a ".properties" string is appeneded and
356      * once this file is located, it is treated just like a properties file
357      * but with bundle encoding also considered while loading.
358      */

359     private void loadResourceMaps() throws BuildException {
360         Locale JavaDoc locale = new Locale JavaDoc(bundleLanguage,
361                                    bundleCountry,
362                                    bundleVariant);
363         String JavaDoc language = locale.getLanguage().length() > 0
364             ? "_" + locale.getLanguage() : "";
365         String JavaDoc country = locale.getCountry().length() > 0
366             ? "_" + locale.getCountry() : "";
367         String JavaDoc variant = locale.getVariant().length() > 0
368             ? "_" + locale.getVariant() : "";
369         String JavaDoc bundleFile = bundle + language + country + variant;
370         processBundle(bundleFile, BUNDLE_SPECIFIED_LANGUAGE_COUNTRY_VARIANT, false);
371
372         bundleFile = bundle + language + country;
373         processBundle(bundleFile, BUNDLE_SPECIFIED_LANGUAGE_COUNTRY, false);
374
375         bundleFile = bundle + language;
376         processBundle(bundleFile, BUNDLE_SPECIFIED_LANGUAGE, false);
377
378         bundleFile = bundle;
379         processBundle(bundleFile, BUNDLE_NOMATCH, false);
380
381         //Load default locale bundle files
382
//using default file encoding scheme.
383
locale = Locale.getDefault();
384
385         language = locale.getLanguage().length() > 0
386             ? "_" + locale.getLanguage() : "";
387         country = locale.getCountry().length() > 0
388             ? "_" + locale.getCountry() : "";
389         variant = locale.getVariant().length() > 0
390             ? "_" + locale.getVariant() : "";
391         bundleEncoding = System.getProperty("file.encoding");
392
393         bundleFile = bundle + language + country + variant;
394         processBundle(bundleFile, BUNDLE_DEFAULT_LANGUAGE_COUNTRY_VARIANT, false);
395
396         bundleFile = bundle + language + country;
397         processBundle(bundleFile, BUNDLE_DEFAULT_LANGUAGE_COUNTRY, false);
398
399         bundleFile = bundle + language;
400         processBundle(bundleFile, BUNDLE_DEFAULT_LANGUAGE, true);
401     }
402
403     /**
404      * Process each file that makes up this bundle.
405      */

406     private void processBundle(final String JavaDoc bundleFile, final int i,
407                                final boolean checkLoaded) throws BuildException {
408         final File JavaDoc propsFile = getProject().resolveFile(bundleFile + ".properties");
409         FileInputStream JavaDoc ins = null;
410         try {
411             ins = new FileInputStream JavaDoc(propsFile);
412             loaded = true;
413             bundleLastModified[i] = propsFile.lastModified();
414             log("Using " + propsFile, Project.MSG_DEBUG);
415             loadResourceMap(ins);
416         } catch (IOException JavaDoc ioe) {
417             log(propsFile + " not found.", Project.MSG_DEBUG);
418             //if all resource files associated with this bundle
419
//have been scanned for and still not able to
420
//find a single resrouce file, throw exception
421
if (!loaded && checkLoaded) {
422                 throw new BuildException(ioe.getMessage(), getLocation());
423             }
424         }
425     }
426
427     /**
428      * Load resourceMap with key value pairs. Values of existing keys
429      * are not overwritten. Bundle's encoding scheme is used.
430      */

431     private void loadResourceMap(FileInputStream JavaDoc ins) throws BuildException {
432         try {
433             BufferedReader JavaDoc in = null;
434             InputStreamReader JavaDoc isr = new InputStreamReader JavaDoc(ins, bundleEncoding);
435             in = new BufferedReader JavaDoc(isr);
436             String JavaDoc line = null;
437             while ((line = in.readLine()) != null) {
438                 //So long as the line isn't empty and isn't a comment...
439
if (line.trim().length() > 1 && '#' != line.charAt(0) && '!' != line.charAt(0)) {
440                     //Legal Key-Value separators are :, = and white space.
441
int sepIndex = line.indexOf('=');
442                     if (-1 == sepIndex) {
443                         sepIndex = line.indexOf(':');
444                     }
445                     if (-1 == sepIndex) {
446                         for (int k = 0; k < line.length(); k++) {
447                             if (Character.isSpaceChar(line.charAt(k))) {
448                                 sepIndex = k;
449                                 break;
450                             }
451                         }
452                     }
453                     //Only if we do have a key is there going to be a value
454
if (-1 != sepIndex) {
455                         String JavaDoc key = line.substring(0, sepIndex).trim();
456                         String JavaDoc value = line.substring(sepIndex + 1).trim();
457                         //Handle line continuations, if any
458
while (value.endsWith("\\")) {
459                             value = value.substring(0, value.length() - 1);
460                             if ((line = in.readLine()) != null) {
461                                 value = value + line.trim();
462                             } else {
463                                 break;
464                             }
465                         }
466                         if (key.length() > 0) {
467                             //Has key already been loaded into resourceMap?
468
if (resourceMap.get(key) == null) {
469                                 resourceMap.put(key, value);
470                             }
471                         }
472                     }
473                 }
474             }
475             if (in != null) {
476                 in.close();
477             }
478         } catch (IOException JavaDoc ioe) {
479             throw new BuildException(ioe.getMessage(), getLocation());
480         }
481     }
482
483     /**
484      * Reads source file line by line using the source encoding and
485      * searches for keys that are sandwiched between the startToken
486      * and endToken. The values for these keys are looked up from
487      * the hashtable and substituted. If the hashtable doesn't
488      * contain the key, they key itself is used as the value.
489      * Detination files and directories are created as needed.
490      * The destination file is overwritten only if
491      * the forceoverwritten attribute is set to true if
492      * the source file or any associated bundle resource file is
493      * newer than the destination file.
494      */

495     private void translate() throws BuildException {
496         int filesProcessed = 0;
497         for (int i = 0; i < filesets.size(); i++) {
498             FileSet fs = (FileSet) filesets.elementAt(i);
499             DirectoryScanner ds = fs.getDirectoryScanner(getProject());
500             String JavaDoc[] srcFiles = ds.getIncludedFiles();
501             for (int j = 0; j < srcFiles.length; j++) {
502                 try {
503                     File JavaDoc dest = FILE_UTILS.resolveFile(toDir, srcFiles[j]);
504                     //Make sure parent dirs exist, else, create them.
505
try {
506                         File JavaDoc destDir = new File JavaDoc(dest.getParent());
507                         if (!destDir.exists()) {
508                             destDir.mkdirs();
509                         }
510                     } catch (Exception JavaDoc e) {
511                         log("Exception occurred while trying to check/create "
512                             + " parent directory. " + e.getMessage(),
513                             Project.MSG_DEBUG);
514                     }
515                     destLastModified = dest.lastModified();
516                     File JavaDoc src = FILE_UTILS.resolveFile(ds.getBasedir(), srcFiles[j]);
517                     srcLastModified = src.lastModified();
518                     //Check to see if dest file has to be recreated
519
boolean needsWork = forceOverwrite
520                         || destLastModified < srcLastModified;
521                     if (!needsWork) {
522                         for (int icounter = 0; icounter < BUNDLE_MAX_ALTERNATIVES; icounter++) {
523                             needsWork = (destLastModified < bundleLastModified[icounter]);
524                             if (needsWork) {
525                                 break;
526                             }
527                         }
528                     }
529                     if (needsWork) {
530                         log("Processing " + srcFiles[j],
531                             Project.MSG_DEBUG);
532                         FileOutputStream JavaDoc fos = new FileOutputStream JavaDoc(dest);
533                         BufferedWriter JavaDoc out
534                             = new BufferedWriter JavaDoc(new OutputStreamWriter JavaDoc(fos, destEncoding));
535                         FileInputStream JavaDoc fis = new FileInputStream JavaDoc(src);
536                         BufferedReader JavaDoc in
537                             = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(fis, srcEncoding));
538                         String JavaDoc line;
539                         LineTokenizer lineTokenizer = new LineTokenizer();
540                         lineTokenizer.setIncludeDelims(true);
541                         line = lineTokenizer.getToken(in);
542                         while ((line) != null) {
543                             // 2003-02-21 new replace algorithm by tbee (tbee@tbee.org)
544
// because it wasn't able to replace something like "@aaa;@bbb;"
545

546                             // is there a startToken
547
// and there is still stuff following the startToken
548
int startIndex = line.indexOf(startToken);
549                             while (startIndex >= 0
550                                 && (startIndex + startToken.length()) <= line.length()) {
551                                 // the new value, this needs to be here
552
// because it is required to calculate the next position to
553
// search from at the end of the loop
554
String JavaDoc replace = null;
555
556                                 // we found a starttoken, is there an endtoken following?
557
// start at token+tokenlength because start and end
558
// token may be indentical
559
int endIndex = line.indexOf(
560                                     endToken, startIndex + startToken.length());
561                                 if (endIndex < 0) {
562                                     startIndex += 1;
563                                 } else {
564                                     // grab the token
565
String JavaDoc token = line.substring(
566                                         startIndex + startToken.length(), endIndex);
567
568                                     // If there is a white space or = or :, then
569
// it isn't to be treated as a valid key.
570
boolean validToken = true;
571                                     for (int k = 0; k < token.length() && validToken; k++) {
572                                         char c = token.charAt(k);
573                                         if (c == ':' || c == '='
574                                             || Character.isSpaceChar(c)) {
575                                             validToken = false;
576                                         }
577                                     }
578                                     if (!validToken) {
579                                         startIndex += 1;
580                                     } else {
581                                         // find the replace string
582
if (resourceMap.containsKey(token)) {
583                                             replace = (String JavaDoc) resourceMap.get(token);
584                                         } else {
585                                             log("Replacement string missing for: "
586                                                 + token, Project.MSG_VERBOSE);
587                                             replace = startToken + token + endToken;
588                                         }
589
590
591                                         // generate the new line
592
line = line.substring(0, startIndex)
593                                              + replace
594                                              + line.substring(endIndex + endToken.length());
595
596                                         // set start position for next search
597
startIndex += replace.length();
598                                     }
599                                 }
600
601                                 // find next starttoken
602
startIndex = line.indexOf(startToken, startIndex);
603                             }
604                             out.write(line);
605                             line = lineTokenizer.getToken(in);
606                         }
607                         if (in != null) {
608                             in.close();
609                         }
610                         if (out != null) {
611                             out.close();
612                         }
613                         ++filesProcessed;
614                     } else {
615                         log("Skipping " + srcFiles[j]
616                             + " as destination file is up to date",
617                             Project.MSG_VERBOSE);
618                     }
619                 } catch (IOException JavaDoc ioe) {
620                     throw new BuildException(ioe.getMessage(), getLocation());
621                 }
622             }
623         }
624         log("Translation performed on " + filesProcessed + " file(s).", Project.MSG_DEBUG);
625     }
626 }
627
Popular Tags