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

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