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 32 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. package org.apache.tools.ant.taskdefs;
  19. import java.io.BufferedReader;
  20. import java.io.BufferedWriter;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileOutputStream;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.InputStreamReader;
  27. import java.io.OutputStream;
  28. import java.io.OutputStreamWriter;
  29. import java.io.Reader;
  30. import java.io.Writer;
  31. import java.util.ArrayList;
  32. import java.util.Iterator;
  33. import java.util.Properties;
  34. import org.apache.tools.ant.BuildException;
  35. import org.apache.tools.ant.DirectoryScanner;
  36. import org.apache.tools.ant.Project;
  37. import org.apache.tools.ant.types.Resource;
  38. import org.apache.tools.ant.types.ResourceCollection;
  39. import org.apache.tools.ant.types.resources.FileProvider;
  40. import org.apache.tools.ant.types.resources.FileResource;
  41. import org.apache.tools.ant.types.resources.Union;
  42. import org.apache.tools.ant.util.FileUtils;
  43. import org.apache.tools.ant.util.StringUtils;
  44. /**
  45. * Replaces all occurrences of one or more string tokens with given
  46. * values in the indicated files. Each value can be either a string
  47. * or the value of a property available in a designated property file.
  48. * If you want to replace a text that crosses line boundaries, you
  49. * must use a nested <code>&lt;replacetoken&gt;</code> element.
  50. *
  51. * @since Ant 1.1
  52. *
  53. * @ant.task category="filesystem"
  54. */
  55. public class Replace extends MatchingTask {
  56. private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
  57. private File sourceFile = null;
  58. private NestedString token = null;
  59. private NestedString value = new NestedString();
  60. private Resource propertyResource = null;
  61. private Resource replaceFilterResource = null;
  62. private Properties properties = null;
  63. private ArrayList replacefilters = new ArrayList();
  64. private File dir = null;
  65. private int fileCount;
  66. private int replaceCount;
  67. private boolean summary = false;
  68. /** The encoding used to read and write files - if null, uses default */
  69. private String encoding = null;
  70. private Union resources;
  71. private boolean preserveLastModified = false;
  72. private boolean failOnNoReplacements = false;
  73. /**
  74. * An inline string to use as the replacement text.
  75. */
  76. public class NestedString {
  77. private boolean expandProperties = false;
  78. private StringBuffer buf = new StringBuffer();
  79. /**
  80. * Whether properties should be expanded in nested test.
  81. *
  82. * <p>If you use this class via its Java interface the text
  83. * you add via {@link #addText addText} has most likely been
  84. * expanded already so you do <b>not</b> want to set this to
  85. * true.</p>
  86. *
  87. * @since Ant 1.8.0
  88. */
  89. public void setExpandProperties(boolean b) {
  90. expandProperties = b;
  91. }
  92. /**
  93. * The text of the element.
  94. *
  95. * @param val the string to add
  96. */
  97. public void addText(String val) {
  98. buf.append(val);
  99. }
  100. /**
  101. * @return the text
  102. */
  103. public String getText() {
  104. String s = buf.toString();
  105. return expandProperties ? getProject().replaceProperties(s) : s;
  106. }
  107. }
  108. /**
  109. * A filter to apply.
  110. */
  111. public class Replacefilter {
  112. private NestedString token;
  113. private NestedString value;
  114. private String replaceValue;
  115. private String property;
  116. private StringBuffer inputBuffer;
  117. private StringBuffer outputBuffer = new StringBuffer();
  118. /**
  119. * Validate the filter's configuration.
  120. * @throws BuildException if any part is invalid.
  121. */
  122. public void validate() throws BuildException {
  123. //Validate mandatory attributes
  124. if (token == null) {
  125. String message = "token is a mandatory for replacefilter.";
  126. throw new BuildException(message);
  127. }
  128. if ("".equals(token.getText())) {
  129. String message = "The token must not be an empty "
  130. + "string.";
  131. throw new BuildException(message);
  132. }
  133. //value and property are mutually exclusive attributes
  134. if ((value != null) && (property != null)) {
  135. String message = "Either value or property "
  136. + "can be specified, but a replacefilter "
  137. + "element cannot have both.";
  138. throw new BuildException(message);
  139. }
  140. if ((property != null)) {
  141. //the property attribute must have access to a property file
  142. if (propertyResource == null) {
  143. String message = "The replacefilter's property attribute "
  144. + "can only be used with the replacetask's "
  145. + "propertyFile/Resource attribute.";
  146. throw new BuildException(message);
  147. }
  148. //Make sure property exists in property file
  149. if (properties == null
  150. || properties.getProperty(property) == null) {
  151. String message = "property \"" + property
  152. + "\" was not found in " + propertyResource.getName();
  153. throw new BuildException(message);
  154. }
  155. }
  156. replaceValue = getReplaceValue();
  157. }
  158. /**
  159. * Get the replacement value for this filter token.
  160. * @return the replacement value
  161. */
  162. public String getReplaceValue() {
  163. if (property != null) {
  164. return properties.getProperty(property);
  165. } else if (value != null) {
  166. return value.getText();
  167. } else if (Replace.this.value != null) {
  168. return Replace.this.value.getText();
  169. } else {
  170. //Default is empty string
  171. return "";
  172. }
  173. }
  174. /**
  175. * Set the token to replace.
  176. * @param t <code>String</code> token.
  177. */
  178. public void setToken(String t) {
  179. createReplaceToken().addText(t);
  180. }
  181. /**
  182. * Get the string to search for.
  183. * @return current <code>String</code> token.
  184. */
  185. public String getToken() {
  186. return token.getText();
  187. }
  188. /**
  189. * The replacement string; required if <code>property<code>
  190. * is not set.
  191. * @param value <code>String</code> value to replace.
  192. */
  193. public void setValue(String value) {
  194. createReplaceValue().addText(value);
  195. }
  196. /**
  197. * Get replacement <code>String</code>.
  198. * @return replacement or null.
  199. */
  200. public String getValue() {
  201. return value.getText();
  202. }
  203. /**
  204. * Set the name of the property whose value is to serve as
  205. * the replacement value; required if <code>value</code> is not set.
  206. * @param property property name.
  207. */
  208. public void setProperty(String property) {
  209. this.property = property;
  210. }
  211. /**
  212. * Get the name of the property whose value is to serve as
  213. * the replacement value.
  214. * @return property or null.
  215. */
  216. public String getProperty() {
  217. return property;
  218. }
  219. /**
  220. * Create a token to filter as the text of a nested element.
  221. * @return nested token <code>NestedString</code> to configure.
  222. * @since Ant 1.8.0
  223. */
  224. public NestedString createReplaceToken() {
  225. if (token == null) {
  226. token = new NestedString();
  227. }
  228. return token;
  229. }
  230. /**
  231. * Create a string to replace the token as the text of a nested element.
  232. * @return replacement value <code>NestedString</code> to configure.
  233. * @since Ant 1.8.0
  234. */
  235. public NestedString createReplaceValue() {
  236. if (value == null) {
  237. value = new NestedString();
  238. }
  239. return value;
  240. }
  241. /**
  242. * Retrieves the output buffer of this filter. The filter guarantees
  243. * that data is only appended to the end of this StringBuffer.
  244. * @return The StringBuffer containing the output of this filter.
  245. */
  246. StringBuffer getOutputBuffer() {
  247. return outputBuffer;
  248. }
  249. /**
  250. * Sets the input buffer for this filter.
  251. * The filter expects from the component providing the input that data
  252. * is only added by that component to the end of this StringBuffer.
  253. * This StringBuffer will be modified by this filter, and expects that
  254. * another component will only apped to this StringBuffer.
  255. * @param input The input for this filter.
  256. */
  257. void setInputBuffer(StringBuffer input) {
  258. inputBuffer = input;
  259. }
  260. /**
  261. * Processes the buffer as far as possible. Takes into account that
  262. * appended data may make it possible to replace the end of the already
  263. * received data, when the token is split over the "old" and the "new"
  264. * part.
  265. * @return true if some data has been made available in the
  266. * output buffer.
  267. */
  268. boolean process() {
  269. String t = getToken();
  270. if (inputBuffer.length() > t.length()) {
  271. int pos = replace();
  272. pos = Math.max((inputBuffer.length() - t.length()), pos);
  273. outputBuffer.append(inputBuffer.substring(0, pos));
  274. inputBuffer.delete(0, pos);
  275. return true;
  276. }
  277. return false;
  278. }
  279. /**
  280. * Processes the buffer to the end. Does not take into account that
  281. * appended data may make it possible to replace the end of the already
  282. * received data.
  283. */
  284. void flush() {
  285. replace();
  286. outputBuffer.append(inputBuffer);
  287. inputBuffer.delete(0, inputBuffer.length());
  288. }
  289. /**
  290. * Performs the replace operation.
  291. * @return The position of the last character that was inserted as
  292. * replacement.
  293. */
  294. private int replace() {
  295. String t = getToken();
  296. int found = inputBuffer.indexOf(t);
  297. int pos = -1;
  298. final int tokenLength = t.length();
  299. final int replaceValueLength = replaceValue.length();
  300. while (found >= 0) {
  301. inputBuffer.replace(found, found + tokenLength, replaceValue);
  302. pos = found + replaceValueLength;
  303. found = inputBuffer.indexOf(t, pos);
  304. ++replaceCount;
  305. }
  306. return pos;
  307. }
  308. }
  309. /**
  310. * Class reading a file in small chunks, and presenting these chunks in
  311. * a StringBuffer. Compatible with the Replacefilter.
  312. * @since 1.7
  313. */
  314. private class FileInput /* JDK 5: implements Closeable */ {
  315. private StringBuffer outputBuffer;
  316. private final InputStream is;
  317. private Reader reader;
  318. private char[] buffer;
  319. private static final int BUFF_SIZE = 4096;
  320. /**
  321. * Constructs the input component. Opens the file for reading.
  322. * @param source The file to read from.
  323. * @throws IOException When the file cannot be read from.
  324. */
  325. FileInput(File source) throws IOException {
  326. outputBuffer = new StringBuffer();
  327. buffer = new char[BUFF_SIZE];
  328. is = new FileInputStream(source);
  329. try {
  330. reader = new BufferedReader(encoding != null ? new InputStreamReader(is, encoding) : new InputStreamReader(is));
  331. } finally {
  332. if (reader == null) {
  333. is.close();
  334. }
  335. }
  336. }
  337. /**
  338. * Retrieves the output buffer of this filter. The component guarantees
  339. * that data is only appended to the end of this StringBuffer.
  340. * @return The StringBuffer containing the output of this filter.
  341. */
  342. StringBuffer getOutputBuffer() {
  343. return outputBuffer;
  344. }
  345. /**
  346. * Reads some data from the file.
  347. * @return true when the end of the file has not been reached.
  348. * @throws IOException When the file cannot be read from.
  349. */
  350. boolean readChunk() throws IOException {
  351. int bufferLength = 0;
  352. bufferLength = reader.read(buffer);
  353. if (bufferLength < 0) {
  354. return false;
  355. }
  356. outputBuffer.append(new String(buffer, 0, bufferLength));
  357. return true;
  358. }
  359. /**
  360. * Closes the file.
  361. * @throws IOException When the file cannot be closed.
  362. */
  363. public void close() throws IOException {
  364. is.close();
  365. }
  366. }
  367. /**
  368. * Component writing a file in chunks, taking the chunks from the
  369. * Replacefilter.
  370. * @since 1.7
  371. */
  372. private class FileOutput /* JDK 5: implements Closeable */ {
  373. private StringBuffer inputBuffer;
  374. private final OutputStream os;
  375. private Writer writer;
  376. /**
  377. * Constructs the output component. Opens the file for writing.
  378. * @param out The file to read to.
  379. * @throws IOException When the file cannot be read from.
  380. */
  381. FileOutput(File out) throws IOException {
  382. os = new FileOutputStream(out);
  383. try {
  384. writer = new BufferedWriter(encoding != null ? new OutputStreamWriter(os, encoding) : new OutputStreamWriter(os));
  385. } finally {
  386. if (writer == null) {
  387. os.close();
  388. }
  389. }
  390. }
  391. /**
  392. * Sets the input buffer for this component.
  393. * The filter expects from the component providing the input that data
  394. * is only added by that component to the end of this StringBuffer.
  395. * This StringBuffer will be modified by this filter, and expects that
  396. * another component will only append to this StringBuffer.
  397. * @param input The input for this filter.
  398. */
  399. void setInputBuffer(StringBuffer input) {
  400. inputBuffer = input;
  401. }
  402. /**
  403. * Writes the buffer as far as possible.
  404. * @return false to be inline with the Replacefilter.
  405. * (Yes defining an interface crossed my mind, but would publish the
  406. * internal behavior.)
  407. * @throws IOException when the output cannot be written.
  408. */
  409. boolean process() throws IOException {
  410. writer.write(inputBuffer.toString());
  411. inputBuffer.delete(0, inputBuffer.length());
  412. return false;
  413. }
  414. /**
  415. * Processes the buffer to the end.
  416. * @throws IOException when the output cannot be flushed.
  417. */
  418. void flush() throws IOException {
  419. process();
  420. writer.flush();
  421. }
  422. /**
  423. * Closes the file.
  424. * @throws IOException When the file cannot be closed.
  425. */
  426. public void close() throws IOException {
  427. os.close();
  428. }
  429. }
  430. /**
  431. * Do the execution.
  432. * @throws BuildException if we cant build
  433. */
  434. public void execute() throws BuildException {
  435. ArrayList savedFilters = (ArrayList) replacefilters.clone();
  436. Properties savedProperties =
  437. properties == null ? null : (Properties) properties.clone();
  438. if (token != null) {
  439. // line separators in values and tokens are "\n"
  440. // in order to compare with the file contents, replace them
  441. // as needed
  442. StringBuffer val = new StringBuffer(value.getText());
  443. stringReplace(val, "\r\n", "\n");
  444. stringReplace(val, "\n", StringUtils.LINE_SEP);
  445. StringBuffer tok = new StringBuffer(token.getText());
  446. stringReplace(tok, "\r\n", "\n");
  447. stringReplace(tok, "\n", StringUtils.LINE_SEP);
  448. Replacefilter firstFilter = createPrimaryfilter();
  449. firstFilter.setToken(tok.toString());
  450. firstFilter.setValue(val.toString());
  451. }
  452. try {
  453. if (replaceFilterResource != null) {
  454. Properties props = getProperties(replaceFilterResource);
  455. Iterator e = props.keySet().iterator();
  456. while (e.hasNext()) {
  457. String tok = e.next().toString();
  458. Replacefilter replaceFilter = createReplacefilter();
  459. replaceFilter.setToken(tok);
  460. replaceFilter.setValue(props.getProperty(tok));
  461. }
  462. }
  463. validateAttributes();
  464. if (propertyResource != null) {
  465. properties = getProperties(propertyResource);
  466. }
  467. validateReplacefilters();
  468. fileCount = 0;
  469. replaceCount = 0;
  470. if (sourceFile != null) {
  471. processFile(sourceFile);
  472. }
  473. if (dir != null) {
  474. DirectoryScanner ds = super.getDirectoryScanner(dir);
  475. String[] srcs = ds.getIncludedFiles();
  476. for (int i = 0; i < srcs.length; i++) {
  477. File file = new File(dir, srcs[i]);
  478. processFile(file);
  479. }
  480. }
  481. if (resources != null) {
  482. for (Resource r : resources) {
  483. FileProvider fp =
  484. r.as(FileProvider.class);
  485. processFile(fp.getFile());
  486. }
  487. }
  488. if (summary) {
  489. log("Replaced " + replaceCount + " occurrences in "
  490. + fileCount + " files.", Project.MSG_INFO);
  491. }
  492. if (failOnNoReplacements && replaceCount == 0) {
  493. throw new BuildException("didn't replace anything");
  494. }
  495. } finally {
  496. replacefilters = savedFilters;
  497. properties = savedProperties;
  498. } // end of finally
  499. }
  500. /**
  501. * Validate attributes provided for this task in .xml build file.
  502. *
  503. * @exception BuildException if any supplied attribute is invalid or any
  504. * mandatory attribute is missing.
  505. */
  506. public void validateAttributes() throws BuildException {
  507. if (sourceFile == null && dir == null && resources == null) {
  508. String message = "Either the file or the dir attribute "
  509. + "or nested resources must be specified";
  510. throw new BuildException(message, getLocation());
  511. }
  512. if (propertyResource != null && !propertyResource.isExists()) {
  513. String message = "Property file " + propertyResource.getName()
  514. + " does not exist.";
  515. throw new BuildException(message, getLocation());
  516. }
  517. if (token == null && replacefilters.size() == 0) {
  518. String message = "Either token or a nested replacefilter "
  519. + "must be specified";
  520. throw new BuildException(message, getLocation());
  521. }
  522. if (token != null && "".equals(token.getText())) {
  523. String message = "The token attribute must not be an empty string.";
  524. throw new BuildException(message, getLocation());
  525. }
  526. }
  527. /**
  528. * Validate nested elements.
  529. *
  530. * @exception BuildException if any supplied attribute is invalid or any
  531. * mandatory attribute is missing.
  532. */
  533. public void validateReplacefilters()
  534. throws BuildException {
  535. final int size = replacefilters.size();
  536. for (int i = 0; i < size; i++) {
  537. Replacefilter element =
  538. (Replacefilter) replacefilters.get(i);
  539. element.validate();
  540. }
  541. }
  542. /**
  543. * Load a properties file.
  544. * @param propertyFile the file to load the properties from.
  545. * @return loaded <code>Properties</code> object.
  546. * @throws BuildException if the file could not be found or read.
  547. */
  548. public Properties getProperties(File propertyFile) throws BuildException {
  549. return getProperties(new FileResource(getProject(), propertyFile));
  550. }
  551. /**
  552. * Load a properties resource.
  553. * @param propertyResource the resource to load the properties from.
  554. * @return loaded <code>Properties</code> object.
  555. * @throws BuildException if the resource could not be found or read.
  556. * @since Ant 1.8.0
  557. */
  558. public Properties getProperties(Resource propertyResource)
  559. throws BuildException {
  560. Properties props = new Properties();
  561. InputStream in = null;
  562. try {
  563. in = propertyResource.getInputStream();
  564. props.load(in);
  565. } catch (IOException e) {
  566. String message = "Property resource (" + propertyResource.getName()
  567. + ") cannot be loaded.";
  568. throw new BuildException(message);
  569. } finally {
  570. FileUtils.close(in);
  571. }
  572. return props;
  573. }
  574. /**
  575. * Perform the replacement on the given file.
  576. *
  577. * The replacement is performed on a temporary file which then
  578. * replaces the original file.
  579. *
  580. * @param src the source <code>File</code>.
  581. */
  582. private void processFile(File src) throws BuildException {
  583. if (!src.exists()) {
  584. throw new BuildException("Replace: source file " + src.getPath()
  585. + " doesn't exist", getLocation());
  586. }
  587. int repCountStart = replaceCount;
  588. logFilterChain(src.getPath());
  589. try {
  590. File temp = FILE_UTILS.createTempFile("rep", ".tmp",
  591. src.getParentFile(), false, true);
  592. try {
  593. FileInput in = new FileInput(src);
  594. try {
  595. FileOutput out = new FileOutput(temp);
  596. try {
  597. out.setInputBuffer(buildFilterChain(in.getOutputBuffer()));
  598. while (in.readChunk()) {
  599. if (processFilterChain()) {
  600. out.process();
  601. }
  602. }
  603. flushFilterChain();
  604. out.flush();
  605. } finally {
  606. out.close();
  607. }
  608. } finally {
  609. in.close();
  610. }
  611. boolean changes = (replaceCount != repCountStart);
  612. if (changes) {
  613. fileCount++;
  614. long origLastModified = src.lastModified();
  615. FILE_UTILS.rename(temp, src);
  616. if (preserveLastModified) {
  617. FILE_UTILS.setFileLastModified(src, origLastModified);
  618. }
  619. }
  620. } finally {
  621. if (temp.isFile() && !temp.delete()) {
  622. temp.deleteOnExit();
  623. }
  624. }
  625. } catch (IOException ioe) {
  626. throw new BuildException("IOException in " + src + " - "
  627. + ioe.getClass().getName() + ":"
  628. + ioe.getMessage(), ioe, getLocation());
  629. }
  630. }
  631. /**
  632. * Flushes all filters.
  633. */
  634. private void flushFilterChain() {
  635. final int size = replacefilters.size();
  636. for (int i = 0; i < size; i++) {
  637. Replacefilter filter = (Replacefilter) replacefilters.get(i);
  638. filter.flush();
  639. }
  640. }
  641. /**
  642. * Performs the normal processing of the filters.
  643. * @return true if the filter chain produced new output.
  644. */
  645. private boolean processFilterChain() {
  646. final int size = replacefilters.size();
  647. for (int i = 0; i < size; i++) {
  648. Replacefilter filter = (Replacefilter) replacefilters.get(i);
  649. if (!filter.process()) {
  650. return false;
  651. }
  652. }
  653. return true;
  654. }
  655. /**
  656. * Creates the chain of filters to operate.
  657. * @param inputBuffer <code>StringBuffer</code> containing the input for the
  658. * first filter.
  659. * @return <code>StringBuffer</code> containing the output of the last filter.
  660. */
  661. private StringBuffer buildFilterChain(StringBuffer inputBuffer) {
  662. StringBuffer buf = inputBuffer;
  663. final int size = replacefilters.size();
  664. for (int i = 0; i < size; i++) {
  665. Replacefilter filter = (Replacefilter) replacefilters.get(i);
  666. filter.setInputBuffer(buf);
  667. buf = filter.getOutputBuffer();
  668. }
  669. return buf;
  670. }
  671. /**
  672. * Logs the chain of filters to operate on the file.
  673. * @param filename <code>String</code>.
  674. */
  675. private void logFilterChain(String filename) {
  676. final int size = replacefilters.size();
  677. for (int i = 0; i < size; i++) {
  678. Replacefilter filter = (Replacefilter) replacefilters.get(i);
  679. log("Replacing in " + filename + ": " + filter.getToken()
  680. + " --> " + filter.getReplaceValue(), Project.MSG_VERBOSE);
  681. }
  682. }
  683. /**
  684. * Set the source file; required unless <code>dir</code> is set.
  685. * @param file source <code>File</code>.
  686. */
  687. public void setFile(File file) {
  688. this.sourceFile = file;
  689. }
  690. /**
  691. * Indicates whether a summary of the replace operation should be
  692. * produced, detailing how many token occurrences and files were
  693. * processed; optional, default=<code>false</code>.
  694. *
  695. * @param summary <code>boolean</code> whether a summary of the
  696. * replace operation should be logged.
  697. */
  698. public void setSummary(boolean summary) {
  699. this.summary = summary;
  700. }
  701. /**
  702. * Sets the name of a property file containing filters; optional.
  703. * Each property will be treated as a replacefilter where token is the name
  704. * of the property and value is the value of the property.
  705. * @param replaceFilterFile <code>File</code> to load.
  706. */
  707. public void setReplaceFilterFile(File replaceFilterFile) {
  708. setReplaceFilterResource(new FileResource(getProject(),
  709. replaceFilterFile));
  710. }
  711. /**
  712. * Sets the name of a resource containing filters; optional.
  713. * Each property will be treated as a replacefilter where token is the name
  714. * of the property and value is the value of the property.
  715. * @param replaceFilter <code>Resource</code> to load.
  716. * @since Ant 1.8.0
  717. */
  718. public void setReplaceFilterResource(Resource replaceFilter) {
  719. this.replaceFilterResource = replaceFilter;
  720. }
  721. /**
  722. * The base directory to use when replacing a token in multiple files;
  723. * required if <code>file</code> is not defined.
  724. * @param dir <code>File</code> representing the base directory.
  725. */
  726. public void setDir(File dir) {
  727. this.dir = dir;
  728. }
  729. /**
  730. * Set the string token to replace; required unless a nested
  731. * <code>replacetoken</code> element or the
  732. * <code>replacefilterresource</code> attribute is used.
  733. * @param token token <code>String</code>.
  734. */
  735. public void setToken(String token) {
  736. createReplaceToken().addText(token);
  737. }
  738. /**
  739. * Set the string value to use as token replacement;
  740. * optional, default is the empty string "".
  741. * @param value replacement value.
  742. */
  743. public void setValue(String value) {
  744. createReplaceValue().addText(value);
  745. }
  746. /**
  747. * Set the file encoding to use on the files read and written by the task;
  748. * optional, defaults to default JVM encoding.
  749. *
  750. * @param encoding the encoding to use on the files.
  751. */
  752. public void setEncoding(String encoding) {
  753. this.encoding = encoding;
  754. }
  755. /**
  756. * Create a token to filter as the text of a nested element.
  757. * @return nested token <code>NestedString</code> to configure.
  758. */
  759. public NestedString createReplaceToken() {
  760. if (token == null) {
  761. token = new NestedString();
  762. }
  763. return token;
  764. }
  765. /**
  766. * Create a string to replace the token as the text of a nested element.
  767. * @return replacement value <code>NestedString</code> to configure.
  768. */
  769. public NestedString createReplaceValue() {
  770. return value;
  771. }
  772. /**
  773. * The name of a property file from which properties specified using nested
  774. * <code>&lt;replacefilter&gt;</code> elements are drawn; required only if
  775. * the <i>property</i> attribute of <code>&lt;replacefilter&gt;</code> is used.
  776. * @param propertyFile <code>File</code> to load.
  777. */
  778. public void setPropertyFile(File propertyFile) {
  779. setPropertyResource(new FileResource(propertyFile));
  780. }
  781. /**
  782. * A resource from which properties specified using nested
  783. * <code>&lt;replacefilter&gt;</code> elements are drawn; required
  784. * only if the <i>property</i> attribute of
  785. * <code>&lt;replacefilter&gt;</code> is used.
  786. * @param propertyResource <code>Resource</code> to load.
  787. *
  788. * @since Ant 1.8.0
  789. */
  790. public void setPropertyResource(Resource propertyResource) {
  791. this.propertyResource = propertyResource;
  792. }
  793. /**
  794. * Add a nested &lt;replacefilter&gt; element.
  795. * @return a nested <code>Replacefilter</code> object to be configured.
  796. */
  797. public Replacefilter createReplacefilter() {
  798. Replacefilter filter = new Replacefilter();
  799. replacefilters.add(filter);
  800. return filter;
  801. }
  802. /**
  803. * Support arbitrary file system based resource collections.
  804. *
  805. * @since Ant 1.8.0
  806. */
  807. public void addConfigured(ResourceCollection rc) {
  808. if (!rc.isFilesystemOnly()) {
  809. throw new BuildException("only filesystem resources are supported");
  810. }
  811. if (resources == null) {
  812. resources = new Union();
  813. }
  814. resources.add(rc);
  815. }
  816. /**
  817. * Whether the file timestamp shall be preserved even if the file
  818. * is modified.
  819. *
  820. * @since Ant 1.8.0
  821. */
  822. public void setPreserveLastModified(boolean b) {
  823. preserveLastModified = b;
  824. }
  825. /**
  826. * Whether the build should fail if nothing has been replaced.
  827. *
  828. * @since Ant 1.8.0
  829. */
  830. public void setFailOnNoReplacements(boolean b) {
  831. failOnNoReplacements = b;
  832. }
  833. /**
  834. * Adds the token and value as first &lt;replacefilter&gt; element.
  835. * The token and value are always processed first.
  836. * @return a nested <code>Replacefilter</code> object to be configured.
  837. */
  838. private Replacefilter createPrimaryfilter() {
  839. Replacefilter filter = new Replacefilter();
  840. replacefilters.add(0, filter);
  841. return filter;
  842. }
  843. /**
  844. * Replace occurrences of str1 in StringBuffer str with str2.
  845. */
  846. private void stringReplace(StringBuffer str, String str1, String str2) {
  847. int found = str.indexOf(str1);
  848. final int str1Length = str1.length();
  849. final int str2Length = str2.length();
  850. while (found >= 0) {
  851. str.replace(found, found + str1Length, str2);
  852. found = str.indexOf(str1, found + str2Length);
  853. }
  854. }
  855. }