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

8 years ago
11 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  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.IOException;
  23. import java.io.InputStream;
  24. import java.io.InputStreamReader;
  25. import java.io.OutputStream;
  26. import java.io.OutputStreamWriter;
  27. import java.io.Reader;
  28. import java.io.Writer;
  29. import java.nio.file.Files;
  30. import java.util.ArrayList;
  31. import java.util.Collections;
  32. import java.util.Comparator;
  33. import java.util.Iterator;
  34. import java.util.List;
  35. import java.util.Objects;
  36. import java.util.Properties;
  37. import org.apache.tools.ant.BuildException;
  38. import org.apache.tools.ant.DirectoryScanner;
  39. import org.apache.tools.ant.Project;
  40. import org.apache.tools.ant.types.Resource;
  41. import org.apache.tools.ant.types.ResourceCollection;
  42. import org.apache.tools.ant.types.resources.FileProvider;
  43. import org.apache.tools.ant.types.resources.FileResource;
  44. import org.apache.tools.ant.types.resources.Union;
  45. import org.apache.tools.ant.util.FileUtils;
  46. import org.apache.tools.ant.util.StringUtils;
  47. /**
  48. * Replaces all occurrences of one or more string tokens with given
  49. * values in the indicated files. Each value can be either a string
  50. * or the value of a property available in a designated property file.
  51. * If you want to replace a text that crosses line boundaries, you
  52. * must use a nested <code>&lt;replacetoken&gt;</code> element.
  53. *
  54. * @since Ant 1.1
  55. *
  56. * @ant.task category="filesystem"
  57. */
  58. public class Replace extends MatchingTask {
  59. private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
  60. private File sourceFile = null;
  61. private NestedString token = null;
  62. private NestedString value = new NestedString();
  63. private Resource propertyResource = null;
  64. private Resource replaceFilterResource = null;
  65. private Properties properties = null;
  66. private List<Replacefilter> replacefilters = new ArrayList<>();
  67. private File dir = null;
  68. private int fileCount;
  69. private int replaceCount;
  70. private boolean summary = false;
  71. /** The encoding used to read and write files - if null, uses default */
  72. private String encoding = null;
  73. private Union resources;
  74. private boolean preserveLastModified = false;
  75. private boolean failOnNoReplacements = false;
  76. /**
  77. * An inline string to use as the replacement text.
  78. */
  79. public class NestedString {
  80. private boolean expandProperties = false;
  81. private StringBuffer buf = new StringBuffer();
  82. /**
  83. * Whether properties should be expanded in nested test.
  84. *
  85. * <p>If you use this class via its Java interface the text
  86. * you add via {@link #addText addText} has most likely been
  87. * expanded already so you do <b>not</b> want to set this to
  88. * true.</p>
  89. *
  90. * @param b boolean
  91. * @since Ant 1.8.0
  92. */
  93. public void setExpandProperties(boolean b) {
  94. expandProperties = b;
  95. }
  96. /**
  97. * The text of the element.
  98. *
  99. * @param val the string to add
  100. */
  101. public void addText(String val) {
  102. buf.append(val);
  103. }
  104. /**
  105. * @return the text
  106. */
  107. public String getText() {
  108. String s = buf.toString();
  109. return expandProperties ? getProject().replaceProperties(s) : s;
  110. }
  111. }
  112. /**
  113. * A filter to apply.
  114. */
  115. public class Replacefilter {
  116. private NestedString token;
  117. private NestedString value;
  118. private String replaceValue;
  119. private String property;
  120. private StringBuffer inputBuffer;
  121. private StringBuffer outputBuffer = new StringBuffer();
  122. /**
  123. * Validate the filter's configuration.
  124. * @throws BuildException if any part is invalid.
  125. */
  126. public void validate() throws BuildException {
  127. //Validate mandatory attributes
  128. if (token == null) {
  129. throw new BuildException(
  130. "token is a mandatory for replacefilter.");
  131. }
  132. if ("".equals(token.getText())) {
  133. throw new BuildException(
  134. "The token must not be an empty string.");
  135. }
  136. //value and property are mutually exclusive attributes
  137. if ((value != null) && (property != null)) {
  138. throw new BuildException(
  139. "Either value or property can be specified, but a replacefilter element cannot have both.");
  140. }
  141. if ((property != null)) {
  142. //the property attribute must have access to a property file
  143. if (propertyResource == null) {
  144. throw new BuildException(
  145. "The replacefilter's property attribute can only be used with the replacetask's propertyFile/Resource attribute.");
  146. }
  147. //Make sure property exists in property file
  148. if (properties == null
  149. || properties.getProperty(property) == null) {
  150. throw new BuildException(
  151. "property \"%s\" was not found in %s", property,
  152. propertyResource.getName());
  153. }
  154. }
  155. replaceValue = getReplaceValue();
  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. }
  165. if (value != null) {
  166. return value.getText();
  167. }
  168. if (Replace.this.value != null) {
  169. return Replace.this.value.getText();
  170. }
  171. //Default is empty string
  172. return "";
  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 added 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 implements AutoCloseable {
  315. private static final int BUFF_SIZE = 4096;
  316. private StringBuffer outputBuffer;
  317. private final InputStream is;
  318. private Reader reader;
  319. private char[] buffer;
  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 = Files.newInputStream(source.toPath());
  329. try {
  330. reader = new BufferedReader(
  331. encoding != null ? new InputStreamReader(is, encoding)
  332. : new InputStreamReader(is));
  333. } finally {
  334. if (reader == null) {
  335. is.close();
  336. }
  337. }
  338. }
  339. /**
  340. * Retrieves the output buffer of this filter. The component guarantees
  341. * that data is only appended to the end of this StringBuffer.
  342. * @return The StringBuffer containing the output of this filter.
  343. */
  344. StringBuffer getOutputBuffer() {
  345. return outputBuffer;
  346. }
  347. /**
  348. * Reads some data from the file.
  349. * @return true when the end of the file has not been reached.
  350. * @throws IOException When the file cannot be read from.
  351. */
  352. boolean readChunk() throws IOException {
  353. int bufferLength = reader.read(buffer);
  354. if (bufferLength < 0) {
  355. return false;
  356. }
  357. outputBuffer.append(new String(buffer, 0, bufferLength));
  358. return true;
  359. }
  360. /**
  361. * Closes the file.
  362. * @throws IOException When the file cannot be closed.
  363. */
  364. @Override
  365. public void close() throws IOException {
  366. is.close();
  367. }
  368. }
  369. /**
  370. * Component writing a file in chunks, taking the chunks from the
  371. * Replacefilter.
  372. * @since 1.7
  373. */
  374. private class FileOutput implements AutoCloseable {
  375. private StringBuffer inputBuffer;
  376. private final OutputStream os;
  377. private Writer writer;
  378. /**
  379. * Constructs the output component. Opens the file for writing.
  380. * @param out The file to read to.
  381. * @throws IOException When the file cannot be read from.
  382. */
  383. FileOutput(File out) throws IOException {
  384. os = Files.newOutputStream(out.toPath());
  385. try {
  386. writer = new BufferedWriter(
  387. encoding != null ? new OutputStreamWriter(os, encoding)
  388. : new OutputStreamWriter(os));
  389. } finally {
  390. if (writer == null) {
  391. os.close();
  392. }
  393. }
  394. }
  395. /**
  396. * Sets the input buffer for this component.
  397. * The filter expects from the component providing the input that data
  398. * is only added by that component to the end of this StringBuffer.
  399. * This StringBuffer will be modified by this filter, and expects that
  400. * another component will only append to this StringBuffer.
  401. * @param input The input for this filter.
  402. */
  403. void setInputBuffer(StringBuffer input) {
  404. inputBuffer = input;
  405. }
  406. /**
  407. * Writes the buffer as far as possible.
  408. * @return false to be inline with the Replacefilter.
  409. * (Yes defining an interface crossed my mind, but would publish the
  410. * internal behavior.)
  411. * @throws IOException when the output cannot be written.
  412. */
  413. boolean process() throws IOException {
  414. writer.write(inputBuffer.toString());
  415. inputBuffer.delete(0, inputBuffer.length());
  416. return false;
  417. }
  418. /**
  419. * Processes the buffer to the end.
  420. * @throws IOException when the output cannot be flushed.
  421. */
  422. void flush() throws IOException {
  423. process();
  424. writer.flush();
  425. }
  426. /**
  427. * Closes the file.
  428. * @throws IOException When the file cannot be closed.
  429. */
  430. @Override
  431. public void close() throws IOException {
  432. os.close();
  433. }
  434. }
  435. /**
  436. * Do the execution.
  437. * @throws BuildException if we can't build
  438. */
  439. @Override
  440. public void execute() throws BuildException {
  441. List<Replacefilter> savedFilters = new ArrayList<>(replacefilters);
  442. Properties savedProperties =
  443. properties == null ? null : (Properties) properties.clone();
  444. if (token != null) {
  445. // line separators in values and tokens are "\n"
  446. // in order to compare with the file contents, replace them
  447. // as needed
  448. StringBuilder val = new StringBuilder(value.getText());
  449. stringReplace(val, "\r\n", "\n");
  450. stringReplace(val, "\n", StringUtils.LINE_SEP);
  451. StringBuilder tok = new StringBuilder(token.getText());
  452. stringReplace(tok, "\r\n", "\n");
  453. stringReplace(tok, "\n", StringUtils.LINE_SEP);
  454. Replacefilter firstFilter = createPrimaryfilter();
  455. firstFilter.setToken(tok.toString());
  456. firstFilter.setValue(val.toString());
  457. }
  458. try {
  459. if (replaceFilterResource != null) {
  460. Properties props = getProperties(replaceFilterResource);
  461. Iterator<Object> e = getOrderedIterator(props);
  462. while (e.hasNext()) {
  463. String tok = e.next().toString();
  464. Replacefilter replaceFilter = createReplacefilter();
  465. replaceFilter.setToken(tok);
  466. replaceFilter.setValue(props.getProperty(tok));
  467. }
  468. }
  469. validateAttributes();
  470. if (propertyResource != null) {
  471. properties = getProperties(propertyResource);
  472. }
  473. validateReplacefilters();
  474. fileCount = 0;
  475. replaceCount = 0;
  476. if (sourceFile != null) {
  477. processFile(sourceFile);
  478. }
  479. if (dir != null) {
  480. DirectoryScanner ds = super.getDirectoryScanner(dir);
  481. for (String src : ds.getIncludedFiles()) {
  482. File file = new File(dir, src);
  483. processFile(file);
  484. }
  485. }
  486. if (resources != null) {
  487. for (Resource r : resources) {
  488. processFile(r.as(FileProvider.class).getFile());
  489. }
  490. }
  491. if (summary) {
  492. log("Replaced " + replaceCount + " occurrences in "
  493. + fileCount + " files.", Project.MSG_INFO);
  494. }
  495. if (failOnNoReplacements && replaceCount == 0) {
  496. throw new BuildException("didn't replace anything");
  497. }
  498. } finally {
  499. replacefilters = savedFilters;
  500. properties = savedProperties;
  501. } // end of finally
  502. }
  503. /**
  504. * Validate attributes provided for this task in .xml build file.
  505. *
  506. * @exception BuildException if any supplied attribute is invalid or any
  507. * mandatory attribute is missing.
  508. */
  509. public void validateAttributes() throws BuildException {
  510. if (sourceFile == null && dir == null && resources == null) {
  511. throw new BuildException(
  512. "Either the file or the dir attribute or nested resources must be specified",
  513. getLocation());
  514. }
  515. if (propertyResource != null && !propertyResource.isExists()) {
  516. throw new BuildException("Property file "
  517. + propertyResource.getName() + " does not exist.",
  518. getLocation());
  519. }
  520. if (token == null && replacefilters.isEmpty()) {
  521. throw new BuildException(
  522. "Either token or a nested replacefilter must be specified",
  523. getLocation());
  524. }
  525. if (token != null && "".equals(token.getText())) {
  526. throw new BuildException(
  527. "The token attribute must not be an empty string.",
  528. getLocation());
  529. }
  530. }
  531. /**
  532. * Validate nested elements.
  533. *
  534. * @exception BuildException if any supplied attribute is invalid or any
  535. * mandatory attribute is missing.
  536. */
  537. public void validateReplacefilters()
  538. throws BuildException {
  539. replacefilters.forEach(Replacefilter::validate);
  540. }
  541. /**
  542. * Load a properties file.
  543. * @param propertyFile the file to load the properties from.
  544. * @return loaded <code>Properties</code> object.
  545. * @throws BuildException if the file could not be found or read.
  546. */
  547. public Properties getProperties(File propertyFile) throws BuildException {
  548. return getProperties(new FileResource(getProject(), propertyFile));
  549. }
  550. /**
  551. * Load a properties resource.
  552. * @param propertyResource the resource to load the properties from.
  553. * @return loaded <code>Properties</code> object.
  554. * @throws BuildException if the resource could not be found or read.
  555. * @since Ant 1.8.0
  556. */
  557. public Properties getProperties(Resource propertyResource)
  558. throws BuildException {
  559. Properties props = new Properties();
  560. try (InputStream in = propertyResource.getInputStream()) {
  561. props.load(in);
  562. } catch (IOException e) {
  563. throw new BuildException("Property resource (%s) cannot be loaded.",
  564. propertyResource.getName());
  565. }
  566. return props;
  567. }
  568. /**
  569. * Perform the replacement on the given file.
  570. *
  571. * The replacement is performed on a temporary file which then
  572. * replaces the original file.
  573. *
  574. * @param src the source <code>File</code>.
  575. */
  576. private void processFile(File src) throws BuildException {
  577. if (!src.exists()) {
  578. throw new BuildException("Replace: source file " + src.getPath()
  579. + " doesn't exist", getLocation());
  580. }
  581. int repCountStart = replaceCount;
  582. logFilterChain(src.getPath());
  583. try {
  584. File temp = FILE_UTILS.createTempFile("rep", ".tmp",
  585. src.getParentFile(), false, true);
  586. try {
  587. try (FileInput in = new FileInput(src);
  588. FileOutput out = new FileOutput(temp)) {
  589. out.setInputBuffer(buildFilterChain(in.getOutputBuffer()));
  590. while (in.readChunk()) {
  591. if (processFilterChain()) {
  592. out.process();
  593. }
  594. }
  595. flushFilterChain();
  596. out.flush();
  597. }
  598. boolean changes = (replaceCount != repCountStart);
  599. if (changes) {
  600. fileCount++;
  601. long origLastModified = src.lastModified();
  602. FILE_UTILS.rename(temp, src);
  603. if (preserveLastModified) {
  604. FILE_UTILS.setFileLastModified(src, origLastModified);
  605. }
  606. }
  607. } finally {
  608. if (temp.isFile() && !temp.delete()) {
  609. temp.deleteOnExit();
  610. }
  611. }
  612. } catch (IOException ioe) {
  613. throw new BuildException("IOException in " + src + " - "
  614. + ioe.getClass().getName() + ":"
  615. + ioe.getMessage(), ioe, getLocation());
  616. }
  617. }
  618. /**
  619. * Flushes all filters.
  620. */
  621. private void flushFilterChain() {
  622. replacefilters.forEach(Replacefilter::flush);
  623. }
  624. /**
  625. * Performs the normal processing of the filters.
  626. * @return true if the filter chain produced new output.
  627. */
  628. private boolean processFilterChain() {
  629. return replacefilters.stream().allMatch(Replacefilter::process);
  630. }
  631. /**
  632. * Creates the chain of filters to operate.
  633. * @param inputBuffer <code>StringBuffer</code> containing the input for the
  634. * first filter.
  635. * @return <code>StringBuffer</code> containing the output of the last filter.
  636. */
  637. private StringBuffer buildFilterChain(StringBuffer inputBuffer) {
  638. StringBuffer buf = inputBuffer;
  639. for (Replacefilter filter : replacefilters) {
  640. filter.setInputBuffer(buf);
  641. buf = filter.getOutputBuffer();
  642. }
  643. return buf;
  644. }
  645. /**
  646. * Logs the chain of filters to operate on the file.
  647. * @param filename <code>String</code>.
  648. */
  649. private void logFilterChain(String filename) {
  650. replacefilters
  651. .forEach(
  652. filter -> log(
  653. "Replacing in " + filename + ": " + filter.getToken()
  654. + " --> " + filter.getReplaceValue(),
  655. Project.MSG_VERBOSE));
  656. }
  657. /**
  658. * Set the source file; required unless <code>dir</code> is set.
  659. * @param file source <code>File</code>.
  660. */
  661. public void setFile(File file) {
  662. this.sourceFile = file;
  663. }
  664. /**
  665. * Indicates whether a summary of the replace operation should be
  666. * produced, detailing how many token occurrences and files were
  667. * processed; optional, default=<code>false</code>.
  668. *
  669. * @param summary <code>boolean</code> whether a summary of the
  670. * replace operation should be logged.
  671. */
  672. public void setSummary(boolean summary) {
  673. this.summary = summary;
  674. }
  675. /**
  676. * Sets the name of a property file containing filters; optional.
  677. * Each property will be treated as a replacefilter where token is the name
  678. * of the property and value is the value of the property.
  679. * @param replaceFilterFile <code>File</code> to load.
  680. */
  681. public void setReplaceFilterFile(File replaceFilterFile) {
  682. setReplaceFilterResource(new FileResource(getProject(),
  683. replaceFilterFile));
  684. }
  685. /**
  686. * Sets the name of a resource containing filters; optional.
  687. * Each property will be treated as a replacefilter where token is the name
  688. * of the property and value is the value of the property.
  689. * @param replaceFilter <code>Resource</code> to load.
  690. * @since Ant 1.8.0
  691. */
  692. public void setReplaceFilterResource(Resource replaceFilter) {
  693. this.replaceFilterResource = replaceFilter;
  694. }
  695. /**
  696. * The base directory to use when replacing a token in multiple files;
  697. * required if <code>file</code> is not defined.
  698. * @param dir <code>File</code> representing the base directory.
  699. */
  700. public void setDir(File dir) {
  701. this.dir = dir;
  702. }
  703. /**
  704. * Set the string token to replace; required unless a nested
  705. * <code>replacetoken</code> element or the
  706. * <code>replacefilterresource</code> attribute is used.
  707. * @param token token <code>String</code>.
  708. */
  709. public void setToken(String token) {
  710. createReplaceToken().addText(token);
  711. }
  712. /**
  713. * Set the string value to use as token replacement;
  714. * optional, default is the empty string "".
  715. * @param value replacement value.
  716. */
  717. public void setValue(String value) {
  718. createReplaceValue().addText(value);
  719. }
  720. /**
  721. * Set the file encoding to use on the files read and written by the task;
  722. * optional, defaults to default JVM encoding.
  723. *
  724. * @param encoding the encoding to use on the files.
  725. */
  726. public void setEncoding(String encoding) {
  727. this.encoding = encoding;
  728. }
  729. /**
  730. * Create a token to filter as the text of a nested element.
  731. * @return nested token <code>NestedString</code> to configure.
  732. */
  733. public NestedString createReplaceToken() {
  734. if (token == null) {
  735. token = new NestedString();
  736. }
  737. return token;
  738. }
  739. /**
  740. * Create a string to replace the token as the text of a nested element.
  741. * @return replacement value <code>NestedString</code> to configure.
  742. */
  743. public NestedString createReplaceValue() {
  744. return value;
  745. }
  746. /**
  747. * The name of a property file from which properties specified using nested
  748. * <code>&lt;replacefilter&gt;</code> elements are drawn; required only if
  749. * the <i>property</i> attribute of <code>&lt;replacefilter&gt;</code> is used.
  750. * @param propertyFile <code>File</code> to load.
  751. */
  752. public void setPropertyFile(File propertyFile) {
  753. setPropertyResource(new FileResource(propertyFile));
  754. }
  755. /**
  756. * A resource from which properties specified using nested
  757. * <code>&lt;replacefilter&gt;</code> elements are drawn; required
  758. * only if the <i>property</i> attribute of
  759. * <code>&lt;replacefilter&gt;</code> is used.
  760. * @param propertyResource <code>Resource</code> to load.
  761. *
  762. * @since Ant 1.8.0
  763. */
  764. public void setPropertyResource(Resource propertyResource) {
  765. this.propertyResource = propertyResource;
  766. }
  767. /**
  768. * Add a nested &lt;replacefilter&gt; element.
  769. * @return a nested <code>Replacefilter</code> object to be configured.
  770. */
  771. public Replacefilter createReplacefilter() {
  772. Replacefilter filter = new Replacefilter();
  773. replacefilters.add(filter);
  774. return filter;
  775. }
  776. /**
  777. * Support arbitrary file system based resource collections.
  778. *
  779. * @param rc ResourceCollection
  780. * @since Ant 1.8.0
  781. */
  782. public void addConfigured(ResourceCollection rc) {
  783. if (!rc.isFilesystemOnly()) {
  784. throw new BuildException("only filesystem resources are supported");
  785. }
  786. if (resources == null) {
  787. resources = new Union();
  788. }
  789. resources.add(rc);
  790. }
  791. /**
  792. * Whether the file timestamp shall be preserved even if the file
  793. * is modified.
  794. *
  795. * @param b boolean
  796. * @since Ant 1.8.0
  797. */
  798. public void setPreserveLastModified(boolean b) {
  799. preserveLastModified = b;
  800. }
  801. /**
  802. * Whether the build should fail if nothing has been replaced.
  803. *
  804. * @param b boolean
  805. * @since Ant 1.8.0
  806. */
  807. public void setFailOnNoReplacements(boolean b) {
  808. failOnNoReplacements = b;
  809. }
  810. /**
  811. * Adds the token and value as first &lt;replacefilter&gt; element.
  812. * The token and value are always processed first.
  813. * @return a nested <code>Replacefilter</code> object to be configured.
  814. */
  815. private Replacefilter createPrimaryfilter() {
  816. Replacefilter filter = new Replacefilter();
  817. replacefilters.add(0, filter);
  818. return filter;
  819. }
  820. /**
  821. * Replace occurrences of str1 in StringBuffer str with str2.
  822. *
  823. * @param str StringBuilder
  824. * @param str1 String
  825. * @param str2 String
  826. */
  827. private void stringReplace(StringBuilder str, String str1, String str2) {
  828. int found = str.indexOf(str1);
  829. final int str1Length = str1.length();
  830. final int str2Length = str2.length();
  831. while (found >= 0) {
  832. str.replace(found, found + str1Length, str2);
  833. found = str.indexOf(str1, found + str2Length);
  834. }
  835. }
  836. /**
  837. * Sort keys by size so that tokens that are substrings of other
  838. * strings are tried later.
  839. *
  840. * @param props Properties
  841. */
  842. private Iterator<Object> getOrderedIterator(Properties props) {
  843. List<Object> keys = new ArrayList<>(props.keySet());
  844. Collections.sort(keys, Comparator
  845. .comparingInt(o -> Objects.toString(o, "").length()).reversed());
  846. return keys.iterator();
  847. }
  848. }