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

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