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
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 }