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 import java.nio.charset.Charset;
17
18 /*** @author kreiger */
19 public class Translate extends Task {
20
21 private File toDir;
22
23 private String startToken;
24 private String endToken;
25
26 private File bundle;
27 private String bundleLanguage;
28 private List fileSets = new ArrayList();
29
30 private int verbosity = Project.MSG_VERBOSE;
31
32 private boolean filtering;
33 private List filterSets = new ArrayList();
34
35 private FileUtils fileUtils = FileUtils.newFileUtils();
36 private Properties properties = new Properties();
37 private long propertiesFileLastModified;
38 private File propertiesFile;
39 private String encoding;
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 public void setEncoding(String encoding) {
74 this.encoding = encoding;
75 }
76
77 /*** Create a nested filterset */
78 public FilterSet createFilterSet() {
79 FilterSet filterSet = new FilterSet();
80 filterSets.add(filterSet);
81 return filterSet;
82 }
83
84 /*** Used to force listing of all names of copied files. */
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 void setProperties(Properties properties) {
94 this.properties = properties;
95 }
96
97 public void execute() {
98 validateAttributes();
99 loadBundle();
100 FilterSetCollection filters = getFilterSetCollection();
101 translateFiles(filters);
102 }
103
104 private void translateFiles(FilterSetCollection filters) {
105 for ( Iterator it = fileSets.iterator(); it.hasNext(); ) {
106 FileSet fileSet = (FileSet) it.next();
107 DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject());
108 String[] sourceFiles = directoryScanner.getIncludedFiles();
109 log("Translating " + sourceFiles.length + ( sourceFiles.length == 1
110 ? " file to "
111 : " files to " ) + toDir, verbosity);
112 for ( int i = 0; i < sourceFiles.length; i++ ) {
113 String sourcePath = sourceFiles[i];
114 File destFile = fileUtils.resolveFile(toDir, sourcePath);
115 File destDir = destFile.getParentFile();
116 if ( !destDir.exists() ) {
117 if ( !destDir.mkdirs() ) {
118 throw new BuildException("Failed to create directory " + destDir);
119 }
120 }
121 File sourceFile = fileUtils.resolveFile(directoryScanner.getBasedir(), sourcePath);
122 long destFileLastModified = destFile.lastModified();
123 boolean needsWork = destFileLastModified < sourceFile.lastModified()
124 || destFileLastModified < propertiesFileLastModified;
125 if ( needsWork ) {
126 log("Translating file " + sourceFile + " to file " + destFile, verbosity);
127 translateFile(sourceFile, destFile, filters);
128 } else {
129 log("Omitting translation of " + sourceFile + " as " + destFile + " is up-to-date.", verbosity);
130 }
131 }
132 }
133 }
134
135 private void translateFile(File sourceFile, File destFile, FilterSetCollection filters) {
136 try {
137 InputStream sourceStream = new FileInputStream(sourceFile);
138 FileOutputStream destStream = new FileOutputStream(destFile);
139 translateStream(sourceStream, destStream, filters);
140 } catch ( IOException e ) {
141 destFile.delete();
142 throw new BuildException(e);
143 } catch ( PropertiesNotFoundException e ) {
144 destFile.delete();
145 String[] missingPropertyKeys = e.getPropertyKeys();
146 String missingPropertyKeysString = StringUtils.join(missingPropertyKeys, ", ");
147 String message = "\n" + sourceFile + ":0: " + ( missingPropertyKeys.length > 1 ? "Keys " : "Key " )
148 + missingPropertyKeysString + " not found in " + propertiesFile;
149 throw new BuildException(message);
150 }
151 }
152
153 void translateStream(InputStream sourceStream, OutputStream destStream,
154 FilterSetCollection filters) throws IOException, PropertiesNotFoundException {
155 InputStreamReader inputStreamReader;
156 OutputStreamWriter outputStreamWriter;
157 if ( null != encoding ) {
158 inputStreamReader = new InputStreamReader(sourceStream, encoding);
159 outputStreamWriter = new OutputStreamWriter(destStream, encoding);
160 } else {
161 inputStreamReader = new InputStreamReader(sourceStream);
162 outputStreamWriter = new OutputStreamWriter(destStream);
163 }
164 LineReader lineReader = new LineReader(new BufferedReader(inputStreamReader));
165 BufferedWriter destWriter = new BufferedWriter(outputStreamWriter);
166 List propertiesNotFound = new ArrayList();
167 for ( String line; null != ( line = lineReader.readLine() ); ) {
168 try {
169 String translatedLine = translateLine(line);
170 String translatedAndFilteredLine = filters.replaceTokens(translatedLine);
171 destWriter.write(translatedAndFilteredLine);
172 } catch ( PropertiesNotFoundException e ) {
173 propertiesNotFound.addAll(Arrays.asList(e.getPropertyKeys()));
174 }
175 }
176 destWriter.flush();
177 destWriter.close();
178 if ( !propertiesNotFound.isEmpty() ) {
179 throw new PropertiesNotFoundException((String[]) propertiesNotFound.toArray(new String[propertiesNotFound.size()]));
180 }
181 }
182
183 private String translateLine(String line) throws PropertiesNotFoundException {
184 String translatedLine = line;
185 List propertiesNotFound = new ArrayList();
186 int startTokenIndex = 0;
187 while ( -1 != ( startTokenIndex = translatedLine.indexOf(startToken, startTokenIndex) ) ) {
188 int endTokenIndex = translatedLine.indexOf(endToken, startTokenIndex + startToken.length());
189 if ( -1 != endTokenIndex ) {
190 String bundleKey = translatedLine.substring(startTokenIndex + startToken.length(), endTokenIndex);
191 String bundleValue = properties.getProperty(bundleKey);
192 if ( null == bundleValue ) {
193 bundleValue = bundleKey;
194 propertiesNotFound.add(bundleKey);
195 } else {
196 log("Replacing key " + bundleKey + " with value " + bundleValue, Project.MSG_DEBUG);
197 translatedLine = translatedLine.substring(0, startTokenIndex) + bundleValue
198 + translatedLine.substring(endTokenIndex + endToken.length());
199 }
200 startTokenIndex += bundleValue.length();
201 } else {
202 break;
203 }
204 }
205 if ( !propertiesNotFound.isEmpty() ) {
206 throw new PropertiesNotFoundException((String[]) propertiesNotFound.toArray(new String[propertiesNotFound.size()]));
207 }
208 return translatedLine;
209 }
210
211 private void loadBundle() {
212 if ( null == propertiesFile ) {
213 propertiesFile = new File(bundle.getPath() + '_' + bundleLanguage + ".properties");
214 }
215 propertiesFileLastModified = propertiesFile.lastModified();
216 try {
217 properties.load(new FileInputStream(propertiesFile));
218 } catch ( IOException e ) {
219 throw new BuildException(e.getMessage(), e);
220 }
221 }
222
223 private void validateAttributes() {
224 if ( fileSets.size() == 0 ) {
225 throw new BuildException("Specify at least one fileset.");
226 }
227
228 if ( null == toDir ) {
229 throw new BuildException("The todir attribute must be set.");
230 }
231
232 if ( null == bundle == ( null == propertiesFile ) ) {
233 throw new BuildException("One of the bundle or the propertiesFile attributes must be set, but not both.");
234 }
235
236 if ( null == bundleLanguage ) {
237 bundleLanguage = Locale.getDefault().getLanguage();
238 }
239
240 }
241
242 /*** Handle the filtersets. * */
243 private FilterSetCollection getFilterSetCollection() {
244
245 FilterSetCollection executionFilters = new FilterSetCollection();
246 if ( filtering ) {
247 executionFilters.addFilterSet(getProject().getGlobalFilterSet());
248 }
249 for ( Iterator filterEnum = filterSets.iterator(); filterEnum.hasNext(); ) {
250 executionFilters.addFilterSet((FilterSet) filterEnum.next());
251 }
252 return executionFilters;
253 }
254
255 private static class PropertiesNotFoundException extends Exception {
256
257 private String[] propertyKeys;
258
259 private PropertiesNotFoundException(String[] propertyKeys) {
260 this.propertyKeys = propertyKeys;
261 }
262
263 public String[] getPropertyKeys() {
264 return propertyKeys;
265 }
266
267 }
268 }