1. /*
  2. * Copyright 2002-2004 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.filters;
  18. import java.io.IOException;
  19. import java.io.Reader;
  20. import java.util.Hashtable;
  21. import org.apache.tools.ant.types.Parameter;
  22. import org.apache.tools.ant.BuildException;
  23. /**
  24. * Replaces tokens in the original input with user-supplied values.
  25. *
  26. * Example:
  27. *
  28. * <pre><replacetokens begintoken="#" endtoken="#">
  29. * <token key="DATE" value="${TODAY}"/>
  30. * </replacetokens></pre>
  31. *
  32. * Or:
  33. *
  34. * <pre><filterreader classname="org.apache.tools.ant.filters.ReplaceTokens">
  35. * <param type="tokenchar" name="begintoken" value="#"/>
  36. * <param type="tokenchar" name="endtoken" value="#"/>
  37. * <param type="token" name="DATE" value="${TODAY}"/>
  38. * </filterreader></pre>
  39. *
  40. */
  41. public final class ReplaceTokens
  42. extends BaseParamFilterReader
  43. implements ChainableReader {
  44. /** Default "begin token" character. */
  45. private static final char DEFAULT_BEGIN_TOKEN = '@';
  46. /** Default "end token" character. */
  47. private static final char DEFAULT_END_TOKEN = '@';
  48. /** Data to be used before reading from stream again */
  49. private String queuedData = null;
  50. /** replacement test from a token */
  51. private String replaceData = null;
  52. /** Index into replacement data */
  53. private int replaceIndex = -1;
  54. /** Index into queue data */
  55. private int queueIndex = -1;
  56. /** Hashtable to hold the replacee-replacer pairs (String to String). */
  57. private Hashtable hash = new Hashtable();
  58. /** Character marking the beginning of a token. */
  59. private char beginToken = DEFAULT_BEGIN_TOKEN;
  60. /** Character marking the end of a token. */
  61. private char endToken = DEFAULT_END_TOKEN;
  62. /**
  63. * Constructor for "dummy" instances.
  64. *
  65. * @see BaseFilterReader#BaseFilterReader()
  66. */
  67. public ReplaceTokens() {
  68. super();
  69. }
  70. /**
  71. * Creates a new filtered reader.
  72. *
  73. * @param in A Reader object providing the underlying stream.
  74. * Must not be <code>null</code>.
  75. */
  76. public ReplaceTokens(final Reader in) {
  77. super(in);
  78. }
  79. private int getNextChar() throws IOException {
  80. if (queueIndex != -1) {
  81. final int ch = queuedData.charAt(queueIndex++);
  82. if (queueIndex >= queuedData.length()) {
  83. queueIndex = -1;
  84. }
  85. return ch;
  86. }
  87. return in.read();
  88. }
  89. /**
  90. * Returns the next character in the filtered stream, replacing tokens
  91. * from the original stream.
  92. *
  93. * @return the next character in the resulting stream, or -1
  94. * if the end of the resulting stream has been reached
  95. *
  96. * @exception IOException if the underlying stream throws an IOException
  97. * during reading
  98. */
  99. public final int read() throws IOException {
  100. if (!getInitialized()) {
  101. initialize();
  102. setInitialized(true);
  103. }
  104. if (replaceIndex != -1) {
  105. final int ch = replaceData.charAt(replaceIndex++);
  106. if (replaceIndex >= replaceData.length()) {
  107. replaceIndex = -1;
  108. }
  109. return ch;
  110. }
  111. int ch = getNextChar();
  112. if (ch == beginToken) {
  113. final StringBuffer key = new StringBuffer("");
  114. do {
  115. ch = getNextChar();
  116. if (ch != -1) {
  117. key.append((char) ch);
  118. } else {
  119. break;
  120. }
  121. } while (ch != endToken);
  122. if (ch == -1) {
  123. if (queuedData == null || queueIndex == -1) {
  124. queuedData = key.toString();
  125. } else {
  126. queuedData
  127. = key.toString() + queuedData.substring(queueIndex);
  128. }
  129. queueIndex = 0;
  130. return beginToken;
  131. } else {
  132. key.setLength(key.length() - 1);
  133. final String replaceWith = (String) hash.get(key.toString());
  134. if (replaceWith != null) {
  135. if (replaceWith.length() > 0) {
  136. replaceData = replaceWith;
  137. replaceIndex = 0;
  138. }
  139. return read();
  140. } else {
  141. String newData = key.toString() + endToken;
  142. if (queuedData == null || queueIndex == -1) {
  143. queuedData = newData;
  144. } else {
  145. queuedData = newData + queuedData.substring(queueIndex);
  146. }
  147. queueIndex = 0;
  148. return beginToken;
  149. }
  150. }
  151. }
  152. return ch;
  153. }
  154. /**
  155. * Sets the "begin token" character.
  156. *
  157. * @param beginToken the character used to denote the beginning of a token
  158. */
  159. public final void setBeginToken(final char beginToken) {
  160. this.beginToken = beginToken;
  161. }
  162. /**
  163. * Returns the "begin token" character.
  164. *
  165. * @return the character used to denote the beginning of a token
  166. */
  167. private final char getBeginToken() {
  168. return beginToken;
  169. }
  170. /**
  171. * Sets the "end token" character.
  172. *
  173. * @param endToken the character used to denote the end of a token
  174. */
  175. public final void setEndToken(final char endToken) {
  176. this.endToken = endToken;
  177. }
  178. /**
  179. * Returns the "end token" character.
  180. *
  181. * @return the character used to denote the end of a token
  182. */
  183. private final char getEndToken() {
  184. return endToken;
  185. }
  186. /**
  187. * Adds a token element to the map of tokens to replace.
  188. *
  189. * @param token The token to add to the map of replacements.
  190. * Must not be <code>null</code>.
  191. */
  192. public final void addConfiguredToken(final Token token) {
  193. hash.put(token.getKey(), token.getValue());
  194. }
  195. /**
  196. * Sets the map of tokens to replace.
  197. *
  198. * @param hash A map (String->String) of token keys to replacement
  199. * values. Must not be <code>null</code>.
  200. */
  201. private void setTokens(final Hashtable hash) {
  202. this.hash = hash;
  203. }
  204. /**
  205. * Returns the map of tokens which will be replaced.
  206. *
  207. * @return a map (String->String) of token keys to replacement
  208. * values
  209. */
  210. private final Hashtable getTokens() {
  211. return hash;
  212. }
  213. /**
  214. * Creates a new ReplaceTokens using the passed in
  215. * Reader for instantiation.
  216. *
  217. * @param rdr A Reader object providing the underlying stream.
  218. * Must not be <code>null</code>.
  219. *
  220. * @return a new filter based on this configuration, but filtering
  221. * the specified reader
  222. */
  223. public final Reader chain(final Reader rdr) {
  224. ReplaceTokens newFilter = new ReplaceTokens(rdr);
  225. newFilter.setBeginToken(getBeginToken());
  226. newFilter.setEndToken(getEndToken());
  227. newFilter.setTokens(getTokens());
  228. newFilter.setInitialized(true);
  229. return newFilter;
  230. }
  231. /**
  232. * Initializes tokens and loads the replacee-replacer hashtable.
  233. */
  234. private final void initialize() {
  235. Parameter[] params = getParameters();
  236. if (params != null) {
  237. for (int i = 0; i < params.length; i++) {
  238. if (params[i] != null) {
  239. final String type = params[i].getType();
  240. if ("tokenchar".equals(type)) {
  241. final String name = params[i].getName();
  242. String value = params[i].getValue();
  243. if ("begintoken".equals(name)) {
  244. if (value.length() == 0) {
  245. throw new BuildException("Begin token cannot "
  246. + "be empty");
  247. }
  248. beginToken = params[i].getValue().charAt(0);
  249. } else if ("endtoken".equals(name)) {
  250. if (value.length() == 0) {
  251. throw new BuildException("End token cannot "
  252. + "be empty");
  253. }
  254. endToken = params[i].getValue().charAt(0);
  255. }
  256. } else if ("token".equals(type)) {
  257. final String name = params[i].getName();
  258. final String value = params[i].getValue();
  259. hash.put(name, value);
  260. }
  261. }
  262. }
  263. }
  264. }
  265. /**
  266. * Holds a token
  267. */
  268. public static class Token {
  269. /** Token key */
  270. private String key;
  271. /** Token value */
  272. private String value;
  273. /**
  274. * Sets the token key
  275. *
  276. * @param key The key for this token. Must not be <code>null</code>.
  277. */
  278. public final void setKey(String key) {
  279. this.key = key;
  280. }
  281. /**
  282. * Sets the token value
  283. *
  284. * @param value The value for this token. Must not be <code>null</code>.
  285. */
  286. public final void setValue(String value) {
  287. this.value = value;
  288. }
  289. /**
  290. * Returns the key for this token.
  291. *
  292. * @return the key for this token
  293. */
  294. public final String getKey() {
  295. return key;
  296. }
  297. /**
  298. * Returns the value for this token.
  299. *
  300. * @return the value for this token
  301. */
  302. public final String getValue() {
  303. return value;
  304. }
  305. }
  306. }