001: /*
002: * Copyright 2000-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: *
016: * Contributor: Shenheng Liang
017: */
018:
019: package org.objectweb.jonas.ant.jonasbase;
020:
021: import java.io.BufferedReader;
022: import java.io.BufferedWriter;
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.FileNotFoundException;
026: import java.io.FileOutputStream;
027: import java.io.FileReader;
028: import java.io.FileWriter;
029: import java.io.IOException;
030: import java.io.InputStreamReader;
031: import java.io.OutputStreamWriter;
032: import java.io.Reader;
033: import java.io.Writer;
034: import java.util.Enumeration;
035: import java.util.Properties;
036: import java.util.Vector;
037: import org.apache.tools.ant.BuildException;
038: import org.apache.tools.ant.DirectoryScanner;
039: import org.apache.tools.ant.Project;
040: import org.apache.tools.ant.taskdefs.Delete;
041: import org.apache.tools.ant.taskdefs.MatchingTask;
042: import org.apache.tools.ant.taskdefs.Move;
043: import org.apache.tools.ant.util.FileUtils;
044: import org.apache.tools.ant.util.StringUtils;
045:
046: /**
047: * Replaces all occurrences of one or more string tokens with given
048: * values in the indicated files. Each value can be either a string
049: * or the value of a property available in a designated property file.
050: * If you want to replace a text that crosses line boundaries, you
051: * must use a nested <code><replacetoken></code> element.
052: *
053: * @since Ant 1.1
054: *
055: * @ant.task category="filesystem"
056: */
057: public class Replace extends MatchingTask {
058:
059: private File src = null;
060: private NestedString token = null;
061: private NestedString value = new NestedString();
062:
063: private File propertyFile = null;
064: private File replaceFilterFile = null;
065: private Properties properties = null;
066: private Vector replacefilters = new Vector();
067:
068: private File dir = null;
069:
070: private int fileCount;
071: private int replaceCount;
072: private boolean summary = false;
073:
074: /** The encoding used to read and write files - if null, uses default */
075: private String encoding = null;
076:
077: private FileUtils fileUtils = FileUtils.newFileUtils();
078:
079: /**
080: * an inline string to use as the replacement text
081: */
082: public class NestedString {
083:
084: private StringBuffer buf = new StringBuffer();
085:
086: /**
087: * the text of the element
088: *
089: * @param val the string to add
090: */
091: public void addText(String val) {
092: buf.append(val);
093: }
094:
095: /**
096: * @return the text
097: */
098: public String getText() {
099: return buf.substring(0);
100: }
101: }
102:
103: /**
104: * A filter to apply.
105: */
106: public class Replacefilter {
107: private String token;
108: private String value;
109: private String property;
110:
111: /**
112: * validate the filter's configuration
113: * @throws BuildException if any part is invalid
114: */
115: public void validate() throws BuildException {
116: //Validate mandatory attributes
117: if (token == null) {
118: String message = "token is a mandatory attribute "
119: + "of replacefilter.";
120: throw new BuildException(message);
121: }
122:
123: if ("".equals(token)) {
124: String message = "The token attribute must not be an empty "
125: + "string.";
126: throw new BuildException(message);
127: }
128:
129: //value and property are mutually exclusive attributes
130: if ((value != null) && (property != null)) {
131: String message = "Either value or property "
132: + "can be specified, but a replacefilter "
133: + "element cannot have both.";
134: throw new BuildException(message);
135: }
136:
137: if ((property != null)) {
138: //the property attribute must have access to a property file
139: if (propertyFile == null) {
140: String message = "The replacefilter's property attribute "
141: + "can only be used with the replacetask's "
142: + "propertyFile attribute.";
143: throw new BuildException(message);
144: }
145:
146: //Make sure property exists in property file
147: if (properties == null
148: || properties.getProperty(property) == null) {
149: String message = "property \"" + property
150: + "\" was not found in "
151: + propertyFile.getPath();
152: throw new BuildException(message);
153: }
154: }
155: }
156:
157: /**
158: * Get the replacement value for this filter token.
159: * @return the replacement value
160: */
161: public String getReplaceValue() {
162: if (property != null) {
163: return properties.getProperty(property);
164: } else if (value != null) {
165: return value;
166: } else if (Replace.this .value != null) {
167: return Replace.this .value.getText();
168: } else {
169: //Default is empty string
170: return new String("");
171: }
172: }
173:
174: /**
175: * Set the token to replace
176: * @param token token
177: */
178: public void setToken(String token) {
179: this .token = token;
180: }
181:
182: /**
183: * Get the string to search for
184: * @return current token
185: */
186: public String getToken() {
187: return token;
188: }
189:
190: /**
191: * The replacement string; required if <code>property<code>
192: * is not set
193: * @param value value to replace
194: */
195: public void setValue(String value) {
196: this .value = value;
197: }
198:
199: /**
200: * Get replacements string
201: * @return replacement or null
202: */
203: public String getValue() {
204: return value;
205: }
206:
207: /**
208: * Set the name of the property whose value is to serve as
209: * the replacement value; required if <code>value</code> is not set.
210: * @param property propname
211: */
212: public void setProperty(String property) {
213: this .property = property;
214: }
215:
216: /**
217: * Get the name of the property whose value is to serve as
218: * the replacement value;
219: * @return property or null
220: */
221: public String getProperty() {
222: return property;
223: }
224: }
225:
226: /**
227: * Do the execution.
228: * @throws BuildException if we cant build
229: */
230: public void execute() throws BuildException {
231:
232: Vector savedFilters = (Vector) replacefilters.clone();
233: Properties savedProperties = properties == null ? null
234: : (Properties) properties.clone();
235:
236: try {
237: if (replaceFilterFile != null) {
238: Properties props = getProperties(replaceFilterFile);
239: Enumeration e = props.keys();
240: while (e.hasMoreElements()) {
241: String token = e.nextElement().toString();
242: Replacefilter replaceFilter = createReplacefilter();
243: replaceFilter.setToken(token);
244: replaceFilter.setValue(props.getProperty(token));
245: }
246: }
247:
248: validateAttributes();
249:
250: if (propertyFile != null) {
251: properties = getProperties(propertyFile);
252: }
253:
254: validateReplacefilters();
255: fileCount = 0;
256: replaceCount = 0;
257:
258: if (src != null) {
259: processFile(src);
260: }
261:
262: if (dir != null) {
263: DirectoryScanner ds = super .getDirectoryScanner(dir);
264: String[] srcs = ds.getIncludedFiles();
265:
266: for (int i = 0; i < srcs.length; i++) {
267: File file = new File(dir, srcs[i]);
268: processFile(file);
269: }
270: }
271:
272: if (summary) {
273: log("Replaced " + replaceCount + " occurrences in "
274: + fileCount + " files.", Project.MSG_INFO);
275: }
276: } finally {
277: replacefilters = savedFilters;
278: properties = savedProperties;
279: } // end of finally
280:
281: }
282:
283: /**
284: * Validate attributes provided for this task in .xml build file.
285: *
286: * @exception BuildException if any supplied attribute is invalid or any
287: * mandatory attribute is missing
288: */
289: public void validateAttributes() throws BuildException {
290: if (src == null && dir == null) {
291: String message = "Either the file or the dir attribute "
292: + "must be specified";
293: throw new BuildException(message, getLocation());
294: }
295: if (propertyFile != null && !propertyFile.exists()) {
296: String message = "Property file " + propertyFile.getPath()
297: + " does not exist.";
298: throw new BuildException(message, getLocation());
299: }
300: if (token == null && replacefilters.size() == 0) {
301: String message = "Either token or a nested replacefilter "
302: + "must be specified";
303: throw new BuildException(message, getLocation());
304: }
305: if (token != null && "".equals(token.getText())) {
306: String message = "The token attribute must not be an empty string.";
307: throw new BuildException(message, getLocation());
308: }
309: }
310:
311: /**
312: * Validate nested elements.
313: *
314: * @exception BuildException if any supplied attribute is invalid or any
315: * mandatory attribute is missing
316: */
317: public void validateReplacefilters() throws BuildException {
318: for (int i = 0; i < replacefilters.size(); i++) {
319: Replacefilter element = (Replacefilter) replacefilters
320: .elementAt(i);
321: element.validate();
322: }
323: }
324:
325: /**
326: * helper method to load a properties file and throw a build exception
327: * if it cannot be loaded
328: * @param propertyFile the file to load the properties from
329: * @return loaded properties collection
330: * @throws BuildException if the file could not be found or read
331: */
332: public Properties getProperties(File propertyFile)
333: throws BuildException {
334: Properties properties = new Properties();
335:
336: FileInputStream in = null;
337: try {
338: in = new FileInputStream(propertyFile);
339: properties.load(in);
340: } catch (FileNotFoundException e) {
341: String message = "Property file (" + propertyFile.getPath()
342: + ") not found.";
343: throw new BuildException(message);
344: } catch (IOException e) {
345: String message = "Property file (" + propertyFile.getPath()
346: + ") cannot be loaded.";
347: throw new BuildException(message);
348: } finally {
349: if (in != null) {
350: try {
351: in.close();
352: } catch (IOException e) {
353: // ignore
354: }
355: }
356: }
357:
358: return properties;
359: }
360:
361: /**
362: * Perform the replacement on the given file.
363: *
364: * The replacement is performed on a temporary file which then
365: * replaces the original file.
366: *
367: * Re-implement the rename part.
368: *
369: * @author Shenheng Liang
370: * @param src the source file
371: */
372: private void processFile(File src) throws BuildException {
373: if (!src.exists()) {
374: throw new BuildException("Replace: source file "
375: + src.getPath() + " doesn't exist", getLocation());
376: }
377:
378: File temp = fileUtils.createTempFile("rep", ".tmp", fileUtils
379: .getParentFile(src));
380: temp.deleteOnExit();
381:
382: Reader reader = null;
383: Writer writer = null;
384: try {
385: reader = encoding == null ? new FileReader(src)
386: : new InputStreamReader(new FileInputStream(src),
387: encoding);
388: writer = encoding == null ? new FileWriter(temp)
389: : new OutputStreamWriter(
390: new FileOutputStream(temp), encoding);
391:
392: BufferedReader br = new BufferedReader(reader);
393: BufferedWriter bw = new BufferedWriter(writer);
394:
395: String buf = fileUtils.readFully(br);
396: if (buf == null) {
397: buf = "";
398: }
399:
400: //Preserve original string (buf) so we can compare the result
401: String newString = new String(buf);
402:
403: if (token != null) {
404: // line separators in values and tokens are "\n"
405: // in order to compare with the file contents, replace them
406: // as needed
407: String val = stringReplace(value.getText(), "\r\n",
408: "\n", false);
409: val = stringReplace(val, "\n", StringUtils.LINE_SEP,
410: false);
411: String tok = stringReplace(token.getText(), "\r\n",
412: "\n", false);
413: tok = stringReplace(tok, "\n", StringUtils.LINE_SEP,
414: false);
415:
416: // for each found token, replace with value
417: log("Replacing in " + src.getPath() + ": "
418: + token.getText() + " --> " + value.getText(),
419: Project.MSG_VERBOSE);
420: newString = stringReplace(newString, tok, val, true);
421: }
422:
423: if (replacefilters.size() > 0) {
424: newString = processReplacefilters(newString, src
425: .getPath());
426: }
427:
428: boolean changes = !newString.equals(buf);
429: if (changes) {
430: bw.write(newString, 0, newString.length());
431: bw.flush();
432: }
433:
434: // cleanup
435: bw.close();
436: writer = null;
437: br.close();
438: reader = null;
439:
440: // If there were changes, move the new one to the old one;
441: // otherwise, delete the new one
442: if (changes) {
443: ++fileCount;
444:
445: //The original implementation of renaming file
446: //fileUtils.rename(temp, src);
447: //end of original implementation
448:
449: //The new implemention of renaming file
450: Project tempProject = new Project();
451: Delete delete = new Delete();
452: delete.setProject(tempProject);
453: delete.setFile(src);
454: delete.execute();
455: Move mv = new Move();
456: mv.setProject(tempProject);
457: mv.setFile(temp);
458: mv.setTofile(src);
459: mv.execute();
460: //end of the modification
461:
462: temp = null;
463: }
464: } catch (IOException ioe) {
465: throw new BuildException(
466: "IOException in " + src + " - "
467: + ioe.getClass().getName() + ":"
468: + ioe.getMessage(), ioe, getLocation());
469: } finally {
470: if (reader != null) {
471: try {
472: reader.close();
473: } catch (IOException e) {
474: // ignore
475: }
476: }
477: if (writer != null) {
478: try {
479: writer.close();
480: } catch (IOException e) {
481: // ignore
482: }
483: }
484: if (temp != null) {
485: temp.delete();
486: }
487: }
488:
489: }
490:
491: /**
492: * apply all replace filters to a buffer
493: * @param buffer string to filter
494: * @param filename filename for logging purposes
495: * @return filtered string
496: */
497: private String processReplacefilters(String buffer, String filename) {
498: String newString = new String(buffer);
499:
500: for (int i = 0; i < replacefilters.size(); i++) {
501: Replacefilter filter = (Replacefilter) replacefilters
502: .elementAt(i);
503:
504: //for each found token, replace with value
505: log("Replacing in " + filename + ": " + filter.getToken()
506: + " --> " + filter.getReplaceValue(),
507: Project.MSG_VERBOSE);
508: newString = stringReplace(newString, filter.getToken(),
509: filter.getReplaceValue(), true);
510: }
511:
512: return newString;
513: }
514:
515: /**
516: * Set the source file; required unless <code>dir</code> is set.
517: * @param file source file
518: */
519: public void setFile(File file) {
520: this .src = file;
521: }
522:
523: /**
524: * Indicates whether a summary of the replace operation should be
525: * produced, detailing how many token occurrences and files were
526: * processed; optional, default=false
527: *
528: * @param summary true if you would like a summary logged of the
529: * replace operation
530: */
531: public void setSummary(boolean summary) {
532: this .summary = summary;
533: }
534:
535: /**
536: * Sets the name of a property file containing filters; optional.
537: * Each property will be treated as a
538: * replacefilter where token is the name of the property and value
539: * is the value of the property.
540: * @param filename file to load
541: */
542: public void setReplaceFilterFile(File filename) {
543: replaceFilterFile = filename;
544: }
545:
546: /**
547: * The base directory to use when replacing a token in multiple files;
548: * required if <code>file</code> is not defined.
549: * @param dir base dir
550: */
551: public void setDir(File dir) {
552: this .dir = dir;
553: }
554:
555: /**
556: * Set the string token to replace;
557: * required unless a nested
558: * <code>replacetoken</code> element or the <code>replacefilterfile</code>
559: * attribute is used.
560: * @param token token string
561: */
562: public void setToken(String token) {
563: createReplaceToken().addText(token);
564: }
565:
566: /**
567: * Set the string value to use as token replacement;
568: * optional, default is the empty string ""
569: * @param value replacement value
570: */
571: public void setValue(String value) {
572: createReplaceValue().addText(value);
573: }
574:
575: /**
576: * Set the file encoding to use on the files read and written by the task;
577: * optional, defaults to default JVM encoding
578: *
579: * @param encoding the encoding to use on the files
580: */
581: public void setEncoding(String encoding) {
582: this .encoding = encoding;
583: }
584:
585: /**
586: * the token to filter as the text of a nested element
587: * @return nested token to configure
588: */
589: public NestedString createReplaceToken() {
590: if (token == null) {
591: token = new NestedString();
592: }
593: return token;
594: }
595:
596: /**
597: * the string to replace the token as the text of a nested element
598: * @return replacement value to configure
599: */
600: public NestedString createReplaceValue() {
601: return value;
602: }
603:
604: /**
605: * The name of a property file from which properties specified using
606: * nested <code><replacefilter></code> elements are drawn;
607: * Required only if <i>property</i> attribute of
608: * <code><replacefilter></code> is used.
609: * @param filename file to load
610: */
611: public void setPropertyFile(File filename) {
612: propertyFile = filename;
613: }
614:
615: /**
616: * Add a nested <replacefilter> element.
617: * @return a nested ReplaceFilter object to be configured
618: */
619: public Replacefilter createReplacefilter() {
620: Replacefilter filter = new Replacefilter();
621: replacefilters.addElement(filter);
622: return filter;
623: }
624:
625: /**
626: * Replace occurrences of str1 in string str with str2
627: */
628: private String stringReplace(String str, String str1, String str2,
629: boolean countReplaces) {
630: StringBuffer ret = new StringBuffer();
631: int start = 0;
632: int found = str.indexOf(str1);
633: while (found >= 0) {
634: // write everything up to the found str1
635: if (found > start) {
636: ret.append(str.substring(start, found));
637: }
638:
639: // write the replacement str2
640: if (str2 != null) {
641: ret.append(str2);
642: }
643:
644: // search again
645: start = found + str1.length();
646: found = str.indexOf(str1, start);
647: if (countReplaces) {
648: ++replaceCount;
649: }
650: }
651:
652: // write the remaining characters
653: if (str.length() > start) {
654: ret.append(str.substring(start, str.length()));
655: }
656:
657: return ret.toString();
658: }
659:
660: }
|