View Javadoc

1   package com.imcode.ant.tasks;
2   
3   import com.imcode.util.LineReader;
4   import org.apache.tools.ant.BuildException;
5   import org.apache.tools.ant.DirectoryScanner;
6   import org.apache.tools.ant.Project;
7   import org.apache.tools.ant.Task;
8   import org.apache.tools.ant.types.FileSet;
9   import org.apache.tools.ant.types.FilterSet;
10  import org.apache.tools.ant.types.FilterSetCollection;
11  import org.apache.tools.ant.util.FileUtils;
12  import org.apache.commons.lang.StringUtils;
13  
14  import java.io.*;
15  import java.util.*;
16  
17  /***
18   * @author kreiger
19   */
20  public class Translate extends Task {
21  
22      private File toDir;
23  
24      private String startToken;
25      private String endToken;
26  
27      private File bundle;
28      private String bundleLanguage;
29      private List fileSets = new ArrayList();
30  
31      private int verbosity = Project.MSG_VERBOSE;
32  
33      private boolean filtering;
34      private List filterSets = new ArrayList();
35  
36      private FileUtils fileUtils = FileUtils.newFileUtils();
37      private Properties properties = new Properties();
38      private long propertiesFileLastModified;
39      private File propertiesFile;
40  
41      public void addFileSet( FileSet fileSet ) {
42          fileSets.add( fileSet );
43      }
44  
45      public void setToDir( File toDir ) {
46          this.toDir = toDir;
47      }
48  
49      public void setBundle( File bundle ) {
50          this.bundle = bundle;
51      }
52  
53      public void setBundleLanguage( String bundleLanguage ) {
54          this.bundleLanguage = bundleLanguage;
55      }
56  
57      public void setPropertiesFile( File propertiesFile ) {
58          this.propertiesFile = propertiesFile;
59      }
60  
61      public void setStartToken( String startToken ) {
62          this.startToken = startToken;
63      }
64  
65      public void setEndToken( String endToken ) {
66          this.endToken = endToken;
67      }
68  
69      public void setFiltering( boolean filtering ) {
70          this.filtering = filtering;
71      }
72  
73      /***
74       * Create a nested filterset
75       */
76      public FilterSet createFilterSet() {
77          FilterSet filterSet = new FilterSet();
78          filterSets.add( filterSet );
79          return filterSet;
80      }
81  
82      /***
83       * Used to force listing of all names of copied files.
84       */
85      public void setVerbose( boolean verbose ) {
86          if ( verbose ) {
87              verbosity = Project.MSG_INFO;
88          } else {
89              verbosity = Project.MSG_VERBOSE;
90          }
91      }
92  
93      public void execute() {
94          validateAttributes();
95          loadBundle();
96          FilterSetCollection filters = getFilterSetCollection();
97          translateFiles( filters );
98      }
99  
100     private void translateFiles( FilterSetCollection filters ) {
101         for ( Iterator it = fileSets.iterator(); it.hasNext(); ) {
102             FileSet fileSet = (FileSet)it.next();
103             DirectoryScanner directoryScanner = fileSet.getDirectoryScanner( getProject() );
104             String[] sourceFiles = directoryScanner.getIncludedFiles();
105             log( "Translating " + sourceFiles.length + ( sourceFiles.length == 1
106                                                          ? " file to "
107                                                          : " files to " ) + toDir, verbosity );
108             for ( int i = 0; i < sourceFiles.length; i++ ) {
109                 String sourcePath = sourceFiles[i];
110                 File destFile = fileUtils.resolveFile( toDir, sourcePath );
111                 File destDir = destFile.getParentFile();
112                 if ( !destDir.exists() ) {
113                     if ( !destDir.mkdirs() ) {
114                         throw new BuildException( "Failed to create directory " + destDir );
115                     }
116                 }
117                 File sourceFile = fileUtils.resolveFile( directoryScanner.getBasedir(), sourcePath );
118                 long destFileLastModified = destFile.lastModified();
119                 boolean needsWork = destFileLastModified < sourceFile.lastModified()
120                                     || destFileLastModified < propertiesFileLastModified;
121                 if ( needsWork ) {
122                     log( "Translating file " + sourceFile + " to file " + destFile, verbosity );
123                     translateFile( sourceFile, destFile, filters );
124                 } else {
125                     log( "Omitting translation of " + sourceFile + " as " + destFile + " is up-to-date.", verbosity );
126                 }
127             }
128         }
129     }
130 
131     private void translateFile( File sourceFile, File destFile, FilterSetCollection filters ) {
132         try {
133             InputStream sourceStream = new FileInputStream( sourceFile );
134             FileOutputStream destStream = new FileOutputStream( destFile );
135             translateStream( sourceStream, destStream, filters );
136         } catch ( IOException e ) {
137             destFile.delete();
138             throw new BuildException( e );
139         } catch ( PropertiesNotFoundException e ) {
140             destFile.delete();
141             String[] missingPropertyKeys = e.getPropertyKeys();
142             String missingPropertyKeysString = StringUtils.join(missingPropertyKeys, ", ") ;
143             String message = "\n"+sourceFile+":0: "+(missingPropertyKeys.length > 1 ? "Keys " : "Key ")+missingPropertyKeysString+" not found in " + propertiesFile;
144             throw new BuildException( message );
145         }
146     }
147 
148     private void translateStream( InputStream sourceStream, FileOutputStream destStream, FilterSetCollection filters ) throws IOException, PropertiesNotFoundException {
149         LineReader lineReader = new LineReader( new BufferedReader( new InputStreamReader( sourceStream ) ) );
150         BufferedWriter destWriter = new BufferedWriter( new OutputStreamWriter( destStream ) );
151         List propertiesNotFound = new ArrayList();
152         for ( String line; null != ( line = lineReader.readLine() ); ) {
153             try {
154                 String translatedLine = translateLine( line );
155                 String translatedAndFilteredLine = filters.replaceTokens( translatedLine );
156                 destWriter.write( translatedAndFilteredLine );
157             } catch ( PropertiesNotFoundException e ) {
158                 propertiesNotFound.addAll( Arrays.asList(e.getPropertyKeys()) );
159             }
160         }
161         destWriter.flush();
162         destWriter.close();
163         if ( !propertiesNotFound.isEmpty() ) {
164             throw new PropertiesNotFoundException( (String[])propertiesNotFound.toArray( new String[propertiesNotFound.size()] ) );
165         }
166     }
167 
168     private String translateLine( String line ) throws PropertiesNotFoundException {
169         String translatedLine = line;
170         List propertiesNotFound = new ArrayList();
171         int startTokenIndex = 0;
172         while ( -1 != ( startTokenIndex = translatedLine.indexOf( startToken, startTokenIndex ) ) ) {
173             int endTokenIndex = translatedLine.indexOf( endToken, startTokenIndex + startToken.length() );
174             if ( -1 != endTokenIndex ) {
175                 String bundleKey = translatedLine.substring( startTokenIndex + startToken.length(), endTokenIndex );
176                 String bundleValue = properties.getProperty( bundleKey );
177                 if ( null == bundleValue ) {
178                     bundleValue = bundleKey;
179                     propertiesNotFound.add( bundleKey );
180                 } else {
181                     log( "Replacing key " + bundleKey + " with value " + bundleValue, Project.MSG_DEBUG );
182                     translatedLine = translatedLine.substring( 0, startTokenIndex ) + bundleValue
183                                      + translatedLine.substring( endTokenIndex + endToken.length() );
184                 }
185                 startTokenIndex += bundleValue.length();
186             } else {
187                 break;
188             }
189         }
190         if (!propertiesNotFound.isEmpty()) {
191             throw new PropertiesNotFoundException( (String[])propertiesNotFound.toArray( new String[propertiesNotFound.size()] ) );
192         }
193         return translatedLine;
194     }
195 
196     private void loadBundle() {
197         if (null == propertiesFile ) {
198             propertiesFile = new File( bundle.getPath() + '_' + bundleLanguage + ".properties" );
199         }
200         propertiesFileLastModified = propertiesFile.lastModified();
201         try {
202             properties.load( new FileInputStream( propertiesFile ) );
203         } catch ( IOException e ) {
204             throw new BuildException( e.getMessage(), e );
205         }
206     }
207 
208     private void validateAttributes() {
209         if ( fileSets.size() == 0 ) {
210             throw new BuildException( "Specify at least one fileset." );
211         }
212 
213         if ( null == toDir ) {
214             throw new BuildException( "The todir attribute must be set." );
215         }
216 
217         if ( null == bundle == (null == propertiesFile) ) {
218             throw new BuildException( "One of the bundle or the propertiesFile attributes must be set, but not both." );
219         }
220 
221         if ( null == bundleLanguage ) {
222             bundleLanguage = Locale.getDefault().getLanguage();
223         }
224 
225     }
226 
227     /***
228      * Handle the filtersets. *
229      */
230     private FilterSetCollection getFilterSetCollection() {
231         // handle filters
232         FilterSetCollection executionFilters = new FilterSetCollection();
233         if ( filtering ) {
234             executionFilters.addFilterSet( getProject().getGlobalFilterSet() );
235         }
236         for ( Iterator filterEnum = filterSets.iterator(); filterEnum.hasNext(); ) {
237             executionFilters.addFilterSet( (FilterSet)filterEnum.next() );
238         }
239         return executionFilters;
240     }
241 
242     private static class PropertiesNotFoundException extends Exception {
243 
244         private String[] propertyKeys;
245 
246         private PropertiesNotFoundException( String[] propertyKeys ) {
247             this.propertyKeys = propertyKeys;
248         }
249 
250         public String[] getPropertyKeys() {
251             return propertyKeys;
252         }
253 
254     }
255 }