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

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