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.

Concat.java 31 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.File;
  20. import java.io.Reader;
  21. import java.io.Writer;
  22. import java.io.FileReader;
  23. import java.io.InputStream;
  24. import java.io.IOException;
  25. import java.io.StringReader;
  26. import java.io.BufferedReader;
  27. import java.io.FileInputStream;
  28. import java.io.InputStreamReader;
  29. import java.util.Arrays;
  30. import java.util.Collections;
  31. import java.util.Iterator;
  32. import java.util.Vector;
  33. import org.apache.tools.ant.Task;
  34. import org.apache.tools.ant.Project;
  35. import org.apache.tools.ant.BuildException;
  36. import org.apache.tools.ant.ProjectComponent;
  37. import org.apache.tools.ant.filters.util.ChainReaderHelper;
  38. import org.apache.tools.ant.types.Path;
  39. import org.apache.tools.ant.types.FileSet;
  40. import org.apache.tools.ant.types.FileList;
  41. import org.apache.tools.ant.types.FilterChain;
  42. import org.apache.tools.ant.types.Resource;
  43. import org.apache.tools.ant.types.ResourceCollection;
  44. import org.apache.tools.ant.types.resources.FileResource;
  45. import org.apache.tools.ant.types.resources.Intersect;
  46. import org.apache.tools.ant.types.resources.LogOutputResource;
  47. import org.apache.tools.ant.types.resources.Restrict;
  48. import org.apache.tools.ant.types.resources.Resources;
  49. import org.apache.tools.ant.types.resources.StringResource;
  50. import org.apache.tools.ant.types.resources.selectors.Not;
  51. import org.apache.tools.ant.types.resources.selectors.Exists;
  52. import org.apache.tools.ant.types.resources.selectors.ResourceSelector;
  53. import org.apache.tools.ant.types.selectors.SelectorUtils;
  54. import org.apache.tools.ant.util.ConcatResourceInputStream;
  55. import org.apache.tools.ant.util.FileUtils;
  56. import org.apache.tools.ant.util.ReaderInputStream;
  57. import org.apache.tools.ant.util.ResourceUtils;
  58. import org.apache.tools.ant.util.StringUtils;
  59. /**
  60. * This class contains the 'concat' task, used to concatenate a series
  61. * of files into a single stream. The destination of this stream may
  62. * be the system console, or a file. The following is a sample
  63. * invocation:
  64. *
  65. * <pre>
  66. * &lt;concat destfile=&quot;${build.dir}/index.xml&quot;
  67. * append=&quot;false&quot;&gt;
  68. *
  69. * &lt;fileset dir=&quot;${xml.root.dir}&quot;
  70. * includes=&quot;*.xml&quot; /&gt;
  71. *
  72. * &lt;/concat&gt;
  73. * </pre>
  74. *
  75. */
  76. public class Concat extends Task implements ResourceCollection {
  77. // The size of buffers to be used
  78. private static final int BUFFER_SIZE = 8192;
  79. private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
  80. private static final ResourceSelector EXISTS = new Exists();
  81. private static final ResourceSelector NOT_EXISTS = new Not(EXISTS);
  82. /**
  83. * sub element points to a file or contains text
  84. */
  85. public static class TextElement extends ProjectComponent {
  86. private String value = "";
  87. private boolean trimLeading = false;
  88. private boolean trim = false;
  89. private boolean filtering = true;
  90. private String encoding = null;
  91. /**
  92. * whether to filter the text in this element
  93. * or not.
  94. *
  95. * @param filtering true if the text should be filtered.
  96. * the default value is true.
  97. */
  98. public void setFiltering(boolean filtering) {
  99. this.filtering = filtering;
  100. }
  101. /** return the filtering attribute */
  102. private boolean getFiltering() {
  103. return filtering;
  104. }
  105. /**
  106. * The encoding of the text element
  107. *
  108. * @param encoding the name of the charset used to encode
  109. */
  110. public void setEncoding(String encoding) {
  111. this.encoding = encoding;
  112. }
  113. /**
  114. * set the text using a file
  115. * @param file the file to use
  116. * @throws BuildException if the file does not exist, or cannot be
  117. * read
  118. */
  119. public void setFile(File file) throws BuildException {
  120. // non-existing files are not allowed
  121. if (!file.exists()) {
  122. throw new BuildException("File " + file + " does not exist.");
  123. }
  124. BufferedReader reader = null;
  125. try {
  126. if (this.encoding == null) {
  127. reader = new BufferedReader(new FileReader(file));
  128. } else {
  129. reader = new BufferedReader(
  130. new InputStreamReader(new FileInputStream(file),
  131. this.encoding));
  132. }
  133. value = FileUtils.safeReadFully(reader);
  134. } catch (IOException ex) {
  135. throw new BuildException(ex);
  136. } finally {
  137. FileUtils.close(reader);
  138. }
  139. }
  140. /**
  141. * set the text using inline
  142. * @param value the text to place inline
  143. */
  144. public void addText(String value) {
  145. this.value += getProject().replaceProperties(value);
  146. }
  147. /**
  148. * s:^\s*:: on each line of input
  149. * @param strip if true do the trim
  150. */
  151. public void setTrimLeading(boolean strip) {
  152. this.trimLeading = strip;
  153. }
  154. /**
  155. * whether to call text.trim()
  156. * @param trim if true trim the text
  157. */
  158. public void setTrim(boolean trim) {
  159. this.trim = trim;
  160. }
  161. /**
  162. * @return the text, after possible trimming
  163. */
  164. public String getValue() {
  165. if (value == null) {
  166. value = "";
  167. }
  168. if (value.trim().length() == 0) {
  169. value = "";
  170. }
  171. if (trimLeading) {
  172. char[] current = value.toCharArray();
  173. StringBuffer b = new StringBuffer(current.length);
  174. boolean startOfLine = true;
  175. int pos = 0;
  176. while (pos < current.length) {
  177. char ch = current[pos++];
  178. if (startOfLine) {
  179. if (ch == ' ' || ch == '\t') {
  180. continue;
  181. }
  182. startOfLine = false;
  183. }
  184. b.append(ch);
  185. if (ch == '\n' || ch == '\r') {
  186. startOfLine = true;
  187. }
  188. }
  189. value = b.toString();
  190. }
  191. if (trim) {
  192. value = value.trim();
  193. }
  194. return value;
  195. }
  196. }
  197. private interface ReaderFactory {
  198. Reader getReader(Object o) throws IOException;
  199. }
  200. /**
  201. * This class reads from each of the source files in turn.
  202. * The concatentated result can then be filtered as
  203. * a single stream.
  204. */
  205. private final class MultiReader extends Reader {
  206. private Reader reader = null;
  207. private int lastPos = 0;
  208. private char[] lastChars = new char[eolString.length()];
  209. private boolean needAddSeparator = false;
  210. private Iterator readerSources;
  211. private ReaderFactory factory;
  212. private MultiReader(Iterator readerSources, ReaderFactory factory) {
  213. this.readerSources = readerSources;
  214. this.factory = factory;
  215. }
  216. private Reader getReader() throws IOException {
  217. if (reader == null && readerSources.hasNext()) {
  218. reader = factory.getReader(readerSources.next());
  219. Arrays.fill(lastChars, (char) 0);
  220. }
  221. return reader;
  222. }
  223. private void nextReader() throws IOException {
  224. close();
  225. reader = null;
  226. }
  227. /**
  228. * Read a character from the current reader object. Advance
  229. * to the next if the reader is finished.
  230. * @return the character read, -1 for EOF on the last reader.
  231. * @exception IOException - possibly thrown by the read for a reader
  232. * object.
  233. */
  234. public int read() throws IOException {
  235. if (needAddSeparator) {
  236. int ret = eolString.charAt(lastPos++);
  237. if (lastPos >= eolString.length()) {
  238. lastPos = 0;
  239. needAddSeparator = false;
  240. }
  241. return ret;
  242. }
  243. while (getReader() != null) {
  244. int ch = getReader().read();
  245. if (ch == -1) {
  246. nextReader();
  247. if (isFixLastLine() && isMissingEndOfLine()) {
  248. needAddSeparator = true;
  249. lastPos = 0;
  250. }
  251. } else {
  252. addLastChar((char) ch);
  253. return ch;
  254. }
  255. }
  256. return -1;
  257. }
  258. /**
  259. * Read into the buffer <code>cbuf</code>.
  260. * @param cbuf The array to be read into.
  261. * @param off The offset.
  262. * @param len The length to read.
  263. * @exception IOException - possibly thrown by the reads to the
  264. * reader objects.
  265. */
  266. public int read(char[] cbuf, int off, int len)
  267. throws IOException {
  268. int amountRead = 0;
  269. while (getReader() != null || needAddSeparator) {
  270. if (needAddSeparator) {
  271. cbuf[off] = eolString.charAt(lastPos++);
  272. if (lastPos >= eolString.length()) {
  273. lastPos = 0;
  274. needAddSeparator = false;
  275. }
  276. len--;
  277. off++;
  278. amountRead++;
  279. if (len == 0) {
  280. return amountRead;
  281. }
  282. continue;
  283. }
  284. int nRead = getReader().read(cbuf, off, len);
  285. if (nRead == -1 || nRead == 0) {
  286. nextReader();
  287. if (isFixLastLine() && isMissingEndOfLine()) {
  288. needAddSeparator = true;
  289. lastPos = 0;
  290. }
  291. } else {
  292. if (isFixLastLine()) {
  293. for (int i = nRead;
  294. i > (nRead - lastChars.length);
  295. --i) {
  296. if (i <= 0) {
  297. break;
  298. }
  299. addLastChar(cbuf[off + i - 1]);
  300. }
  301. }
  302. len -= nRead;
  303. off += nRead;
  304. amountRead += nRead;
  305. if (len == 0) {
  306. return amountRead;
  307. }
  308. }
  309. }
  310. if (amountRead == 0) {
  311. return -1;
  312. } else {
  313. return amountRead;
  314. }
  315. }
  316. /**
  317. * Close the current reader
  318. */
  319. public void close() throws IOException {
  320. if (reader != null) {
  321. reader.close();
  322. }
  323. }
  324. /**
  325. * if checking for end of line at end of file
  326. * add a character to the lastchars buffer
  327. */
  328. private void addLastChar(char ch) {
  329. for (int i = lastChars.length - 2; i >= 0; --i) {
  330. lastChars[i] = lastChars[i + 1];
  331. }
  332. lastChars[lastChars.length - 1] = ch;
  333. }
  334. /**
  335. * return true if the lastchars buffer does
  336. * not contain the lineseparator
  337. */
  338. private boolean isMissingEndOfLine() {
  339. for (int i = 0; i < lastChars.length; ++i) {
  340. if (lastChars[i] != eolString.charAt(i)) {
  341. return true;
  342. }
  343. }
  344. return false;
  345. }
  346. private boolean isFixLastLine() {
  347. return fixLastLine && textBuffer == null;
  348. }
  349. }
  350. private final class ConcatResource extends Resource {
  351. private ResourceCollection c;
  352. private ConcatResource(ResourceCollection c) {
  353. this.c = c;
  354. }
  355. public InputStream getInputStream() throws IOException {
  356. if (binary) {
  357. ConcatResourceInputStream result = new ConcatResourceInputStream(c);
  358. result.setManagingComponent(this);
  359. return result;
  360. }
  361. Reader resourceReader = getFilteredReader(
  362. new MultiReader(c.iterator(), resourceReaderFactory));
  363. Reader rdr;
  364. if (header == null && footer == null) {
  365. rdr = resourceReader;
  366. } else {
  367. int readerCount = 1;
  368. if (header != null) {
  369. readerCount++;
  370. }
  371. if (footer != null) {
  372. readerCount++;
  373. }
  374. Reader[] readers = new Reader[readerCount];
  375. int pos = 0;
  376. if (header != null) {
  377. readers[pos] = new StringReader(header.getValue());
  378. if (header.getFiltering()) {
  379. readers[pos] = getFilteredReader(readers[pos]);
  380. }
  381. pos++;
  382. }
  383. readers[pos++] = resourceReader;
  384. if (footer != null) {
  385. readers[pos] = new StringReader(footer.getValue());
  386. if (footer.getFiltering()) {
  387. readers[pos] = getFilteredReader(readers[pos]);
  388. }
  389. }
  390. rdr = new MultiReader(Arrays.asList(readers).iterator(),
  391. identityReaderFactory);
  392. }
  393. return outputEncoding == null ? new ReaderInputStream(rdr)
  394. : new ReaderInputStream(rdr, outputEncoding);
  395. }
  396. public String getName() {
  397. return resourceName == null
  398. ? "concat (" + String.valueOf(c) + ")" : resourceName;
  399. }
  400. }
  401. // Attributes.
  402. /**
  403. * The destination of the stream. If <code>null</code>, the system
  404. * console is used.
  405. */
  406. private Resource dest;
  407. /**
  408. * Whether or not the stream should be appended if the destination file
  409. * exists.
  410. * Defaults to <code>false</code>.
  411. */
  412. private boolean append;
  413. /**
  414. * Stores the input file encoding.
  415. */
  416. private String encoding;
  417. /** Stores the output file encoding. */
  418. private String outputEncoding;
  419. /** Stores the binary attribute */
  420. private boolean binary;
  421. // Child elements.
  422. /**
  423. * This buffer stores the text within the 'concat' element.
  424. */
  425. private StringBuffer textBuffer;
  426. /**
  427. * Stores a collection of file sets and/or file lists, used to
  428. * select multiple files for concatenation.
  429. */
  430. private Resources rc;
  431. /** for filtering the concatenated */
  432. private Vector filterChains;
  433. /** ignore dates on input files */
  434. private boolean forceOverwrite = true;
  435. /** overwrite read-only files */
  436. private boolean force = false;
  437. /** String to place at the start of the concatented stream */
  438. private TextElement footer;
  439. /** String to place at the end of the concatented stream */
  440. private TextElement header;
  441. /** add missing line.separator to files **/
  442. private boolean fixLastLine = false;
  443. /** endofline for fixlast line */
  444. private String eolString;
  445. /** outputwriter */
  446. private Writer outputWriter = null;
  447. /** whether to not create dest if no source files are
  448. * available */
  449. private boolean ignoreEmpty = true;
  450. /** exposed resource name */
  451. private String resourceName;
  452. private ReaderFactory resourceReaderFactory = new ReaderFactory() {
  453. public Reader getReader(Object o) throws IOException {
  454. InputStream is = ((Resource) o).getInputStream();
  455. return new BufferedReader(encoding == null
  456. ? new InputStreamReader(is)
  457. : new InputStreamReader(is, encoding));
  458. }
  459. };
  460. private ReaderFactory identityReaderFactory = new ReaderFactory() {
  461. public Reader getReader(Object o) {
  462. return (Reader) o;
  463. }
  464. };
  465. /**
  466. * Construct a new Concat task.
  467. */
  468. public Concat() {
  469. reset();
  470. }
  471. /**
  472. * Reset state to default.
  473. */
  474. public void reset() {
  475. append = false;
  476. forceOverwrite = true;
  477. dest = null;
  478. encoding = null;
  479. outputEncoding = null;
  480. fixLastLine = false;
  481. filterChains = null;
  482. footer = null;
  483. header = null;
  484. binary = false;
  485. outputWriter = null;
  486. textBuffer = null;
  487. eolString = StringUtils.LINE_SEP;
  488. rc = null;
  489. ignoreEmpty = true;
  490. force = false;
  491. }
  492. // Attribute setters.
  493. /**
  494. * Sets the destination file, or uses the console if not specified.
  495. * @param destinationFile the destination file
  496. */
  497. public void setDestfile(File destinationFile) {
  498. setDest(new FileResource(destinationFile));
  499. }
  500. /**
  501. * Set the resource to write to.
  502. * @param dest the Resource to write to.
  503. * @since Ant 1.8
  504. */
  505. public void setDest(Resource dest) {
  506. this.dest = dest;
  507. }
  508. /**
  509. * Sets the behavior when the destination exists. If set to
  510. * <code>true</code> the task will append the stream data an
  511. * {@link Appendable} resource; otherwise existing content will be
  512. * overwritten. Defaults to <code>false</code>.
  513. * @param append if true append output.
  514. */
  515. public void setAppend(boolean append) {
  516. this.append = append;
  517. }
  518. /**
  519. * Sets the character encoding
  520. * @param encoding the encoding of the input stream and unless
  521. * outputencoding is set, the outputstream.
  522. */
  523. public void setEncoding(String encoding) {
  524. this.encoding = encoding;
  525. if (outputEncoding == null) {
  526. outputEncoding = encoding;
  527. }
  528. }
  529. /**
  530. * Sets the character encoding for outputting
  531. * @param outputEncoding the encoding for the output file
  532. * @since Ant 1.6
  533. */
  534. public void setOutputEncoding(String outputEncoding) {
  535. this.outputEncoding = outputEncoding;
  536. }
  537. /**
  538. * Force overwrite existing destination file
  539. * @param forceOverwrite if true always overwrite, otherwise only
  540. * overwrite if the output file is older any of the
  541. * input files.
  542. * @since Ant 1.6
  543. * @deprecated use #setOverwrite instead
  544. */
  545. public void setForce(boolean forceOverwrite) {
  546. this.forceOverwrite = forceOverwrite;
  547. }
  548. /**
  549. * Force overwrite existing destination file
  550. * @param forceOverwrite if true always overwrite, otherwise only
  551. * overwrite if the output file is older any of the
  552. * input files.
  553. * @since Ant 1.8.2
  554. */
  555. public void setOverwrite(boolean forceOverwrite) {
  556. setForce(forceOverwrite);
  557. }
  558. /**
  559. * Whether read-only destinations will be overwritten.
  560. *
  561. * <p>Defaults to false</p>
  562. *
  563. * @since Ant 1.8.2
  564. */
  565. public void setForceReadOnly(boolean f) {
  566. force = f;
  567. }
  568. /**
  569. * Sets the behavior when no source resource files are available. If set to
  570. * <code>false</code> the destination file will always be created.
  571. * Defaults to <code>true</code>.
  572. * @param ignoreEmpty if false honour destinationfile creation.
  573. * @since Ant 1.8.0
  574. */
  575. public void setIgnoreEmpty(boolean ignoreEmpty) {
  576. this.ignoreEmpty = ignoreEmpty;
  577. }
  578. /**
  579. * Set the name that will be reported by the exposed {@link Resource}.
  580. * @param resourceName to set
  581. * @since Ant 1.8.3
  582. */
  583. public void setResourceName(String resourceName) {
  584. this.resourceName = resourceName;
  585. }
  586. // Nested element creators.
  587. /**
  588. * Path of files to concatenate.
  589. * @return the path used for concatenating
  590. * @since Ant 1.6
  591. */
  592. public Path createPath() {
  593. Path path = new Path(getProject());
  594. add(path);
  595. return path;
  596. }
  597. /**
  598. * Set of files to concatenate.
  599. * @param set the set of files
  600. */
  601. public void addFileset(FileSet set) {
  602. add(set);
  603. }
  604. /**
  605. * List of files to concatenate.
  606. * @param list the list of files
  607. */
  608. public void addFilelist(FileList list) {
  609. add(list);
  610. }
  611. /**
  612. * Add an arbitrary ResourceCollection.
  613. * @param c the ResourceCollection to add.
  614. * @since Ant 1.7
  615. */
  616. public void add(ResourceCollection c) {
  617. synchronized (this) {
  618. if (rc == null) {
  619. rc = new Resources();
  620. rc.setProject(getProject());
  621. rc.setCache(true);
  622. }
  623. }
  624. rc.add(c);
  625. }
  626. /**
  627. * Adds a FilterChain.
  628. * @param filterChain a filterchain to filter the concatenated input
  629. * @since Ant 1.6
  630. */
  631. public void addFilterChain(FilterChain filterChain) {
  632. if (filterChains == null) {
  633. filterChains = new Vector();
  634. }
  635. filterChains.addElement(filterChain);
  636. }
  637. /**
  638. * This method adds text which appears in the 'concat' element.
  639. * @param text the text to be concated.
  640. */
  641. public void addText(String text) {
  642. if (textBuffer == null) {
  643. // Initialize to the size of the first text fragment, with
  644. // the hopes that it's the only one.
  645. textBuffer = new StringBuffer(text.length());
  646. }
  647. // Append the fragment -- we defer property replacement until
  648. // later just in case we get a partial property in a fragment.
  649. textBuffer.append(text);
  650. }
  651. /**
  652. * Add a header to the concatenated output
  653. * @param headerToAdd the header
  654. * @since Ant 1.6
  655. */
  656. public void addHeader(TextElement headerToAdd) {
  657. this.header = headerToAdd;
  658. }
  659. /**
  660. * Add a footer to the concatenated output
  661. * @param footerToAdd the footer
  662. * @since Ant 1.6
  663. */
  664. public void addFooter(TextElement footerToAdd) {
  665. this.footer = footerToAdd;
  666. }
  667. /**
  668. * Append line.separator to files that do not end
  669. * with a line.separator, default false.
  670. * @param fixLastLine if true make sure each input file has
  671. * new line on the concatenated stream
  672. * @since Ant 1.6
  673. */
  674. public void setFixLastLine(boolean fixLastLine) {
  675. this.fixLastLine = fixLastLine;
  676. }
  677. /**
  678. * Specify the end of line to find and to add if
  679. * not present at end of each input file. This attribute
  680. * is used in conjunction with fixlastline.
  681. * @param crlf the type of new line to add -
  682. * cr, mac, lf, unix, crlf, or dos
  683. * @since Ant 1.6
  684. */
  685. public void setEol(FixCRLF.CrLf crlf) {
  686. String s = crlf.getValue();
  687. if (s.equals("cr") || s.equals("mac")) {
  688. eolString = "\r";
  689. } else if (s.equals("lf") || s.equals("unix")) {
  690. eolString = "\n";
  691. } else if (s.equals("crlf") || s.equals("dos")) {
  692. eolString = "\r\n";
  693. }
  694. }
  695. /**
  696. * Set the output writer. This is to allow
  697. * concat to be used as a nested element.
  698. * @param outputWriter the output writer.
  699. * @since Ant 1.6
  700. */
  701. public void setWriter(Writer outputWriter) {
  702. this.outputWriter = outputWriter;
  703. }
  704. /**
  705. * Set the binary attribute. If true, concat will concatenate the files
  706. * byte for byte. This mode does not allow any filtering or other
  707. * modifications to the input streams. The default value is false.
  708. * @since Ant 1.6.2
  709. * @param binary if true, enable binary mode.
  710. */
  711. public void setBinary(boolean binary) {
  712. this.binary = binary;
  713. }
  714. /**
  715. * Execute the concat task.
  716. */
  717. public void execute() {
  718. validate();
  719. if (binary && dest == null) {
  720. throw new BuildException(
  721. "dest|destfile attribute is required for binary concatenation");
  722. }
  723. ResourceCollection c = getResources();
  724. if (isUpToDate(c)) {
  725. log(dest + " is up-to-date.", Project.MSG_VERBOSE);
  726. return;
  727. }
  728. if (c.size() == 0 && ignoreEmpty) {
  729. return;
  730. }
  731. try {
  732. //most of these are defaulted because the concat-as-a-resource code hijacks a lot:
  733. ResourceUtils.copyResource(new ConcatResource(c), dest == null
  734. ? new LogOutputResource(this, Project.MSG_WARN)
  735. : dest,
  736. null, null, true, false, append, null,
  737. null, getProject(), force);
  738. } catch (IOException e) {
  739. throw new BuildException("error concatenating content to " + dest, e);
  740. }
  741. }
  742. /**
  743. * Implement ResourceCollection.
  744. * @return Iterator<Resource>.
  745. */
  746. public Iterator<Resource> iterator() {
  747. validate();
  748. return Collections.<Resource>singletonList(new ConcatResource(getResources())).iterator();
  749. }
  750. /**
  751. * Implement ResourceCollection.
  752. * @return 1.
  753. */
  754. public int size() {
  755. return 1;
  756. }
  757. /**
  758. * Implement ResourceCollection.
  759. * @return false.
  760. */
  761. public boolean isFilesystemOnly() {
  762. return false;
  763. }
  764. /**
  765. * Validate configuration options.
  766. */
  767. private void validate() {
  768. // treat empty nested text as no text
  769. sanitizeText();
  770. // if binary check if incompatible attributes are used
  771. if (binary) {
  772. if (textBuffer != null) {
  773. throw new BuildException(
  774. "Nested text is incompatible with binary concatenation");
  775. }
  776. if (encoding != null || outputEncoding != null) {
  777. throw new BuildException(
  778. "Setting input or output encoding is incompatible with binary"
  779. + " concatenation");
  780. }
  781. if (filterChains != null) {
  782. throw new BuildException(
  783. "Setting filters is incompatible with binary concatenation");
  784. }
  785. if (fixLastLine) {
  786. throw new BuildException(
  787. "Setting fixlastline is incompatible with binary concatenation");
  788. }
  789. if (header != null || footer != null) {
  790. throw new BuildException(
  791. "Nested header or footer is incompatible with binary concatenation");
  792. }
  793. }
  794. if (dest != null && outputWriter != null) {
  795. throw new BuildException(
  796. "Cannot specify both a destination resource and an output writer");
  797. }
  798. // Sanity check our inputs.
  799. if (rc == null && textBuffer == null) {
  800. // Nothing to concatenate!
  801. throw new BuildException(
  802. "At least one resource must be provided, or some text.");
  803. }
  804. if (rc != null && textBuffer != null) {
  805. // If using resources, disallow inline text. This is similar to
  806. // using GNU 'cat' with file arguments--stdin is simply ignored.
  807. throw new BuildException(
  808. "Cannot include inline text when using resources.");
  809. }
  810. }
  811. /**
  812. * Get the resources to concatenate.
  813. */
  814. private ResourceCollection getResources() {
  815. if (rc == null) {
  816. return new StringResource(getProject(), textBuffer.toString());
  817. }
  818. if (dest != null) {
  819. Intersect checkDestNotInSources = new Intersect();
  820. checkDestNotInSources.setProject(getProject());
  821. checkDestNotInSources.add(rc);
  822. checkDestNotInSources.add(dest);
  823. if (checkDestNotInSources.size() > 0) {
  824. throw new BuildException("Destination resource " + dest
  825. + " was specified as an input resource.");
  826. }
  827. }
  828. Restrict noexistRc = new Restrict();
  829. noexistRc.add(NOT_EXISTS);
  830. noexistRc.add(rc);
  831. for (Resource r : noexistRc) {
  832. log(r + " does not exist.", Project.MSG_ERR);
  833. }
  834. Restrict result = new Restrict();
  835. result.add(EXISTS);
  836. result.add(rc);
  837. return result;
  838. }
  839. private boolean isUpToDate(ResourceCollection c) {
  840. if (dest == null || forceOverwrite) {
  841. return false;
  842. }
  843. for (Resource r : c) {
  844. if (SelectorUtils.isOutOfDate(r, dest, FILE_UTILS.getFileTimestampGranularity())) {
  845. return false;
  846. }
  847. }
  848. return true;
  849. }
  850. /**
  851. * Treat empty nested text as no text.
  852. *
  853. * <p>Depending on the XML parser, addText may have been called
  854. * for &quot;ignorable whitespace&quot; as well.</p>
  855. */
  856. private void sanitizeText() {
  857. if (textBuffer != null && "".equals(textBuffer.toString().trim())) {
  858. textBuffer = null;
  859. }
  860. }
  861. private Reader getFilteredReader(Reader r) {
  862. if (filterChains == null) {
  863. return r;
  864. }
  865. ChainReaderHelper helper = new ChainReaderHelper();
  866. helper.setBufferSize(BUFFER_SIZE);
  867. helper.setPrimaryReader(r);
  868. helper.setFilterChains(filterChains);
  869. helper.setProject(getProject());
  870. //used to be a BufferedReader here, but we should be buffering lower:
  871. return helper.getAssembledReader();
  872. }
  873. }