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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowlegement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowlegement may appear in the software itself,
  24. * if and wherever such third-party acknowlegements normally appear.
  25. *
  26. * 4. The names "Ant" and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Group.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.tools.ant.taskdefs;
  55. import java.io.BufferedReader;
  56. import java.io.BufferedWriter;
  57. import java.io.File;
  58. import java.io.FileInputStream;
  59. import java.io.FileOutputStream;
  60. import java.io.FileReader;
  61. import java.io.IOException;
  62. import java.io.InputStreamReader;
  63. import java.io.OutputStream;
  64. import java.io.OutputStreamWriter;
  65. import java.io.PrintWriter;
  66. import java.io.Reader;
  67. import java.io.StringReader;
  68. import java.io.Writer;
  69. import java.util.Enumeration;
  70. import java.util.Vector;
  71. import org.apache.tools.ant.BuildException;
  72. import org.apache.tools.ant.DirectoryScanner;
  73. import org.apache.tools.ant.Project;
  74. import org.apache.tools.ant.Task;
  75. import org.apache.tools.ant.filters.util.ChainReaderHelper;
  76. import org.apache.tools.ant.types.FileList;
  77. import org.apache.tools.ant.types.FileSet;
  78. import org.apache.tools.ant.types.FilterChain;
  79. import org.apache.tools.ant.types.Path;
  80. import org.apache.tools.ant.util.FileUtils;
  81. /**
  82. * This class contains the 'concat' task, used to concatenate a series
  83. * of files into a single stream. The destination of this stream may
  84. * be the system console, or a file. The following is a sample
  85. * invocation:
  86. *
  87. * <pre>
  88. * &lt;concat destfile=&quot;${build.dir}/index.xml&quot;
  89. * append=&quot;false&quot;&gt;
  90. *
  91. * &lt;fileset dir=&quot;${xml.root.dir}&quot;
  92. * includes=&quot;*.xml&quot; /&gt;
  93. *
  94. * &lt;/concat&gt;
  95. * </pre>
  96. *
  97. * @author <a href="mailto:derek@activate.net">Derek Slager</a>
  98. * @author Peter Reilly
  99. */
  100. public class Concat extends Task {
  101. // The size of buffers to be used
  102. private static final int BUFFER_SIZE = 8192;
  103. // Attributes.
  104. /**
  105. * The destination of the stream. If <code>null</code>, the system
  106. * console is used.
  107. */
  108. private File destinationFile = null;
  109. /**
  110. * Whether or not the stream should be appended if the destination file
  111. * exists.
  112. * Defaults to <code>false</code>.
  113. */
  114. private boolean append = false;
  115. /**
  116. * Stores the input file encoding.
  117. */
  118. private String encoding = null;
  119. /** Stores the output file encoding. */
  120. private String outputEncoding = null;
  121. // Child elements.
  122. /**
  123. * This buffer stores the text within the 'concat' element.
  124. */
  125. private StringBuffer textBuffer;
  126. /**
  127. * Stores a collection of file sets and/or file lists, used to
  128. * select multiple files for concatenation.
  129. */
  130. private Vector sources = new Vector();
  131. /** for filtering the concatenated */
  132. private Vector filterChains = null;
  133. /** ignore dates on input files */
  134. private boolean forceOverwrite = true;
  135. /** String to place at the start of the concatented stream */
  136. private TextElement footer;
  137. /** String to place at the end of the concatented stream */
  138. private TextElement header;
  139. /** add missing line.separator to files **/
  140. private boolean fixLastLine = false;
  141. /** endofline for fixlast line */
  142. private String eolString = System.getProperty("line.separator");
  143. /** outputwriter */
  144. private Writer outputWriter = null;
  145. /** internal variable - used to collect the source files from sources */
  146. private Vector sourceFiles = new Vector();
  147. /** 1.1 utilities and copy utilities */
  148. private static FileUtils fileUtils = FileUtils.newFileUtils();
  149. // Attribute setters.
  150. /**
  151. * Sets the destination file, or uses the console if not specified.
  152. * @param destinationFile the destination file
  153. */
  154. public void setDestfile(File destinationFile) {
  155. this.destinationFile = destinationFile;
  156. }
  157. /**
  158. * Sets the behavior when the destination file exists. If set to
  159. * <code>true</code> the stream data will be appended to the
  160. * existing file, otherwise the existing file will be
  161. * overwritten. Defaults to <code>false</code>.
  162. * @param append if true append to the file.
  163. */
  164. public void setAppend(boolean append) {
  165. this.append = append;
  166. }
  167. /**
  168. * Sets the character encoding
  169. * @param encoding the encoding of the input stream and unless
  170. * outputencoding is set, the outputstream.
  171. */
  172. public void setEncoding(String encoding) {
  173. this.encoding = encoding;
  174. if (outputEncoding == null) {
  175. outputEncoding = encoding;
  176. }
  177. }
  178. /**
  179. * Sets the character encoding for outputting
  180. * @param outputEncoding the encoding for the output file
  181. * @since Ant 1.6
  182. */
  183. public void setOutputEncoding(String outputEncoding) {
  184. this.outputEncoding = outputEncoding;
  185. }
  186. /**
  187. * Force overwrite existing destination file
  188. * @param force if true always overwrite, otherwise only overwrite
  189. * if the output file is older any of the input files.
  190. * @since Ant 1.6
  191. */
  192. public void setForce(boolean force) {
  193. this.forceOverwrite = force;
  194. }
  195. // Nested element creators.
  196. /**
  197. * Path of files to concatenate.
  198. * @return the path used for concatenating
  199. * @since Ant 1.6
  200. */
  201. public Path createPath() {
  202. Path path = new Path(getProject());
  203. sources.addElement(path);
  204. return path;
  205. }
  206. /**
  207. * Set of files to concatenate.
  208. * @param set the set of files
  209. */
  210. public void addFileset(FileSet set) {
  211. sources.addElement(set);
  212. }
  213. /**
  214. * List of files to concatenate.
  215. * @param list the list of files
  216. */
  217. public void addFilelist(FileList list) {
  218. sources.addElement(list);
  219. }
  220. /**
  221. * Adds a FilterChain.
  222. * @param filterChain a filterchain to filter the concatenated input
  223. * @since Ant 1.6
  224. */
  225. public void addFilterChain(FilterChain filterChain) {
  226. if (filterChains == null) {
  227. filterChains = new Vector();
  228. }
  229. filterChains.addElement(filterChain);
  230. }
  231. /**
  232. * This method adds text which appears in the 'concat' element.
  233. * @param text the text to be concated.
  234. */
  235. public void addText(String text) {
  236. if (textBuffer == null) {
  237. // Initialize to the size of the first text fragment, with
  238. // the hopes that it's the only one.
  239. textBuffer = new StringBuffer(text.length());
  240. }
  241. // Append the fragment -- we defer property replacement until
  242. // later just in case we get a partial property in a fragment.
  243. textBuffer.append(text);
  244. }
  245. /**
  246. * Add a header to the concatenated output
  247. * @param header the header
  248. * @since Ant 1.6
  249. */
  250. public void addHeader(TextElement header) {
  251. this.header = header;
  252. }
  253. /**
  254. * Add a footer to the concatenated output
  255. * @param footer the footer
  256. * @since Ant 1.6
  257. */
  258. public void addFooter(TextElement footer) {
  259. this.footer = footer;
  260. }
  261. /**
  262. * Append line.separator to files that do not end
  263. * with a line.separator, default false.
  264. * @param fixLastLine if true make sure each input file has
  265. * new line on the concatenated stream
  266. * @since Ant 1.6
  267. */
  268. public void setFixLastLine(boolean fixLastLine) {
  269. this.fixLastLine = fixLastLine;
  270. }
  271. /**
  272. * Specify the end of line to find and to add if
  273. * not present at end of each input file. This attribute
  274. * is used in conjuction with fixlastline.
  275. * @param enum the type of new line to add -
  276. * cr, mac, lf, unix, crlf, or dos
  277. * @since Ant 1.6
  278. */
  279. public void setEol(FixCRLF.CrLf enum) {
  280. String s = enum.getValue();
  281. if (s.equals("cr") || s.equals("mac")) {
  282. eolString = "\r";
  283. } else if (s.equals("lf") || s.equals("unix")) {
  284. eolString = "\n";
  285. } else if (s.equals("crlf") || s.equals("dos")) {
  286. eolString = "\r\n";
  287. }
  288. }
  289. /**
  290. * set the output writer, this is to allow
  291. * concat to be used as a nested element
  292. * @param outputWriter the output writer
  293. * @since Ant 1.6
  294. */
  295. public void setWriter(Writer outputWriter) {
  296. this.outputWriter = outputWriter;
  297. }
  298. /**
  299. * This method performs the concatenation.
  300. */
  301. public void execute() {
  302. // treat empty nested text as no text
  303. sanitizeText();
  304. if (destinationFile != null && outputWriter != null) {
  305. throw new BuildException(
  306. "Cannot specify both a destination file and an output writer");
  307. }
  308. // Sanity check our inputs.
  309. if (sources.size() == 0 && textBuffer == null) {
  310. // Nothing to concatenate!
  311. throw new BuildException(
  312. "At least one file must be provided, or some text.");
  313. }
  314. // If using filesets, disallow inline text. This is similar to
  315. // using GNU 'cat' with file arguments -- stdin is simply
  316. // ignored.
  317. if (sources.size() > 0 && textBuffer != null) {
  318. throw new BuildException(
  319. "Cannot include inline text when using filesets.");
  320. }
  321. // Iterate thru the sources - paths, filesets and filelists
  322. for (Enumeration e = sources.elements(); e.hasMoreElements();) {
  323. Object o = e.nextElement();
  324. if (o instanceof Path) {
  325. Path path = (Path) o;
  326. checkAddFiles(null, path.list());
  327. } else if (o instanceof FileSet) {
  328. FileSet fileSet = (FileSet) o;
  329. DirectoryScanner scanner =
  330. fileSet.getDirectoryScanner(getProject());
  331. checkAddFiles(fileSet.getDir(getProject()),
  332. scanner.getIncludedFiles());
  333. } else if (o instanceof FileList) {
  334. FileList fileList = (FileList) o;
  335. checkAddFiles(fileList.getDir(getProject()),
  336. fileList.getFiles(getProject()));
  337. }
  338. }
  339. // check if the files are outofdate
  340. if (destinationFile != null && !forceOverwrite
  341. && (sourceFiles.size() > 0) && destinationFile.exists()) {
  342. boolean outofdate = false;
  343. for (int i = 0; i < sourceFiles.size(); ++i) {
  344. File file = (File) sourceFiles.elementAt(i);
  345. if (file.lastModified() > destinationFile.lastModified()) {
  346. outofdate = true;
  347. break;
  348. }
  349. }
  350. if (!outofdate) {
  351. log(destinationFile + " is up-to-date.", Project.MSG_VERBOSE);
  352. return; // no need to do anything
  353. }
  354. }
  355. // Do nothing if all the sources are not present
  356. // And textBuffer is null
  357. if (textBuffer == null && sourceFiles.size() == 0
  358. && header == null && footer == null) {
  359. log("No existing files and no nested text, doing nothing",
  360. Project.MSG_INFO);
  361. return;
  362. }
  363. cat();
  364. }
  365. /**
  366. * Reset state to default.
  367. */
  368. public void reset() {
  369. append = false;
  370. forceOverwrite = true;
  371. destinationFile = null;
  372. encoding = null;
  373. outputEncoding = null;
  374. fixLastLine = false;
  375. sources.removeAllElements();
  376. sourceFiles.removeAllElements();
  377. filterChains = null;
  378. footer = null;
  379. header = null;
  380. }
  381. private void checkAddFiles(File base, String[] filenames) {
  382. for (int i = 0; i < filenames.length; ++i) {
  383. File file = new File(base, filenames[i]);
  384. if (!file.exists()) {
  385. log("File " + file + " does not exist.", Project.MSG_ERR);
  386. continue;
  387. }
  388. if (destinationFile != null
  389. && fileUtils.fileNameEquals(destinationFile, file)) {
  390. throw new BuildException("Input file \""
  391. + file + "\" "
  392. + "is the same as the output file.");
  393. }
  394. sourceFiles.addElement(file);
  395. }
  396. }
  397. /** perform the concatenation */
  398. private void cat() {
  399. OutputStream os = null;
  400. Reader reader = null;
  401. char[] buffer = new char[BUFFER_SIZE];
  402. try {
  403. PrintWriter writer = null;
  404. if (outputWriter != null) {
  405. writer = new PrintWriter(outputWriter);
  406. } else {
  407. if (destinationFile == null) {
  408. // Log using WARN so it displays in 'quiet' mode.
  409. os = new LogOutputStream(this, Project.MSG_WARN);
  410. } else {
  411. // ensure that the parent dir of dest file exists
  412. File parent = fileUtils.getParentFile(destinationFile);
  413. if (!parent.exists()) {
  414. parent.mkdirs();
  415. }
  416. os = new FileOutputStream(destinationFile.getAbsolutePath(),
  417. append);
  418. }
  419. if (outputEncoding == null) {
  420. writer = new PrintWriter(
  421. new BufferedWriter(
  422. new OutputStreamWriter(os)));
  423. } else {
  424. writer = new PrintWriter(
  425. new BufferedWriter(
  426. new OutputStreamWriter(os, outputEncoding)));
  427. }
  428. }
  429. if (header != null) {
  430. if (header.getFiltering()) {
  431. concatenate(
  432. buffer, writer, new StringReader(header.getValue()));
  433. } else {
  434. writer.print(header.getValue());
  435. }
  436. }
  437. if (textBuffer != null) {
  438. reader = new StringReader(
  439. getProject().replaceProperties(textBuffer.substring(0)));
  440. } else {
  441. reader = new MultiReader();
  442. }
  443. concatenate(buffer, writer, reader);
  444. if (footer != null) {
  445. if (footer.getFiltering()) {
  446. concatenate(
  447. buffer, writer, new StringReader(footer.getValue()));
  448. } else {
  449. writer.print(footer.getValue());
  450. }
  451. }
  452. writer.flush();
  453. if (os != null) {
  454. os.flush();
  455. }
  456. } catch (IOException ioex) {
  457. throw new BuildException("Error while concatenating: "
  458. + ioex.getMessage(), ioex);
  459. } finally {
  460. if (reader != null) {
  461. try {
  462. reader.close();
  463. } catch (IOException ignore) {
  464. // ignore
  465. }
  466. }
  467. if (os != null) {
  468. try {
  469. os.close();
  470. } catch (IOException ignore) {
  471. // ignore
  472. }
  473. }
  474. }
  475. }
  476. /** Concatenate a single reader to the writer using buffer */
  477. private void concatenate(char[] buffer, Writer writer, Reader in)
  478. throws IOException {
  479. if (filterChains != null) {
  480. ChainReaderHelper helper = new ChainReaderHelper();
  481. helper.setBufferSize(BUFFER_SIZE);
  482. helper.setPrimaryReader(in);
  483. helper.setFilterChains(filterChains);
  484. helper.setProject(getProject());
  485. in = new BufferedReader(helper.getAssembledReader());
  486. }
  487. while (true) {
  488. int nRead = in.read(buffer, 0, buffer.length);
  489. if (nRead == -1) {
  490. break;
  491. }
  492. writer.write(buffer, 0, nRead);
  493. }
  494. writer.flush();
  495. }
  496. /**
  497. * Treat empty nested text as no text.
  498. *
  499. * <p>Depending on the XML parser, addText may have been called
  500. * for &quot;ignorable whitespace&quot; as well.</p>
  501. */
  502. private void sanitizeText() {
  503. if (textBuffer != null) {
  504. if (textBuffer.substring(0).trim().length() == 0) {
  505. textBuffer = null;
  506. }
  507. }
  508. }
  509. /**
  510. * sub element points to a file or contains text
  511. */
  512. public static class TextElement {
  513. private String value = "";
  514. private boolean trimLeading = false;
  515. private boolean trim = false;
  516. private boolean filtering = true;
  517. private String encoding = null;
  518. /**
  519. * whether to filter the text in this element
  520. * or not.
  521. *
  522. * @param filtering true if the text should be filtered.
  523. * the default value is true.
  524. */
  525. public void setFiltering(boolean filtering) {
  526. this.filtering = filtering;
  527. }
  528. /** return the filtering attribute */
  529. private boolean getFiltering() {
  530. return filtering;
  531. }
  532. /**
  533. * The encoding of the text element
  534. *
  535. * @param encoding the name of the charset used to encode
  536. */
  537. public void setEncoding(String encoding) {
  538. this.encoding = encoding;
  539. }
  540. /**
  541. * set the text using a file
  542. * @param file the file to use
  543. * @throws BuildException if the file does not exist, or cannot be
  544. * read
  545. */
  546. public void setFile(File file) {
  547. // non-existing files are not allowed
  548. if (!file.exists()) {
  549. throw new BuildException("File " + file + " does not exist.");
  550. }
  551. BufferedReader reader = null;
  552. try {
  553. if (this.encoding == null) {
  554. reader = new BufferedReader(new FileReader(file));
  555. } else {
  556. reader = new BufferedReader(
  557. new InputStreamReader(new FileInputStream(file),
  558. this.encoding));
  559. }
  560. value = fileUtils.readFully(reader);
  561. } catch (IOException ex) {
  562. throw new BuildException(ex);
  563. } finally {
  564. if (reader != null) {
  565. try {
  566. reader.close();
  567. } catch (Throwable t) {
  568. // ignore
  569. }
  570. }
  571. }
  572. }
  573. /**
  574. * set the text using inline
  575. * @param value the text to place inline
  576. */
  577. public void addText(String value) {
  578. this.value += value;
  579. }
  580. /**
  581. * s:^\s*:: on each line of input
  582. * @param strip if true do the trim
  583. */
  584. public void setTrimLeading(boolean strip) {
  585. this.trimLeading = strip;
  586. }
  587. /**
  588. * whether to call text.trim()
  589. * @param trim if true trim the text
  590. */
  591. public void setTrim(boolean trim) {
  592. this.trim = trim;
  593. }
  594. /**
  595. * @return the text, after possible trimming
  596. */
  597. public String getValue() {
  598. if (value == null) {
  599. value = "";
  600. }
  601. if (value.trim().length() == 0) {
  602. value = "";
  603. }
  604. if (trimLeading) {
  605. char[] current = value.toCharArray();
  606. StringBuffer b = new StringBuffer(current.length);
  607. boolean startOfLine = true;
  608. int pos = 0;
  609. while (pos < current.length) {
  610. char ch = current[pos++];
  611. if (startOfLine) {
  612. if (ch == ' ' || ch == '\t') {
  613. continue;
  614. }
  615. startOfLine = false;
  616. }
  617. b.append(ch);
  618. if (ch == '\n' || ch == '\r') {
  619. startOfLine = true;
  620. }
  621. }
  622. value = b.toString();
  623. }
  624. if (trim) {
  625. value = value.trim();
  626. }
  627. return value;
  628. }
  629. }
  630. /**
  631. * This class reads from each of the source files in turn.
  632. * The concatentated result can then be filtered as
  633. * a single stream.
  634. */
  635. private class MultiReader extends Reader {
  636. private int pos = 0;
  637. private Reader reader = null;
  638. private int lastPos = 0;
  639. private char[] lastChars = new char[eolString.length()];
  640. private boolean needAddSeparator = false;
  641. private Reader getReader() throws IOException {
  642. if (reader == null) {
  643. if (encoding == null) {
  644. reader = new BufferedReader(
  645. new FileReader((File) sourceFiles.elementAt(pos)));
  646. } else {
  647. // invoke the zoo of io readers
  648. reader = new BufferedReader(
  649. new InputStreamReader(
  650. new FileInputStream(
  651. (File) sourceFiles.elementAt(pos)),
  652. encoding));
  653. }
  654. for (int i = 0; i < lastChars.length; ++i) {
  655. lastChars[i] = 0;
  656. }
  657. }
  658. return reader;
  659. }
  660. /**
  661. * Read a character from the current reader object. Advance
  662. * to the next if the reader is finished.
  663. * @return the character read, -1 for EOF on the last reader.
  664. * @exception IOException - possiblly thrown by the read for a reader
  665. * object.
  666. */
  667. public int read() throws IOException {
  668. if (needAddSeparator) {
  669. int ret = eolString.charAt(lastPos++);
  670. if (lastPos >= eolString.length()) {
  671. lastPos = 0;
  672. needAddSeparator = false;
  673. }
  674. return ret;
  675. }
  676. while (pos < sourceFiles.size()) {
  677. int ch = getReader().read();
  678. if (ch == -1) {
  679. reader.close();
  680. reader = null;
  681. if (fixLastLine && isMissingEndOfLine()) {
  682. needAddSeparator = true;
  683. lastPos = 0;
  684. }
  685. } else {
  686. addLastChar((char) ch);
  687. return ch;
  688. }
  689. pos++;
  690. }
  691. return -1;
  692. }
  693. /**
  694. * Read into the buffer <code>cbuf</code>.
  695. * @param cbuf The array to be read into.
  696. * @param off The offset.
  697. * @param len The length to read.
  698. * @exception IOException - possiblely thrown by the reads to the
  699. * reader objects.
  700. */
  701. public int read(char[] cbuf, int off, int len)
  702. throws IOException {
  703. int amountRead = 0;
  704. int iOff = off;
  705. while (pos < sourceFiles.size() || (needAddSeparator)) {
  706. if (needAddSeparator) {
  707. cbuf[off] = eolString.charAt(lastPos++);
  708. if (lastPos >= eolString.length()) {
  709. lastPos = 0;
  710. needAddSeparator = false;
  711. pos++;
  712. }
  713. len--;
  714. off++;
  715. amountRead++;
  716. if (len == 0) {
  717. return amountRead;
  718. }
  719. continue;
  720. }
  721. int nRead = getReader().read(cbuf, off, len);
  722. if (nRead == -1 || nRead == 0) {
  723. reader.close();
  724. reader = null;
  725. if (fixLastLine && isMissingEndOfLine()) {
  726. needAddSeparator = true;
  727. lastPos = 0;
  728. } else {
  729. pos++;
  730. }
  731. } else {
  732. if (fixLastLine) {
  733. for (int i = nRead;
  734. i > (nRead - lastChars.length);
  735. --i) {
  736. if (i < 0) {
  737. break;
  738. }
  739. addLastChar(cbuf[off + i]);
  740. }
  741. }
  742. len -= nRead;
  743. off += nRead;
  744. amountRead += nRead;
  745. if (len == 0) {
  746. return amountRead;
  747. }
  748. }
  749. }
  750. if (amountRead == 0) {
  751. return -1;
  752. } else {
  753. return amountRead;
  754. }
  755. }
  756. /**
  757. * Close the current reader
  758. */
  759. public void close() throws IOException {
  760. if (reader != null) {
  761. reader.close();
  762. }
  763. }
  764. /**
  765. * if checking for end of line at end of file
  766. * add a character to the lastchars buffer
  767. */
  768. private void addLastChar(char ch) {
  769. for (int i = lastChars.length - 2; i >= 0; --i) {
  770. lastChars[i] = lastChars[i + 1];
  771. }
  772. lastChars[lastChars.length - 1] = ch;
  773. }
  774. /**
  775. * return true if the lastchars buffer does
  776. * not contain the lineseparator
  777. */
  778. private boolean isMissingEndOfLine() {
  779. for (int i = 0; i < lastChars.length; ++i) {
  780. if (lastChars[i] != eolString.charAt(i)) {
  781. return true;
  782. }
  783. }
  784. return false;
  785. }
  786. }
  787. }