You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Replace.java 17 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 1999 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 "The Jakarta Project", "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;
  55. import org.apache.tools.ant.*;
  56. import org.apache.tools.ant.taskdefs.*;
  57. import java.io.*;
  58. import java.util.*;
  59. /**
  60. * Replaces all occurrences of one or more string tokens with given
  61. * values in the indicated files. Each value can be either a string
  62. * or the value of a property available in a designated property file.
  63. *
  64. * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">stefano@apache.org</a>
  65. * @author <a href="mailto:erik@desknetinc.com">Erik Langenbach</a>
  66. */
  67. public class Replace extends MatchingTask {
  68. private File src = null;
  69. private NestedString token = null;
  70. private NestedString value = new NestedString();
  71. private File propertyFile = null;
  72. private Properties properties = null;
  73. private Vector replacefilters = new Vector();
  74. private File dir = null;
  75. private int fileCount;
  76. private int replaceCount;
  77. private boolean summary = false;
  78. /** The encoding used to read and write files - if null, uses default */
  79. private String encoding = null;
  80. //Inner class
  81. public class NestedString {
  82. private StringBuffer buf = new StringBuffer();
  83. public void addText(String val) {
  84. buf.append(val);
  85. }
  86. public String getText() {
  87. return buf.toString();
  88. }
  89. }
  90. //Inner class
  91. public class Replacefilter
  92. {
  93. private String token;
  94. private String value;
  95. private String property;
  96. public void validate() throws BuildException {
  97. //Validate mandatory attributes
  98. if (token == null) {
  99. String message = "token is a mandatory attribute " + "of replacefilter.";
  100. throw new BuildException(message);
  101. }
  102. if ("".equals(token)) {
  103. String message ="The token attribute must not be an empty string.";
  104. throw new BuildException(message);
  105. }
  106. //value and property are mutually exclusive attributes
  107. if ((value != null) && (property != null)) {
  108. String message = "Either value or property " + "can be specified, but a replacefilter " + "element cannot have both.";
  109. throw new BuildException(message);
  110. }
  111. if ((property != null)) {
  112. //the property attribute must have access to a property file
  113. if (propertyFile == null) {
  114. String message = "The replacefilter's property attribute " + "can only be used with the replacetask's " + "propertyFile attribute.";
  115. throw new BuildException(message);
  116. }
  117. //Make sure property exists in property file
  118. if (properties == null ||
  119. properties.getProperty(property) == null) {
  120. String message = "property \"" + property + "\" was not found in " + propertyFile.getPath();
  121. throw new BuildException(message);
  122. }
  123. }
  124. }
  125. public String getReplaceValue()
  126. {
  127. if (property != null) {
  128. return (String)properties.getProperty(property);
  129. }
  130. else if (value != null) {
  131. return value;
  132. }
  133. else if (Replace.this.value != null) {
  134. return Replace.this.value.getText();
  135. }
  136. else {
  137. //Default is empty string
  138. return new String("");
  139. }
  140. }
  141. public void setToken(String token) {
  142. this.token = token;
  143. }
  144. public String getToken() {
  145. return token;
  146. }
  147. public void setValue(String value) {
  148. this.value = value;
  149. }
  150. public String getValue() {
  151. return value;
  152. }
  153. public void setProperty(String property) {
  154. this.property = property;
  155. }
  156. public String getProperty() {
  157. return property;
  158. }
  159. }
  160. /**
  161. * Do the execution.
  162. */
  163. public void execute() throws BuildException {
  164. validateAttributes();
  165. if (propertyFile != null) {
  166. properties = getProperties(propertyFile);
  167. }
  168. validateReplacefilters();
  169. fileCount = 0;
  170. replaceCount = 0;
  171. if (src != null) {
  172. processFile(src);
  173. }
  174. if (dir != null) {
  175. DirectoryScanner ds = super.getDirectoryScanner(dir);
  176. String[] srcs = ds.getIncludedFiles();
  177. for(int i=0; i<srcs.length; i++) {
  178. File file = new File(dir,srcs[i]);
  179. processFile(file);
  180. }
  181. }
  182. if (summary) {
  183. log("Replaced " + replaceCount + " occurrences in " + fileCount + " files.", Project.MSG_INFO);
  184. }
  185. }
  186. /**
  187. * Validate attributes provided for this task in .xml build file.
  188. *
  189. * @exception BuildException if any supplied attribute is invalid or any
  190. * mandatory attribute is missing
  191. */
  192. public void validateAttributes() throws BuildException {
  193. if (src == null && dir == null) {
  194. String message = "Either the file or the dir attribute " + "must be specified";
  195. throw new BuildException(message, location);
  196. }
  197. if (propertyFile != null && !propertyFile.exists()) {
  198. String message = "Property file " + propertyFile.getPath() + " does not exist.";
  199. throw new BuildException(message, location);
  200. }
  201. if (token == null && replacefilters.size() == 0) {
  202. String message = "Either token or a nested replacefilter "
  203. + "must be specified";
  204. throw new BuildException(message, location);
  205. }
  206. if (token != null && "".equals(token.getText())) {
  207. String message ="The token attribute must not be an empty string.";
  208. throw new BuildException(message, location);
  209. }
  210. }
  211. /**
  212. * Validate nested elements.
  213. *
  214. * @exception BuildException if any supplied attribute is invalid or any
  215. * mandatory attribute is missing
  216. */
  217. public void validateReplacefilters()
  218. throws BuildException {
  219. for (int i = 0; i < replacefilters.size(); i++) {
  220. Replacefilter element = (Replacefilter) replacefilters.elementAt(i);
  221. element.validate();
  222. }
  223. }
  224. public Properties getProperties(File propertyFile) throws BuildException {
  225. Properties properties = new Properties();
  226. try {
  227. properties.load(new FileInputStream(propertyFile));
  228. }
  229. catch (FileNotFoundException e) {
  230. String message = "Property file (" + propertyFile.getPath() + ") not found.";
  231. throw new BuildException(message);
  232. }
  233. catch (IOException e) {
  234. String message = "Property file (" + propertyFile.getPath() + ") cannot be loaded.";
  235. throw new BuildException(message);
  236. }
  237. return properties;
  238. }
  239. /**
  240. * Perform the replacement on the given file.
  241. *
  242. * The replacement is performed on a temporary file which then
  243. * replaces the original file.
  244. *
  245. * @param src the source file
  246. */
  247. private void processFile(File src) throws BuildException {
  248. if (!src.exists()) {
  249. throw new BuildException("Replace: source file " + src.getPath() + " doesn't exist", location);
  250. }
  251. File temp = new File(src.getPath() + ".temp");
  252. if (temp.exists()) {
  253. throw new BuildException("Replace: temporary file " + temp.getPath() + " already exists", location);
  254. }
  255. try {
  256. Reader fileReader = encoding == null ? new FileReader(src)
  257. : new InputStreamReader(new FileInputStream(src), encoding);
  258. Writer fileWriter = encoding == null ? new FileWriter(temp)
  259. : new OutputStreamWriter(new FileOutputStream(temp), encoding);
  260. BufferedReader br = new BufferedReader(fileReader);
  261. BufferedWriter bw = new BufferedWriter(fileWriter);
  262. // read the entire file into a StringBuffer
  263. // size of work buffer may be bigger than needed
  264. // when multibyte characters exist in the source file
  265. // but then again, it might be smaller than needed on
  266. // platforms like Windows where length can't be trusted
  267. int fileLengthInBytes = (int)(src.length());
  268. StringBuffer tmpBuf = new StringBuffer(fileLengthInBytes);
  269. int readChar = 0;
  270. int totread = 0;
  271. while (true) {
  272. readChar = br.read();
  273. if (readChar < 0) { break; }
  274. tmpBuf.append((char)readChar);
  275. totread++;
  276. }
  277. // create a String so we can use indexOf
  278. String buf = tmpBuf.toString();
  279. //Preserve original string (buf) so we can compare the result
  280. String newString = new String(buf);
  281. if (token != null)
  282. {
  283. // line separators in values and tokens are "\n"
  284. // in order to compare with the file contents, replace them
  285. // as needed
  286. String linesep = System.getProperty("line.separator");
  287. String val = stringReplace(value.getText(), "\n", linesep);
  288. String tok = stringReplace(token.getText(), "\n", linesep);
  289. // for each found token, replace with value
  290. log("Replacing in " + src.getPath() + ": " + token.getText() + " --> " + value.getText(), Project.MSG_VERBOSE);
  291. newString = stringReplace(newString, tok, val);
  292. }
  293. if (replacefilters.size() > 0) {
  294. newString = processReplacefilters(newString, src.getPath());
  295. }
  296. boolean changes = !newString.equals(buf);
  297. if (changes) {
  298. bw.write(newString,0,newString.length());
  299. bw.flush();
  300. }
  301. // cleanup
  302. bw.close();
  303. br.close();
  304. // If there were changes, move the new one to the old one;
  305. // otherwise, delete the new one
  306. if (changes) {
  307. ++fileCount;
  308. src.delete();
  309. temp.renameTo(src);
  310. } else {
  311. temp.delete();
  312. }
  313. } catch (IOException ioe) {
  314. throw new BuildException("IOException in " + src + " - " +
  315. ioe.getClass().getName() + ":" + ioe.getMessage(), ioe, location);
  316. }
  317. }
  318. private String processReplacefilters(String buffer, String filename) {
  319. String newString = new String(buffer);
  320. for (int i = 0; i < replacefilters.size(); i++) {
  321. Replacefilter filter = (Replacefilter) replacefilters.elementAt(i);
  322. //for each found token, replace with value
  323. log("Replacing in " + filename + ": " + filter.getToken() + " --> " + filter.getReplaceValue(), Project.MSG_VERBOSE);
  324. newString = stringReplace(newString, filter.getToken(), filter.getReplaceValue());
  325. }
  326. return newString;
  327. }
  328. /**
  329. * Set the source file.
  330. */
  331. public void setFile(File file) {
  332. this.src = file;
  333. }
  334. /**
  335. * Request a summary
  336. *
  337. * @param summary true if you would like a summary logged of the replace operation
  338. */
  339. public void setSummary(boolean summary) {
  340. this.summary = summary;
  341. }
  342. /**
  343. * Set the source files path when using matching tasks.
  344. */
  345. public void setDir(File dir) {
  346. this.dir = dir;
  347. }
  348. /**
  349. * Set the string token to replace.
  350. */
  351. public void setToken(String token) {
  352. createReplaceToken().addText(token);
  353. }
  354. /**
  355. * Set the string value to use as token replacement.
  356. */
  357. public void setValue(String value) {
  358. createReplaceValue().addText(value);
  359. }
  360. /**
  361. * Set the file encoding to use on the files read and written by replace
  362. *
  363. * @param encoding the encoding to use on the files
  364. */
  365. public void setEncoding(String encoding) {
  366. this.encoding = encoding;
  367. }
  368. /**
  369. * Nested <replacetoken> element.
  370. */
  371. public NestedString createReplaceToken() {
  372. if (token == null) {
  373. token = new NestedString();
  374. }
  375. return token;
  376. }
  377. /**
  378. * Nested <replacevalue> element.
  379. */
  380. public NestedString createReplaceValue() {
  381. return value;
  382. }
  383. /**
  384. * Sets a file to be searched for property values.
  385. */
  386. public void setPropertyFile(File filename) {
  387. propertyFile = filename;
  388. }
  389. /**
  390. * Add nested <replacefilter> element.
  391. */
  392. public Replacefilter createReplacefilter() {
  393. Replacefilter filter = new Replacefilter();
  394. replacefilters.addElement(filter);
  395. return filter;
  396. }
  397. /**
  398. * Replace occurrences of str1 in string str with str2
  399. */
  400. private String stringReplace(String str, String str1, String str2) {
  401. StringBuffer ret = new StringBuffer();
  402. int start = 0;
  403. int found = str.indexOf(str1);
  404. while (found >= 0) {
  405. // write everything up to the found str1
  406. if (found > start) {
  407. ret.append(str.substring(start, found));
  408. }
  409. // write the replacement str2
  410. if (str2 != null) {
  411. ret.append(str2);
  412. }
  413. // search again
  414. start = found + str1.length();
  415. found = str.indexOf(str1,start);
  416. ++replaceCount;
  417. }
  418. // write the remaining characters
  419. if (str.length() > start) {
  420. ret.append(str.substring(start, str.length()));
  421. }
  422. return ret.toString();
  423. }
  424. }