source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 23.2 KB
Line 
1/*
2 * Copyright 2001-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17package org.apache.tools.ant.taskdefs.optional.i18n;
18
19import java.io.BufferedReader;
20import java.io.BufferedWriter;
21import java.io.File;
22import java.io.FileInputStream;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.InputStreamReader;
26import java.io.OutputStreamWriter;
27import java.util.Hashtable;
28import java.util.Locale;
29import java.util.Vector;
30import org.apache.tools.ant.BuildException;
31import org.apache.tools.ant.DirectoryScanner;
32import org.apache.tools.ant.Project;
33import org.apache.tools.ant.taskdefs.MatchingTask;
34import org.apache.tools.ant.types.FileSet;
35import org.apache.tools.ant.util.FileUtils;
36import org.apache.tools.ant.util.LineTokenizer;
37
38/**
39 * Translates text embedded in files using Resource Bundle files.
40 * Since ant 1.6 preserves line endings
41 *
42 */
43public class Translate extends MatchingTask {
44 /**
45 * search a bundle matching the specified language, the country and the variant
46 */
47 private static final int BUNDLE_SPECIFIED_LANGUAGE_COUNTRY_VARIANT = 0;
48 /**
49 * search a bundle matching the specified language, and the country
50 */
51 private static final int BUNDLE_SPECIFIED_LANGUAGE_COUNTRY = 1;
52 /**
53 * search a bundle matching the specified language only
54 */
55 private static final int BUNDLE_SPECIFIED_LANGUAGE = 2;
56 /**
57 * search a bundle matching nothing special
58 */
59 private static final int BUNDLE_NOMATCH = 3;
60 /**
61 * search a bundle matching the language, the country and the variant
62 * of the current locale of the computer
63 */
64 private static final int BUNDLE_DEFAULT_LANGUAGE_COUNTRY_VARIANT = 4;
65 /**
66 * search a bundle matching the language, and the country
67 * of the current locale of the computer
68 */
69 private static final int BUNDLE_DEFAULT_LANGUAGE_COUNTRY = 5;
70 /**
71 * search a bundle matching the language only
72 * of the current locale of the computer
73 */
74 private static final int BUNDLE_DEFAULT_LANGUAGE = 6;
75 /**
76 * number of possibilities for the search
77 */
78 private static final int BUNDLE_MAX_ALTERNATIVES = BUNDLE_DEFAULT_LANGUAGE + 1;
79 /**
80 * Family name of resource bundle
81 */
82 private String bundle;
83
84 /**
85 * Locale specific language of the resource bundle
86 */
87 private String bundleLanguage;
88
89 /**
90 * Locale specific country of the resource bundle
91 */
92 private String bundleCountry;
93
94 /**
95 * Locale specific variant of the resource bundle
96 */
97 private String bundleVariant;
98
99 /**
100 * Destination directory
101 */
102 private File toDir;
103
104 /**
105 * Source file encoding scheme
106 */
107 private String srcEncoding;
108
109 /**
110 * Destination file encoding scheme
111 */
112 private String destEncoding;
113
114 /**
115 * Resource Bundle file encoding scheme, defaults to srcEncoding
116 */
117 private String bundleEncoding;
118
119 /**
120 * Starting token to identify keys
121 */
122 private String startToken;
123
124 /**
125 * Ending token to identify keys
126 */
127 private String endToken;
128
129 /**
130 * Whether or not to create a new destination file.
131 * Defaults to <code>false</code>.
132 */
133 private boolean forceOverwrite;
134
135 /**
136 * Vector to hold source file sets.
137 */
138 private Vector filesets = new Vector();
139
140 /**
141 * Holds key value pairs loaded from resource bundle file
142 */
143 private Hashtable resourceMap = new Hashtable();
144 /**
145
146 * Used to resolve file names.
147 */
148 private FileUtils fileUtils = FileUtils.newFileUtils();
149
150 /**
151 * Last Modified Timestamp of resource bundle file being used.
152 */
153 private long[] bundleLastModified = new long[BUNDLE_MAX_ALTERNATIVES];
154
155 /**
156 * Last Modified Timestamp of source file being used.
157 */
158 private long srcLastModified;
159
160 /**
161 * Last Modified Timestamp of destination file being used.
162 */
163 private long destLastModified;
164
165 /**
166 * Has at least one file from the bundle been loaded?
167 */
168 private boolean loaded = false;
169
170 /**
171 * Sets Family name of resource bundle; required.
172 * @param bundle family name of resource bundle
173 */
174 public void setBundle(String bundle) {
175 this.bundle = bundle;
176 }
177
178 /**
179 * Sets locale specific language of resource bundle; optional.
180 * @param bundleLanguage langage of the bundle
181 */
182 public void setBundleLanguage(String bundleLanguage) {
183 this.bundleLanguage = bundleLanguage;
184 }
185
186 /**
187 * Sets locale specific country of resource bundle; optional.
188 * @param bundleCountry country of the bundle
189 */
190 public void setBundleCountry(String bundleCountry) {
191 this.bundleCountry = bundleCountry;
192 }
193
194 /**
195 * Sets locale specific variant of resource bundle; optional.
196 * @param bundleVariant locale variant of resource bundle
197 */
198 public void setBundleVariant(String bundleVariant) {
199 this.bundleVariant = bundleVariant;
200 }
201
202 /**
203 * Sets Destination directory; required.
204 * @param toDir destination directory
205 */
206 public void setToDir(File toDir) {
207 this.toDir = toDir;
208 }
209
210 /**
211 * Sets starting token to identify keys; required.
212 * @param startToken starting token to identify keys
213 */
214 public void setStartToken(String startToken) {
215 this.startToken = startToken;
216 }
217
218 /**
219 * Sets ending token to identify keys; required.
220 * @param endToken ending token to identify keys
221 */
222 public void setEndToken(String endToken) {
223 this.endToken = endToken;
224 }
225
226 /**
227 * Sets source file encoding scheme; optional,
228 * defaults to encoding of local system.
229 * @param srcEncoding source file encoding
230 */
231 public void setSrcEncoding(String srcEncoding) {
232 this.srcEncoding = srcEncoding;
233 }
234
235 /**
236 * Sets destination file encoding scheme; optional. Defaults to source file
237 * encoding
238 * @param destEncoding destination file encoding scheme
239 */
240 public void setDestEncoding(String destEncoding) {
241 this.destEncoding = destEncoding;
242 }
243
244 /**
245 * Sets Resource Bundle file encoding scheme; optional. Defaults to source file
246 * encoding
247 * @param bundleEncoding bundle file encoding scheme
248 */
249 public void setBundleEncoding(String bundleEncoding) {
250 this.bundleEncoding = bundleEncoding;
251 }
252
253 /**
254 * Whether or not to overwrite existing file irrespective of
255 * whether it is newer than the source file as well as the
256 * resource bundle file.
257 * Defaults to false.
258 * @param forceOverwrite whether or not to overwrite existing files
259 */
260 public void setForceOverwrite(boolean forceOverwrite) {
261 this.forceOverwrite = forceOverwrite;
262 }
263
264 /**
265 * Adds a set of files to translate as a nested fileset element.
266 * @param set the fileset to be added
267 */
268 public void addFileset(FileSet set) {
269 filesets.addElement(set);
270 }
271
272 /**
273 * Check attributes values, load resource map and translate
274 * @throws BuildException if the required attributes are not set
275 * Required : <ul>
276 * <li>bundle</li>
277 * <li>starttoken</li>
278 * <li>endtoken</li>
279 * </ul>
280 */
281 public void execute() throws BuildException {
282 if (bundle == null) {
283 throw new BuildException("The bundle attribute must be set.",
284 getLocation());
285 }
286
287 if (startToken == null) {
288 throw new BuildException("The starttoken attribute must be set.",
289 getLocation());
290 }
291
292 if (endToken == null) {
293 throw new BuildException("The endtoken attribute must be set.",
294 getLocation());
295 }
296
297 if (bundleLanguage == null) {
298 Locale l = Locale.getDefault();
299 bundleLanguage = l.getLanguage();
300 }
301
302 if (bundleCountry == null) {
303 bundleCountry = Locale.getDefault().getCountry();
304 }
305
306 if (bundleVariant == null) {
307 Locale l = new Locale(bundleLanguage, bundleCountry);
308 bundleVariant = l.getVariant();
309 }
310
311 if (toDir == null) {
312 throw new BuildException("The todir attribute must be set.",
313 getLocation());
314 }
315
316 if (!toDir.exists()) {
317 toDir.mkdirs();
318 } else if (toDir.isFile()) {
319 throw new BuildException(toDir + " is not a directory");
320 }
321
322 if (srcEncoding == null) {
323 srcEncoding = System.getProperty("file.encoding");
324 }
325
326 if (destEncoding == null) {
327 destEncoding = srcEncoding;
328 }
329
330 if (bundleEncoding == null) {
331 bundleEncoding = srcEncoding;
332 }
333
334 loadResourceMaps();
335
336 translate();
337 }
338
339 /**
340 * Load resource maps based on resource bundle encoding scheme.
341 * The resource bundle lookup searches for resource files with various
342 * suffixes on the basis of (1) the desired locale and (2) the default
343 * locale (basebundlename), in the following order from lower-level
344 * (more specific) to parent-level (less specific):
345 *
346 * basebundlename + "_" + language1 + "_" + country1 + "_" + variant1
347 * basebundlename + "_" + language1 + "_" + country1
348 * basebundlename + "_" + language1
349 * basebundlename
350 * basebundlename + "_" + language2 + "_" + country2 + "_" + variant2
351 * basebundlename + "_" + language2 + "_" + country2
352 * basebundlename + "_" + language2
353 *
354 * To the generated name, a ".properties" string is appeneded and
355 * once this file is located, it is treated just like a properties file
356 * but with bundle encoding also considered while loading.
357 */
358 private void loadResourceMaps() throws BuildException {
359 Locale locale = new Locale(bundleLanguage,
360 bundleCountry,
361 bundleVariant);
362 String language = locale.getLanguage().length() > 0
363 ? "_" + locale.getLanguage() : "";
364 String country = locale.getCountry().length() > 0
365 ? "_" + locale.getCountry() : "";
366 String variant = locale.getVariant().length() > 0
367 ? "_" + locale.getVariant() : "";
368 String bundleFile = bundle + language + country + variant;
369 processBundle(bundleFile, BUNDLE_SPECIFIED_LANGUAGE_COUNTRY_VARIANT, false);
370
371 bundleFile = bundle + language + country;
372 processBundle(bundleFile, BUNDLE_SPECIFIED_LANGUAGE_COUNTRY, false);
373
374 bundleFile = bundle + language;
375 processBundle(bundleFile, BUNDLE_SPECIFIED_LANGUAGE, false);
376
377 bundleFile = bundle;
378 processBundle(bundleFile, BUNDLE_NOMATCH, false);
379
380 //Load default locale bundle files
381 //using default file encoding scheme.
382 locale = Locale.getDefault();
383
384 language = locale.getLanguage().length() > 0
385 ? "_" + locale.getLanguage() : "";
386 country = locale.getCountry().length() > 0
387 ? "_" + locale.getCountry() : "";
388 variant = locale.getVariant().length() > 0
389 ? "_" + locale.getVariant() : "";
390 bundleEncoding = System.getProperty("file.encoding");
391
392 bundleFile = bundle + language + country + variant;
393 processBundle(bundleFile, BUNDLE_DEFAULT_LANGUAGE_COUNTRY_VARIANT, false);
394
395 bundleFile = bundle + language + country;
396 processBundle(bundleFile, BUNDLE_DEFAULT_LANGUAGE_COUNTRY, false);
397
398 bundleFile = bundle + language;
399 processBundle(bundleFile, BUNDLE_DEFAULT_LANGUAGE, true);
400 }
401
402 /**
403 * Process each file that makes up this bundle.
404 */
405 private void processBundle(final String bundleFile, final int i,
406 final boolean checkLoaded) throws BuildException {
407 final File propsFile = getProject().resolveFile(bundleFile + ".properties");
408 FileInputStream ins = null;
409 try {
410 ins = new FileInputStream(propsFile);
411 loaded = true;
412 bundleLastModified[i] = propsFile.lastModified();
413 log("Using " + propsFile, Project.MSG_DEBUG);
414 loadResourceMap(ins);
415 } catch (IOException ioe) {
416 log(propsFile + " not found.", Project.MSG_DEBUG);
417 //if all resource files associated with this bundle
418 //have been scanned for and still not able to
419 //find a single resrouce file, throw exception
420 if (!loaded && checkLoaded) {
421 throw new BuildException(ioe.getMessage(), getLocation());
422 }
423 }
424 }
425
426 /**
427 * Load resourceMap with key value pairs. Values of existing keys
428 * are not overwritten. Bundle's encoding scheme is used.
429 */
430 private void loadResourceMap(FileInputStream ins) throws BuildException {
431 try {
432 BufferedReader in = null;
433 InputStreamReader isr = new InputStreamReader(ins, bundleEncoding);
434 in = new BufferedReader(isr);
435 String line = null;
436 while ((line = in.readLine()) != null) {
437 //So long as the line isn't empty and isn't a comment...
438 if (line.trim().length() > 1 && '#' != line.charAt(0) && '!' != line.charAt(0)) {
439 //Legal Key-Value separators are :, = and white space.
440 int sepIndex = line.indexOf('=');
441 if (-1 == sepIndex) {
442 sepIndex = line.indexOf(':');
443 }
444 if (-1 == sepIndex) {
445 for (int k = 0; k < line.length(); k++) {
446 if (Character.isSpaceChar(line.charAt(k))) {
447 sepIndex = k;
448 break;
449 }
450 }
451 }
452 //Only if we do have a key is there going to be a value
453 if (-1 != sepIndex) {
454 String key = line.substring(0, sepIndex).trim();
455 String value = line.substring(sepIndex + 1).trim();
456 //Handle line continuations, if any
457 while (value.endsWith("\\")) {
458 value = value.substring(0, value.length() - 1);
459 if ((line = in.readLine()) != null) {
460 value = value + line.trim();
461 } else {
462 break;
463 }
464 }
465 if (key.length() > 0) {
466 //Has key already been loaded into resourceMap?
467 if (resourceMap.get(key) == null) {
468 resourceMap.put(key, value);
469 }
470 }
471 }
472 }
473 }
474 if (in != null) {
475 in.close();
476 }
477 } catch (IOException ioe) {
478 throw new BuildException(ioe.getMessage(), getLocation());
479 }
480 }
481
482 /**
483 * Reads source file line by line using the source encoding and
484 * searches for keys that are sandwiched between the startToken
485 * and endToken. The values for these keys are looked up from
486 * the hashtable and substituted. If the hashtable doesn't
487 * contain the key, they key itself is used as the value.
488 * Detination files and directories are created as needed.
489 * The destination file is overwritten only if
490 * the forceoverwritten attribute is set to true if
491 * the source file or any associated bundle resource file is
492 * newer than the destination file.
493 */
494 private void translate() throws BuildException {
495 for (int i = 0; i < filesets.size(); i++) {
496 FileSet fs = (FileSet) filesets.elementAt(i);
497 DirectoryScanner ds = fs.getDirectoryScanner(getProject());
498 String[] srcFiles = ds.getIncludedFiles();
499 for (int j = 0; j < srcFiles.length; j++) {
500 try {
501 File dest = fileUtils.resolveFile(toDir, srcFiles[j]);
502 //Make sure parent dirs exist, else, create them.
503 try {
504 File destDir = new File(dest.getParent());
505 if (!destDir.exists()) {
506 destDir.mkdirs();
507 }
508 } catch (Exception e) {
509 log("Exception occurred while trying to check/create "
510 + " parent directory. " + e.getMessage(),
511 Project.MSG_DEBUG);
512 }
513 destLastModified = dest.lastModified();
514 File src = fileUtils.resolveFile(ds.getBasedir(), srcFiles[j]);
515 srcLastModified = src.lastModified();
516 //Check to see if dest file has to be recreated
517 boolean needsWork = forceOverwrite
518 || destLastModified < srcLastModified;
519 if (!needsWork) {
520 for (int icounter = 0; icounter < BUNDLE_MAX_ALTERNATIVES; icounter++) {
521 needsWork = (destLastModified < bundleLastModified[icounter]);
522 if (needsWork) {
523 break;
524 }
525 }
526 }
527 if (needsWork) {
528 log("Processing " + srcFiles[j],
529 Project.MSG_DEBUG);
530 FileOutputStream fos = new FileOutputStream(dest);
531 BufferedWriter out
532 = new BufferedWriter(new OutputStreamWriter(fos, destEncoding));
533 FileInputStream fis = new FileInputStream(src);
534 BufferedReader in
535 = new BufferedReader(new InputStreamReader(fis, srcEncoding));
536 String line;
537 LineTokenizer lineTokenizer = new LineTokenizer();
538 lineTokenizer.setIncludeDelims(true);
539 line = lineTokenizer.getToken(in);
540 while ((line) != null) {
541 // 2003-02-21 new replace algorithm by tbee ([email protected])
542 // because it wasn't able to replace something like "@aaa;@bbb;"
543
544 // is there a startToken
545 // and there is still stuff following the startToken
546 int startIndex = line.indexOf(startToken);
547 while (startIndex >= 0
548 && (startIndex + startToken.length()) <= line.length()) {
549 // the new value, this needs to be here
550 // because it is required to calculate the next position to search from
551 // at the end of the loop
552 String replace = null;
553
554 // we found a starttoken, is there an endtoken following?
555 // start at token+tokenlength because start and end
556 // token may be indentical
557 int endIndex = line.indexOf(endToken, startIndex + startToken.length());
558 if (endIndex < 0) {
559 startIndex += 1;
560 } else {
561 // grab the token
562 String token
563 = line.substring(startIndex + startToken.length(), endIndex);
564
565 // If there is a white space or = or :, then
566 // it isn't to be treated as a valid key.
567 boolean validToken = true;
568 for (int k = 0; k < token.length() && validToken; k++) {
569 char c = token.charAt(k);
570 if (c == ':' || c == '='
571 || Character.isSpaceChar(c)) {
572 validToken = false;
573 }
574 }
575 if (!validToken) {
576 startIndex += 1;
577 } else {
578 // find the replace string
579 if (resourceMap.containsKey(token)) {
580 replace = (String) resourceMap.get(token);
581 } else {
582 replace = token;
583 }
584
585
586 // generate the new line
587 line = line.substring(0, startIndex)
588 + replace
589 + line.substring(endIndex + endToken.length());
590
591 // set start position for next search
592 startIndex += replace.length();
593 }
594 }
595
596 // find next starttoken
597 startIndex = line.indexOf(startToken, startIndex);
598 }
599
600
601 out.write(line);
602 line = lineTokenizer.getToken(in);
603 }
604 if (in != null) {
605 in.close();
606 }
607 if (out != null) {
608 out.close();
609 }
610 } else {
611 log("Skipping " + srcFiles[j]
612 + " as destination file is up to date",
613 Project.MSG_VERBOSE);
614 }
615 } catch (IOException ioe) {
616 throw new BuildException(ioe.getMessage(), getLocation());
617 }
618 }
619 }
620 }
621}
Note: See TracBrowser for help on using the repository browser.