diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java new file mode 100644 index 00000000..82731fe9 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java @@ -0,0 +1,632 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant.taskdefs.optional.i18n; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Vector; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.LineTokenizer; + +/** + * Translates text embedded in files using Resource Bundle files. + * Since ant 1.6 preserves line endings + * + */ +public class Translate extends MatchingTask { + /** + * search a bundle matching the specified language, the country and the variant + */ + private static final int BUNDLE_SPECIFIED_LANGUAGE_COUNTRY_VARIANT = 0; + /** + * search a bundle matching the specified language, and the country + */ + private static final int BUNDLE_SPECIFIED_LANGUAGE_COUNTRY = 1; + /** + * search a bundle matching the specified language only + */ + private static final int BUNDLE_SPECIFIED_LANGUAGE = 2; + /** + * search a bundle matching nothing special + */ + private static final int BUNDLE_NOMATCH = 3; + /** + * search a bundle matching the language, the country and the variant + * of the current locale of the computer + */ + private static final int BUNDLE_DEFAULT_LANGUAGE_COUNTRY_VARIANT = 4; + /** + * search a bundle matching the language, and the country + * of the current locale of the computer + */ + private static final int BUNDLE_DEFAULT_LANGUAGE_COUNTRY = 5; + /** + * search a bundle matching the language only + * of the current locale of the computer + */ + private static final int BUNDLE_DEFAULT_LANGUAGE = 6; + /** + * number of possibilities for the search + */ + private static final int BUNDLE_MAX_ALTERNATIVES = BUNDLE_DEFAULT_LANGUAGE + 1; + /** + * Family name of resource bundle + */ + private String bundle; + + /** + * Locale specific language of the resource bundle + */ + private String bundleLanguage; + + /** + * Locale specific country of the resource bundle + */ + private String bundleCountry; + + /** + * Locale specific variant of the resource bundle + */ + private String bundleVariant; + + /** + * Destination directory + */ + private File toDir; + + /** + * Source file encoding scheme + */ + private String srcEncoding; + + /** + * Destination file encoding scheme + */ + private String destEncoding; + + /** + * Resource Bundle file encoding scheme, defaults to srcEncoding + */ + private String bundleEncoding; + + /** + * Starting token to identify keys + */ + private String startToken; + + /** + * Ending token to identify keys + */ + private String endToken; + + /** + * Whether or not to create a new destination file. + * Defaults to <code>false</code>. + */ + private boolean forceOverwrite; + + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + + /** + * Holds key value pairs loaded from resource bundle file + */ + private Hashtable resourceMap = new Hashtable(); + /** + + * Used to resolve file names. + */ + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** + * Last Modified Timestamp of resource bundle file being used. + */ + private long[] bundleLastModified = new long[BUNDLE_MAX_ALTERNATIVES]; + + /** + * Last Modified Timestamp of source file being used. + */ + private long srcLastModified; + + /** + * Last Modified Timestamp of destination file being used. + */ + private long destLastModified; + + /** + * Has at least one file from the bundle been loaded? + */ + private boolean loaded = false; + + /** + * Sets Family name of resource bundle; required. + * @param bundle family name of resource bundle + */ + public void setBundle(String bundle) { + this.bundle = bundle; + } + + /** + * Sets locale specific language of resource bundle; optional. + * @param bundleLanguage language of the bundle + */ + public void setBundleLanguage(String bundleLanguage) { + this.bundleLanguage = bundleLanguage; + } + + /** + * Sets locale specific country of resource bundle; optional. + * @param bundleCountry country of the bundle + */ + public void setBundleCountry(String bundleCountry) { + this.bundleCountry = bundleCountry; + } + + /** + * Sets locale specific variant of resource bundle; optional. + * @param bundleVariant locale variant of resource bundle + */ + public void setBundleVariant(String bundleVariant) { + this.bundleVariant = bundleVariant; + } + + /** + * Sets Destination directory; required. + * @param toDir destination directory + */ + public void setToDir(File toDir) { + this.toDir = toDir; + } + + /** + * Sets starting token to identify keys; required. + * @param startToken starting token to identify keys + */ + public void setStartToken(String startToken) { + this.startToken = startToken; + } + + /** + * Sets ending token to identify keys; required. + * @param endToken ending token to identify keys + */ + public void setEndToken(String endToken) { + this.endToken = endToken; + } + + /** + * Sets source file encoding scheme; optional, + * defaults to encoding of local system. + * @param srcEncoding source file encoding + */ + public void setSrcEncoding(String srcEncoding) { + this.srcEncoding = srcEncoding; + } + + /** + * Sets destination file encoding scheme; optional. Defaults to source file + * encoding + * @param destEncoding destination file encoding scheme + */ + public void setDestEncoding(String destEncoding) { + this.destEncoding = destEncoding; + } + + /** + * Sets Resource Bundle file encoding scheme; optional. Defaults to source file + * encoding + * @param bundleEncoding bundle file encoding scheme + */ + public void setBundleEncoding(String bundleEncoding) { + this.bundleEncoding = bundleEncoding; + } + + /** + * Whether or not to overwrite existing file irrespective of + * whether it is newer than the source file as well as the + * resource bundle file. + * Defaults to false. + * @param forceOverwrite whether or not to overwrite existing files + */ + public void setForceOverwrite(boolean forceOverwrite) { + this.forceOverwrite = forceOverwrite; + } + + /** + * Adds a set of files to translate as a nested fileset element. + * @param set the fileset to be added + */ + public void addFileset(FileSet set) { + filesets.addElement(set); + } + + /** + * Check attributes values, load resource map and translate + * @throws BuildException if the required attributes are not set + * Required : <ul> + * <li>bundle</li> + * <li>starttoken</li> + * <li>endtoken</li> + * </ul> + */ + public void execute() throws BuildException { + if (bundle == null) { + throw new BuildException("The bundle attribute must be set.", + getLocation()); + } + + if (startToken == null) { + throw new BuildException("The starttoken attribute must be set.", + getLocation()); + } + + if (endToken == null) { + throw new BuildException("The endtoken attribute must be set.", + getLocation()); + } + + if (bundleLanguage == null) { + Locale l = Locale.getDefault(); + bundleLanguage = l.getLanguage(); + } + + if (bundleCountry == null) { + bundleCountry = Locale.getDefault().getCountry(); + } + + if (bundleVariant == null) { + Locale l = new Locale(bundleLanguage, bundleCountry); + bundleVariant = l.getVariant(); + } + + if (toDir == null) { + throw new BuildException("The todir attribute must be set.", + getLocation()); + } + + if (!toDir.exists()) { + toDir.mkdirs(); + } else if (toDir.isFile()) { + throw new BuildException(toDir + " is not a directory"); + } + + if (srcEncoding == null) { + srcEncoding = System.getProperty("file.encoding"); + } + + if (destEncoding == null) { + destEncoding = srcEncoding; + } + + if (bundleEncoding == null) { + bundleEncoding = srcEncoding; + } + + loadResourceMaps(); + + translate(); + } + + /** + * Load resource maps based on resource bundle encoding scheme. + * The resource bundle lookup searches for resource files with various + * suffixes on the basis of (1) the desired locale and (2) the default + * locale (basebundlename), in the following order from lower-level + * (more specific) to parent-level (less specific): + * + * basebundlename + "_" + language1 + "_" + country1 + "_" + variant1 + * basebundlename + "_" + language1 + "_" + country1 + * basebundlename + "_" + language1 + * basebundlename + * basebundlename + "_" + language2 + "_" + country2 + "_" + variant2 + * basebundlename + "_" + language2 + "_" + country2 + * basebundlename + "_" + language2 + * + * To the generated name, a ".properties" string is appeneded and + * once this file is located, it is treated just like a properties file + * but with bundle encoding also considered while loading. + */ + private void loadResourceMaps() throws BuildException { + Locale locale = new Locale(bundleLanguage, + bundleCountry, + bundleVariant); + String language = locale.getLanguage().length() > 0 + ? "_" + locale.getLanguage() : ""; + String country = locale.getCountry().length() > 0 + ? "_" + locale.getCountry() : ""; + String variant = locale.getVariant().length() > 0 + ? "_" + locale.getVariant() : ""; + String bundleFile = bundle + language + country + variant; + processBundle(bundleFile, BUNDLE_SPECIFIED_LANGUAGE_COUNTRY_VARIANT, false); + + bundleFile = bundle + language + country; + processBundle(bundleFile, BUNDLE_SPECIFIED_LANGUAGE_COUNTRY, false); + + bundleFile = bundle + language; + processBundle(bundleFile, BUNDLE_SPECIFIED_LANGUAGE, false); + + bundleFile = bundle; + processBundle(bundleFile, BUNDLE_NOMATCH, false); + + //Load default locale bundle files + //using default file encoding scheme. + locale = Locale.getDefault(); + + language = locale.getLanguage().length() > 0 + ? "_" + locale.getLanguage() : ""; + country = locale.getCountry().length() > 0 + ? "_" + locale.getCountry() : ""; + variant = locale.getVariant().length() > 0 + ? "_" + locale.getVariant() : ""; + bundleEncoding = System.getProperty("file.encoding"); + + bundleFile = bundle + language + country + variant; + processBundle(bundleFile, BUNDLE_DEFAULT_LANGUAGE_COUNTRY_VARIANT, false); + + bundleFile = bundle + language + country; + processBundle(bundleFile, BUNDLE_DEFAULT_LANGUAGE_COUNTRY, false); + + bundleFile = bundle + language; + processBundle(bundleFile, BUNDLE_DEFAULT_LANGUAGE, true); + } + + /** + * Process each file that makes up this bundle. + */ + private void processBundle(final String bundleFile, final int i, + final boolean checkLoaded) throws BuildException { + final File propsFile = getProject().resolveFile(bundleFile + ".properties"); + FileInputStream ins = null; + try { + ins = new FileInputStream(propsFile); + loaded = true; + bundleLastModified[i] = propsFile.lastModified(); + log("Using " + propsFile, Project.MSG_DEBUG); + loadResourceMap(ins); + } catch (IOException ioe) { + log(propsFile + " not found.", Project.MSG_DEBUG); + //if all resource files associated with this bundle + //have been scanned for and still not able to + //find a single resrouce file, throw exception + if (!loaded && checkLoaded) { + throw new BuildException(ioe.getMessage(), getLocation()); + } + } + } + + /** + * Load resourceMap with key value pairs. Values of existing keys + * are not overwritten. Bundle's encoding scheme is used. + */ + private void loadResourceMap(FileInputStream ins) throws BuildException { + try { + BufferedReader in = null; + InputStreamReader isr = new InputStreamReader(ins, bundleEncoding); + in = new BufferedReader(isr); + String line = null; + while ((line = in.readLine()) != null) { + //So long as the line isn't empty and isn't a comment... + if (line.trim().length() > 1 && '#' != line.charAt(0) && '!' != line.charAt(0)) { + //Legal Key-Value separators are :, = and white space. + int sepIndex = line.indexOf('='); + if (-1 == sepIndex) { + sepIndex = line.indexOf(':'); + } + if (-1 == sepIndex) { + for (int k = 0; k < line.length(); k++) { + if (Character.isSpaceChar(line.charAt(k))) { + sepIndex = k; + break; + } + } + } + //Only if we do have a key is there going to be a value + if (-1 != sepIndex) { + String key = line.substring(0, sepIndex).trim(); + String value = line.substring(sepIndex + 1).trim(); + //Handle line continuations, if any + while (value.endsWith("\\")) { + value = value.substring(0, value.length() - 1); + line = in.readLine(); + if (line != null) { + value = value + line.trim(); + } else { + break; + } + } + if (key.length() > 0) { + //Has key already been loaded into resourceMap? + if (resourceMap.get(key) == null) { + resourceMap.put(key, value); + } + } + } + } + } + if (in != null) { + in.close(); + } + } catch (IOException ioe) { + throw new BuildException(ioe.getMessage(), getLocation()); + } + } + + /** + * Reads source file line by line using the source encoding and + * searches for keys that are sandwiched between the startToken + * and endToken. The values for these keys are looked up from + * the hashtable and substituted. If the hashtable doesn't + * contain the key, they key itself is used as the value. + * Detination files and directories are created as needed. + * The destination file is overwritten only if + * the forceoverwritten attribute is set to true if + * the source file or any associated bundle resource file is + * newer than the destination file. + */ + private void translate() throws BuildException { + int filesProcessed = 0; + final int size = filesets.size(); + for (int i = 0; i < size; i++) { + FileSet fs = (FileSet) filesets.elementAt(i); + DirectoryScanner ds = fs.getDirectoryScanner(getProject()); + String[] srcFiles = ds.getIncludedFiles(); + for (int j = 0; j < srcFiles.length; j++) { + try { + File dest = FILE_UTILS.resolveFile(toDir, srcFiles[j]); + //Make sure parent dirs exist, else, create them. + try { + File destDir = new File(dest.getParent()); + if (!destDir.exists()) { + destDir.mkdirs(); + } + } catch (Exception e) { + log("Exception occurred while trying to check/create " + + " parent directory. " + e.getMessage(), + Project.MSG_DEBUG); + } + destLastModified = dest.lastModified(); + File src = FILE_UTILS.resolveFile(ds.getBasedir(), srcFiles[j]); + srcLastModified = src.lastModified(); + //Check to see if dest file has to be recreated + boolean needsWork = forceOverwrite + || destLastModified < srcLastModified; + if (!needsWork) { + for (int icounter = 0; icounter < BUNDLE_MAX_ALTERNATIVES; icounter++) { + needsWork = (destLastModified < bundleLastModified[icounter]); + if (needsWork) { + break; + } + } + } + if (needsWork) { + log("Processing " + srcFiles[j], + Project.MSG_DEBUG); + translateOneFile(src, dest); + ++filesProcessed; + } else { + log("Skipping " + srcFiles[j] + + " as destination file is up to date", + Project.MSG_VERBOSE); + } + } catch (IOException ioe) { + throw new BuildException(ioe.getMessage(), getLocation()); + } + } + } + log("Translation performed on " + filesProcessed + " file(s).", Project.MSG_DEBUG); + } + + private void translateOneFile(File src, File dest) throws IOException { + BufferedWriter out = null; + BufferedReader in = null; + try { + FileOutputStream fos = new FileOutputStream(dest); + out = new BufferedWriter(new OutputStreamWriter(fos, destEncoding)); + FileInputStream fis = new FileInputStream(src); + in = new BufferedReader(new InputStreamReader(fis, srcEncoding)); + String line; + LineTokenizer lineTokenizer = new LineTokenizer(); + lineTokenizer.setIncludeDelims(true); + line = lineTokenizer.getToken(in); + while ((line) != null) { + // 2003-02-21 new replace algorithm by tbee (tbee@tbee.org) + // because it wasn't able to replace something like "@aaa;@bbb;" + + // is there a startToken + // and there is still stuff following the startToken + int startIndex = line.indexOf(startToken); + while (startIndex >= 0 + && (startIndex + startToken.length()) <= line.length()) { + // the new value, this needs to be here + // because it is required to calculate the next position to + // search from at the end of the loop + String replace = null; + + // we found a starttoken, is there an endtoken following? + // start at token+tokenlength because start and end + // token may be identical + int endIndex = line.indexOf(endToken, startIndex + + startToken.length()); + if (endIndex < 0) { + startIndex += 1; + } else { + // grab the token + String token = line.substring(startIndex + + startToken.length(), + endIndex); + + // If there is a white space or = or :, then + // it isn't to be treated as a valid key. + boolean validToken = true; + for (int k = 0; k < token.length() && validToken; k++) { + char c = token.charAt(k); + if (c == ':' || c == '=' + || Character.isSpaceChar(c)) { + validToken = false; + } + } + if (!validToken) { + startIndex += 1; + } else { + // find the replace string + if (resourceMap.containsKey(token)) { + replace = (String) resourceMap.get(token); + } else { + log("Replacement string missing for: " + token, + Project.MSG_VERBOSE); + replace = startToken + token + endToken; + } + + + // generate the new line + line = line.substring(0, startIndex) + replace + + line.substring(endIndex + endToken.length()); + + // set start position for next search + startIndex += replace.length(); + } + } + + // find next starttoken + startIndex = line.indexOf(startToken, startIndex); + } + out.write(line); + line = lineTokenizer.getToken(in); + } + } finally { + FileUtils.close(in); + FileUtils.close(out); + } + } +} |