Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 577   Methods: 17
NCLOC: 324   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
Translate.java 0% 0% 0% 0%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 package org.apache.tools.ant.taskdefs.optional.i18n;
 55   
 
 56   
 import java.io.BufferedReader;
 57   
 import java.io.BufferedWriter;
 58   
 import java.io.File;
 59   
 import java.io.FileInputStream;
 60   
 import java.io.FileOutputStream;
 61   
 import java.io.IOException;
 62   
 import java.io.InputStreamReader;
 63   
 import java.io.OutputStreamWriter;
 64   
 import java.util.Hashtable;
 65   
 import java.util.Locale;
 66   
 import java.util.Vector;
 67   
 import org.apache.tools.ant.BuildException;
 68   
 import org.apache.tools.ant.DirectoryScanner;
 69   
 import org.apache.tools.ant.Project;
 70   
 import org.apache.tools.ant.taskdefs.MatchingTask;
 71   
 import org.apache.tools.ant.types.FileSet;
 72   
 import org.apache.tools.ant.util.FileUtils;
 73   
 
 74   
 /**
 75   
  * Translates text embedded in files using Resource Bundle files.
 76   
  *
 77   
  * @author Magesh Umasankar, Don Brown
 78   
  */
 79   
 public class Translate extends MatchingTask {
 80   
 
 81   
     /**
 82   
      * Family name of resource bundle
 83   
      */
 84   
     private String bundle;
 85   
     /**
 86   
      * Locale specific language of the resource bundle
 87   
      */
 88   
     private String bundleLanguage;
 89   
     /**
 90   
      * Locale specific country of the resource bundle
 91   
      */
 92   
     private String bundleCountry;
 93   
     /**
 94   
      * Locale specific variant of the resource bundle
 95   
      */
 96   
     private String bundleVariant;
 97   
     /**
 98   
      * Destination directory
 99   
      */
 100   
     private File toDir;
 101   
     /**
 102   
      * Source file encoding scheme
 103   
      */
 104   
     private String srcEncoding;
 105   
     /**
 106   
      * Destination file encoding scheme
 107   
      */
 108   
     private String destEncoding;
 109   
     /**
 110   
      * Resource Bundle file encoding scheme, defaults to srcEncoding
 111   
      */
 112   
     private String bundleEncoding;
 113   
     /**
 114   
      * Starting token to identify keys
 115   
      */
 116   
     private String startToken;
 117   
     /**
 118   
      * Ending token to identify keys
 119   
      */
 120   
     private String endToken;
 121   
     /**
 122   
      * Whether or not to create a new destination file.
 123   
      * Defaults to <code>false</code>.
 124   
      */
 125   
     private boolean forceOverwrite;
 126   
     /**
 127   
      * Vector to hold source file sets.
 128   
      */
 129   
     private Vector filesets = new Vector();
 130   
     /**
 131   
      * Holds key value pairs loaded from resource bundle file
 132   
      */
 133   
     private Hashtable resourceMap = new Hashtable();
 134   
     /**
 135   
      * Used to resolve file names.
 136   
      */
 137   
     private FileUtils fileUtils = FileUtils.newFileUtils();
 138   
     /**
 139   
      * Last Modified Timestamp of resource bundle file being used.
 140   
      */
 141   
     private long[] bundleLastModified = new long[7];
 142   
     /**
 143   
      * Last Modified Timestamp of source file being used.
 144   
      */
 145   
     private long srcLastModified;
 146   
     /**
 147   
      * Last Modified Timestamp of destination file being used.
 148   
      */
 149   
     private long destLastModified;
 150   
     /**
 151   
      * Has at least one file from the bundle been loaded?
 152   
      */
 153   
     private boolean loaded = false;
 154   
 
 155   
     /**
 156   
      * Sets Family name of resource bundle; required.
 157   
      */
 158  0
     public void setBundle(String bundle) {
 159  0
         this.bundle = bundle;
 160   
     }
 161   
 
 162   
     /**
 163   
      * Sets locale specific language of resource bundle; optional.
 164   
      */
 165  0
     public void setBundleLanguage(String bundleLanguage) {
 166  0
         this.bundleLanguage = bundleLanguage;
 167   
     }
 168   
 
 169   
     /**
 170   
      * Sets locale specific country of resource bundle; optional.
 171   
      */
 172  0
     public void setBundleCountry(String bundleCountry) {
 173  0
         this.bundleCountry = bundleCountry;
 174   
     }
 175   
 
 176   
     /**
 177   
      * Sets locale specific variant of resource bundle; optional.
 178   
      */
 179  0
     public void setBundleVariant(String bundleVariant) {
 180  0
         this.bundleVariant = bundleVariant;
 181   
     }
 182   
 
 183   
     /**
 184   
      * Sets Destination directory; required.
 185   
      */
 186  0
     public void setToDir(File toDir) {
 187  0
         this.toDir = toDir;
 188   
     }
 189   
 
 190   
     /**
 191   
      * Sets starting token to identify keys; required.
 192   
      */
 193  0
     public void setStartToken(String startToken) {
 194  0
         this.startToken = startToken;
 195   
     }
 196   
 
 197   
     /**
 198   
      * Sets ending token to identify keys; required.
 199   
      */
 200  0
     public void setEndToken(String endToken) {
 201  0
         this.endToken = endToken;
 202   
     }
 203   
 
 204   
     /**
 205   
      * Sets source file encoding scheme; optional,
 206   
      * defaults to encoding of local system.
 207   
      */
 208  0
     public void setSrcEncoding(String srcEncoding) {
 209  0
         this.srcEncoding = srcEncoding;
 210   
     }
 211   
 
 212   
     /**
 213   
      * Sets destination file encoding scheme; optional.  Defaults to source file
 214   
      * encoding
 215   
      */
 216  0
     public void setDestEncoding(String destEncoding) {
 217  0
         this.destEncoding = destEncoding;
 218   
     }
 219   
 
 220   
     /**
 221   
      * Sets Resource Bundle file encoding scheme; optional.  Defaults to source file
 222   
      * encoding
 223   
      */
 224  0
     public void setBundleEncoding(String bundleEncoding) {
 225  0
         this.bundleEncoding = bundleEncoding;
 226   
     }
 227   
 
 228   
     /**
 229   
      * Whether or not to overwrite existing file irrespective of
 230   
      * whether it is newer than the source file as well as the
 231   
      * resource bundle file.
 232   
      * Defaults to false.
 233   
      */
 234  0
     public void setForceOverwrite(boolean forceOverwrite) {
 235  0
         this.forceOverwrite = forceOverwrite;
 236   
     }
 237   
 
 238   
     /**
 239   
      * Adds a set of files to translate as a nested fileset element.
 240   
      */
 241  0
     public void addFileset(FileSet set) {
 242  0
         filesets.addElement(set);
 243   
     }
 244   
 
 245   
     /**
 246   
      * Check attributes values, load resource map and translate
 247   
      */
 248  0
     public void execute() throws BuildException {
 249  0
         if (bundle == null) {
 250  0
             throw new BuildException("The bundle attribute must be set.",
 251   
                                      getLocation());
 252   
         }
 253   
 
 254  0
         if (startToken == null) {
 255  0
             throw new BuildException("The starttoken attribute must be set.",
 256   
                                      getLocation());
 257   
         }
 258   
 
 259  0
         if (endToken == null) {
 260  0
             throw new BuildException("The endtoken attribute must be set.",
 261   
                                      getLocation());
 262   
         }
 263   
 
 264  0
         if (bundleLanguage == null) {
 265  0
             Locale l = Locale.getDefault();
 266  0
             bundleLanguage  = l.getLanguage();
 267   
         }
 268   
 
 269  0
         if (bundleCountry == null) {
 270  0
             bundleCountry = Locale.getDefault().getCountry();
 271   
         }
 272   
 
 273  0
         if (bundleVariant == null) {
 274  0
             Locale l = new Locale(bundleLanguage, bundleCountry);
 275  0
             bundleVariant = l.getVariant();
 276   
         }
 277   
 
 278  0
         if (toDir == null) {
 279  0
             throw new BuildException("The todir attribute must be set.",
 280   
                                      getLocation());
 281   
         }
 282   
 
 283  0
         if (!toDir.exists()) {
 284  0
             toDir.mkdirs();
 285   
         } else {
 286  0
             if (toDir.isFile()) {
 287  0
                 throw new BuildException(toDir + " is not a directory");
 288   
             }
 289   
         }
 290   
 
 291  0
         if (srcEncoding == null) {
 292  0
             srcEncoding = System.getProperty("file.encoding");
 293   
         }
 294   
 
 295  0
         if (destEncoding == null) {
 296  0
             destEncoding = srcEncoding;
 297   
         }
 298   
 
 299  0
         if (bundleEncoding == null) {
 300  0
             bundleEncoding = srcEncoding;
 301   
         }
 302   
 
 303  0
         loadResourceMaps();
 304   
 
 305  0
         translate();
 306   
     }
 307   
 
 308   
     /**
 309   
      * Load resource maps based on resource bundle encoding scheme.
 310   
      * The resource bundle lookup searches for resource files with various
 311   
      * suffixes on the basis of (1) the desired locale and (2) the default
 312   
      * locale (basebundlename), in the following order from lower-level
 313   
      * (more specific) to parent-level (less specific):
 314   
      *
 315   
      * basebundlename + "_" + language1 + "_" + country1 + "_" + variant1
 316   
      * basebundlename + "_" + language1 + "_" + country1
 317   
      * basebundlename + "_" + language1
 318   
      * basebundlename
 319   
      * basebundlename + "_" + language2 + "_" + country2 + "_" + variant2
 320   
      * basebundlename + "_" + language2 + "_" + country2
 321   
      * basebundlename + "_" + language2
 322   
      *
 323   
      * To the generated name, a ".properties" string is appeneded and
 324   
      * once this file is located, it is treated just like a properties file
 325   
      * but with bundle encoding also considered while loading.
 326   
      */
 327  0
     private void loadResourceMaps() throws BuildException {
 328  0
         Locale locale = new Locale(bundleLanguage,
 329   
                                    bundleCountry,
 330   
                                    bundleVariant);
 331  0
         String language = locale.getLanguage().length() > 0 ?
 332   
             "_" + locale.getLanguage() :
 333   
             "";
 334  0
         String country = locale.getCountry().length() > 0 ?
 335   
             "_" + locale.getCountry() :
 336   
             "";
 337  0
         String variant = locale.getVariant().length() > 0 ?
 338   
             "_" + locale.getVariant() :
 339   
             "";
 340  0
         String bundleFile = bundle + language + country + variant;
 341  0
         processBundle(bundleFile, 0, false);
 342   
 
 343  0
         bundleFile = bundle + language + country;
 344  0
         processBundle(bundleFile, 1, false);
 345   
 
 346  0
         bundleFile = bundle + language;
 347  0
         processBundle(bundleFile, 2, false);
 348   
 
 349  0
         bundleFile = bundle;
 350  0
         processBundle(bundleFile, 3, false);
 351   
 
 352   
         //Load default locale bundle files
 353   
         //using default file encoding scheme.
 354  0
         locale = Locale.getDefault();
 355   
 
 356  0
         language = locale.getLanguage().length() > 0 ?
 357   
             "_" + locale.getLanguage() :
 358   
             "";
 359  0
         country = locale.getCountry().length() > 0 ?
 360   
             "_" + locale.getCountry() :
 361   
             "";
 362  0
         variant = locale.getVariant().length() > 0 ?
 363   
             "_" + locale.getVariant() :
 364   
             "";
 365  0
         bundleEncoding = System.getProperty("file.encoding");
 366   
 
 367  0
         bundleFile = bundle + language + country + variant;
 368  0
         processBundle(bundleFile, 4, false);
 369   
 
 370  0
         bundleFile = bundle + language + country;
 371  0
         processBundle(bundleFile, 5, false);
 372   
 
 373  0
         bundleFile = bundle + language;
 374  0
         processBundle(bundleFile, 6, true);
 375   
     }
 376   
 
 377   
     /**
 378   
      * Process each file that makes up this bundle.
 379   
      */
 380  0
     private void processBundle(final String bundleFile, final int i,
 381   
                                final boolean checkLoaded) throws BuildException {
 382  0
         final File propsFile = getProject().resolveFile(bundleFile + ".properties");
 383  0
         FileInputStream ins = null;
 384  0
         try {
 385  0
             ins = new FileInputStream(propsFile);
 386  0
             loaded = true;
 387  0
             bundleLastModified[i] = propsFile.lastModified();
 388  0
             log("Using " + propsFile, Project.MSG_DEBUG);
 389  0
             loadResourceMap(ins);
 390   
         } catch (IOException ioe) {
 391  0
             log(propsFile + " not found.", Project.MSG_DEBUG);
 392   
             //if all resource files associated with this bundle
 393   
             //have been scanned for and still not able to
 394   
             //find a single resrouce file, throw exception
 395  0
             if (!loaded && checkLoaded) {
 396  0
                 throw new BuildException(ioe.getMessage(), getLocation());
 397   
             }
 398   
         }
 399   
     }
 400   
 
 401   
     /**
 402   
      * Load resourceMap with key value pairs.  Values of existing keys
 403   
      * are not overwritten.  Bundle's encoding scheme is used.
 404   
      */
 405  0
     private void loadResourceMap(FileInputStream ins) throws BuildException {
 406  0
         try {
 407  0
             BufferedReader in = null;
 408  0
             InputStreamReader isr = new InputStreamReader(ins, bundleEncoding);
 409  0
             in = new BufferedReader(isr);
 410  0
             String line = null;
 411  0
             while ((line = in.readLine()) != null) {
 412   
                 //So long as the line isn't empty and isn't a comment...
 413  0
                 if (line.trim().length() > 1 && '#' != line.charAt(0) && '!' != line.charAt(0)) {
 414   
                     //Legal Key-Value separators are :, = and white space.
 415  0
                     int sepIndex = line.indexOf('=');
 416  0
                     if (-1 == sepIndex) {
 417  0
                         sepIndex = line.indexOf(':');
 418   
                     }
 419  0
                     if (-1 == sepIndex) {
 420  0
                         for (int k = 0; k < line.length(); k++) {
 421  0
                             if (Character.isSpaceChar(line.charAt(k))) {
 422  0
                                 sepIndex = k;
 423  0
                                 break;
 424   
                             }
 425   
                         }
 426   
                     }
 427   
                     //Only if we do have a key is there going to be a value
 428  0
                     if (-1 != sepIndex) {
 429  0
                         String key = line.substring(0, sepIndex).trim();
 430  0
                         String value = line.substring(sepIndex + 1).trim();
 431   
                         //Handle line continuations, if any
 432  0
                         while (value.endsWith("\\")) {
 433  0
                             value = value.substring(0, value.length() - 1);
 434  0
                             if ((line = in.readLine()) != null) {
 435  0
                                 value = value + line.trim();
 436   
                             } else {
 437  0
                                 break;
 438   
                             }
 439   
                         }
 440  0
                         if (key.length() > 0) {
 441   
                             //Has key already been loaded into resourceMap?
 442  0
                             if (resourceMap.get(key) == null) {
 443  0
                                 resourceMap.put(key, value);
 444   
                             }
 445   
                         }
 446   
                     }
 447   
                 }
 448   
             }
 449  0
             if (in != null) {
 450  0
                 in.close();
 451   
             }
 452   
         } catch (IOException ioe) {
 453  0
             throw new BuildException(ioe.getMessage(), getLocation());
 454   
         }
 455   
     }
 456   
 
 457   
     /**
 458   
      * Reads source file line by line using the source encoding and
 459   
      * searches for keys that are sandwiched between the startToken
 460   
      * and endToken.  The values for these keys are looked up from
 461   
      * the hashtable and substituted.  If the hashtable doesn't
 462   
      * contain the key, they key itself is used as the value.
 463   
      * Detination files and directories are created as needed.
 464   
      * The destination file is overwritten only if
 465   
      * the forceoverwritten attribute is set to true if
 466   
      * the source file or any associated bundle resource file is
 467   
      * newer than the destination file.
 468   
      */
 469  0
     private void translate() throws BuildException {
 470  0
         for (int i = 0; i < filesets.size(); i++) {
 471  0
             FileSet fs = (FileSet) filesets.elementAt(i);
 472  0
             DirectoryScanner ds = fs.getDirectoryScanner(getProject());
 473  0
             String[] srcFiles = ds.getIncludedFiles();
 474  0
             for (int j = 0; j < srcFiles.length; j++) {
 475  0
                 try {
 476  0
                     File dest = fileUtils.resolveFile(toDir, srcFiles[j]);
 477   
                     //Make sure parent dirs exist, else, create them.
 478  0
                     try {
 479  0
                         File destDir = new File(dest.getParent());
 480  0
                         if (!destDir.exists()) {
 481  0
                             destDir.mkdirs();
 482   
                         }
 483   
                     } catch (Exception e) {
 484  0
                         log("Exception occured while trying to check/create "
 485   
                             + " parent directory.  " + e.getMessage(),
 486   
                             Project.MSG_DEBUG);
 487   
                     }
 488  0
                     destLastModified = dest.lastModified();
 489  0
                     File src = fileUtils.resolveFile(ds.getBasedir(), srcFiles[j]);
 490  0
                     srcLastModified = src.lastModified();
 491   
                     //Check to see if dest file has to be recreated
 492  0
                     if (forceOverwrite
 493   
                         || destLastModified < srcLastModified
 494   
                         || destLastModified < bundleLastModified[0]
 495   
                         || destLastModified < bundleLastModified[1]
 496   
                         || destLastModified < bundleLastModified[2]
 497   
                         || destLastModified < bundleLastModified[3]
 498   
                         || destLastModified < bundleLastModified[4]
 499   
                         || destLastModified < bundleLastModified[5]
 500   
                         || destLastModified < bundleLastModified[6]) {
 501  0
                         log("Processing " + srcFiles[j],
 502   
                             Project.MSG_DEBUG);
 503  0
                         FileOutputStream fos = new FileOutputStream(dest);
 504  0
                         BufferedWriter out
 505   
                             = new BufferedWriter(new OutputStreamWriter(fos, destEncoding));
 506  0
                         FileInputStream fis = new FileInputStream(src);
 507  0
                         BufferedReader in
 508   
                             = new BufferedReader(new InputStreamReader(fis, srcEncoding));
 509  0
                         String line;
 510  0
                         int stLength = startToken.length();
 511  0
                         int etLength = endToken.length();
 512  0
                         while ((line = in.readLine()) != null) {
 513  0
                             int startIndex = -1;
 514  0
                             int endIndex = -1;
 515  0
 outer:                      while (true) {
 516  0
                                 startIndex = line.indexOf(startToken, endIndex + etLength);
 517  0
                                 if (startIndex < 0 ||
 518   
                                     startIndex + stLength >= line.length()) {
 519  0
                                     break;
 520   
                                 }
 521  0
                                 endIndex = line.indexOf(endToken, startIndex + stLength);
 522  0
                                 if (endIndex < 0) {
 523  0
                                     break;
 524   
                                 }
 525  0
                                 String matches = line.substring(startIndex + stLength,
 526   
                                                                 endIndex);
 527   
                                 //If there is a white space or = or :, then
 528   
                                 //it isn't to be treated as a valid key.
 529  0
                                 for (int k = 0; k < matches.length(); k++) {
 530  0
                                     char c = matches.charAt(k);
 531  0
                                     if (c == ':' ||
 532   
                                         c == '=' ||
 533   
                                         Character.isSpaceChar(c)) {
 534  0
                                         endIndex = endIndex - 1;
 535  0
                                         continue outer;
 536   
                                     }
 537   
                                 }
 538  0
                                 String replace = null;
 539  0
                                 replace = (String) resourceMap.get(matches);
 540   
                                     //If the key hasn't been loaded into resourceMap,
 541   
                                     //use the key itself as the value also.
 542  0
                                 if (replace == null) {
 543  0
                                     log("Warning: The key: " + matches
 544   
                                         + " hasn't been defined.",
 545   
                                         Project.MSG_DEBUG);
 546  0
                                     replace = matches;
 547   
                                 }
 548  0
                                 line = line.substring(0, startIndex)
 549   
                                     + replace
 550   
                                     + line.substring(endIndex + etLength);
 551  0
                                 endIndex = startIndex + replace.length() + etLength;
 552  0
                                 if (endIndex + etLength >= line.length()) {
 553  0
                                     break;
 554   
                                 }
 555   
                             }
 556  0
                             out.write(line);
 557  0
                             out.newLine();
 558   
                         }
 559  0
                         if (in != null) {
 560  0
                             in.close();
 561   
                         }
 562  0
                         if (out != null) {
 563  0
                             out.close();
 564   
                         }
 565   
                     } else {
 566  0
                         log("Skipping " + srcFiles[j] +
 567   
                             " as destination file is up to date",
 568   
                             Project.MSG_VERBOSE);
 569   
                     }
 570   
                 } catch (IOException ioe) {
 571  0
                     throw new BuildException(ioe.getMessage(), getLocation());
 572   
                 }
 573   
             }
 574   
         }
 575   
     }
 576   
 }
 577