1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.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.FileWriter;
  25. import java.io.IOException;
  26. import java.io.InputStreamReader;
  27. import java.io.OutputStreamWriter;
  28. import java.io.Reader;
  29. import java.io.Writer;
  30. import java.util.Enumeration;
  31. import java.util.NoSuchElementException;
  32. import org.apache.tools.ant.BuildException;
  33. import org.apache.tools.ant.DirectoryScanner;
  34. import org.apache.tools.ant.Project;
  35. import org.apache.tools.ant.taskdefs.condition.Os;
  36. import org.apache.tools.ant.types.EnumeratedAttribute;
  37. import org.apache.tools.ant.util.FileUtils;
  38. /**
  39. * Converts text source files to local OS formatting conventions, as
  40. * well as repair text files damaged by misconfigured or misguided editors or
  41. * file transfer programs.
  42. * <p>
  43. * This task can take the following arguments:
  44. * <ul>
  45. * <li>srcdir
  46. * <li>destdir
  47. * <li>include
  48. * <li>exclude
  49. * <li>cr
  50. * <li>eol
  51. * <li>tab
  52. * <li>eof
  53. * <li>encoding
  54. * </ul>
  55. * Of these arguments, only <b>sourcedir</b> is required.
  56. * <p>
  57. * When this task executes, it will scan the srcdir based on the include
  58. * and exclude properties.
  59. * <p>
  60. * This version generalises the handling of EOL characters, and allows
  61. * for CR-only line endings (which I suspect is the standard on Macs.)
  62. * Tab handling has also been generalised to accommodate any tabwidth
  63. * from 2 to 80, inclusive. Importantly, it will leave untouched any
  64. * literal TAB characters embedded within string or character constants.
  65. * <p>
  66. * <em>Warning:</em> do not run on binary files.
  67. * <em>Caution:</em> run with care on carefully formatted files.
  68. * This may sound obvious, but if you don't specify asis, presume that
  69. * your files are going to be modified. If "tabs" is "add" or "remove",
  70. * whitespace characters may be added or removed as necessary. Similarly,
  71. * for CR's - in fact "eol"="crlf" or cr="add" can result in cr
  72. * characters being removed in one special case accommodated, i.e.,
  73. * CRCRLF is regarded as a single EOL to handle cases where other
  74. * programs have converted CRLF into CRCRLF.
  75. *
  76. * @version $Revision: 1.54.2.6 $ $Name: $
  77. * @since Ant 1.1
  78. *
  79. * @ant.task category="filesystem"
  80. */
  81. public class FixCRLF extends MatchingTask {
  82. private static final int UNDEF = -1;
  83. private static final int NOTJAVA = 0;
  84. private static final int LOOKING = 1;
  85. private static final int IN_CHAR_CONST = 2;
  86. private static final int IN_STR_CONST = 3;
  87. private static final int IN_SINGLE_COMMENT = 4;
  88. private static final int IN_MULTI_COMMENT = 5;
  89. private static final int ASIS = 0;
  90. private static final int CR = 1;
  91. private static final int LF = 2;
  92. private static final int CRLF = 3;
  93. private static final int ADD = 1;
  94. private static final int REMOVE = -1;
  95. private static final int SPACES = -1;
  96. private static final int TABS = 1;
  97. private static final int INBUFLEN = 8192;
  98. private static final int LINEBUFLEN = 200;
  99. private static final char CTRLZ = '\u001A';
  100. private int tablength = 8;
  101. private String spaces = " ";
  102. private StringBuffer linebuf = new StringBuffer(1024);
  103. private StringBuffer linebuf2 = new StringBuffer(1024);
  104. private int eol;
  105. private String eolstr;
  106. private int ctrlz;
  107. private int tabs;
  108. private boolean javafiles = false;
  109. private boolean fixlast = true;
  110. private File srcDir;
  111. private File destDir = null;
  112. private FileUtils fileUtils = FileUtils.newFileUtils();
  113. /**
  114. * Encoding to assume for the files
  115. */
  116. private String encoding = null;
  117. /**
  118. * Defaults the properties based on the system type.
  119. * <ul><li>Unix: eol="LF" tab="asis" eof="remove"
  120. * <li>Mac: eol="CR" tab="asis" eof="remove"
  121. * <li>DOS: eol="CRLF" tab="asis" eof="asis"</ul>
  122. */
  123. public FixCRLF () {
  124. tabs = ASIS;
  125. if (Os.isFamily("mac")) {
  126. ctrlz = REMOVE;
  127. eol = CR;
  128. eolstr = "\r";
  129. } else if (Os.isFamily("dos")) {
  130. ctrlz = ASIS;
  131. eol = CRLF;
  132. eolstr = "\r\n";
  133. } else {
  134. ctrlz = REMOVE;
  135. eol = LF;
  136. eolstr = "\n";
  137. }
  138. }
  139. /**
  140. * Set the source dir to find the source text files.
  141. */
  142. public void setSrcdir(File srcDir) {
  143. this.srcDir = srcDir;
  144. }
  145. /**
  146. * Set the destination where the fixed files should be placed.
  147. * Default is to replace the original file.
  148. */
  149. public void setDestdir(File destDir) {
  150. this.destDir = destDir;
  151. }
  152. /**
  153. * Set to true if modifying Java source files.
  154. */
  155. public void setJavafiles(boolean javafiles) {
  156. this.javafiles = javafiles;
  157. }
  158. /**
  159. * Specify how EndOfLine characters are to be handled.
  160. *
  161. * @param attr valid values:
  162. * <ul>
  163. * <li>asis: leave line endings alone
  164. * <li>cr: convert line endings to CR
  165. * <li>lf: convert line endings to LF
  166. * <li>crlf: convert line endings to CRLF
  167. * </ul>
  168. */
  169. public void setEol(CrLf attr) {
  170. String option = attr.getValue();
  171. if (option.equals("asis")) {
  172. eol = ASIS;
  173. } else if (option.equals("cr") || option.equals("mac")) {
  174. eol = CR;
  175. eolstr = "\r";
  176. } else if (option.equals("lf") || option.equals("unix")) {
  177. eol = LF;
  178. eolstr = "\n";
  179. } else {
  180. // Must be "crlf"
  181. eol = CRLF;
  182. eolstr = "\r\n";
  183. }
  184. }
  185. /**
  186. * Specify how carriage return (CR) characters are to be handled.
  187. *
  188. * @param attr valid values:
  189. * <ul>
  190. * <li>add: ensure that there is a CR before every LF
  191. * <li>asis: leave CR characters alone
  192. * <li>remove: remove all CR characters
  193. * </ul>
  194. *
  195. * @deprecated use {@link #setEol setEol} instead.
  196. */
  197. public void setCr(AddAsisRemove attr) {
  198. log("DEPRECATED: The cr attribute has been deprecated,",
  199. Project.MSG_WARN);
  200. log("Please use the eol attribute instead", Project.MSG_WARN);
  201. String option = attr.getValue();
  202. CrLf c = new CrLf();
  203. if (option.equals("remove")) {
  204. c.setValue("lf");
  205. } else if (option.equals("asis")) {
  206. c.setValue("asis");
  207. } else {
  208. // must be "add"
  209. c.setValue("crlf");
  210. }
  211. setEol(c);
  212. }
  213. /**
  214. * Specify how tab characters are to be handled.
  215. *
  216. * @param attr valid values:
  217. * <ul>
  218. * <li>add: convert sequences of spaces which span a tab stop to tabs
  219. * <li>asis: leave tab and space characters alone
  220. * <li>remove: convert tabs to spaces
  221. * </ul>
  222. */
  223. public void setTab(AddAsisRemove attr) {
  224. String option = attr.getValue();
  225. if (option.equals("remove")) {
  226. tabs = SPACES;
  227. } else if (option.equals("asis")) {
  228. tabs = ASIS;
  229. } else {
  230. // must be "add"
  231. tabs = TABS;
  232. }
  233. }
  234. /**
  235. * Specify tab length in characters.
  236. *
  237. * @param tlength specify the length of tab in spaces,
  238. */
  239. public void setTablength(int tlength) throws BuildException {
  240. if (tlength < 2 || tlength > 80) {
  241. throw new BuildException("tablength must be between 2 and 80",
  242. getLocation());
  243. }
  244. tablength = tlength;
  245. StringBuffer sp = new StringBuffer();
  246. for (int i = 0; i < tablength; i++) {
  247. sp.append(' ');
  248. }
  249. spaces = sp.toString();
  250. }
  251. /**
  252. * Specify how DOS EOF (control-z) characters are to be handled.
  253. *
  254. * @param attr valid values:
  255. * <ul>
  256. * <li>add: ensure that there is an eof at the end of the file
  257. * <li>asis: leave eof characters alone
  258. * <li>remove: remove any eof character found at the end
  259. * </ul>
  260. */
  261. public void setEof(AddAsisRemove attr) {
  262. String option = attr.getValue();
  263. if (option.equals("remove")) {
  264. ctrlz = REMOVE;
  265. } else if (option.equals("asis")) {
  266. ctrlz = ASIS;
  267. } else {
  268. // must be "add"
  269. ctrlz = ADD;
  270. }
  271. }
  272. /**
  273. * Specifies the encoding Ant expects the files to be in -
  274. * defaults to the platforms default encoding.
  275. */
  276. public void setEncoding(String encoding) {
  277. this.encoding = encoding;
  278. }
  279. /**
  280. * Specify whether a missing EOL will be added
  281. * to the final line of a file.
  282. */
  283. public void setFixlast(boolean fixlast) {
  284. this.fixlast = fixlast;
  285. }
  286. /**
  287. * Executes the task.
  288. */
  289. public void execute() throws BuildException {
  290. // first off, make sure that we've got a srcdir and destdir
  291. if (srcDir == null) {
  292. throw new BuildException("srcdir attribute must be set!");
  293. }
  294. if (!srcDir.exists()) {
  295. throw new BuildException("srcdir does not exist!");
  296. }
  297. if (!srcDir.isDirectory()) {
  298. throw new BuildException("srcdir is not a directory!");
  299. }
  300. if (destDir != null) {
  301. if (!destDir.exists()) {
  302. throw new BuildException("destdir does not exist!");
  303. }
  304. if (!destDir.isDirectory()) {
  305. throw new BuildException("destdir is not a directory!");
  306. }
  307. }
  308. // log options used
  309. log("options:"
  310. + " eol="
  311. + (eol == ASIS ? "asis" : eol == CR ? "cr" : eol == LF ? "lf" : "crlf")
  312. + " tab=" + (tabs == TABS ? "add" : tabs == ASIS ? "asis" : "remove")
  313. + " eof=" + (ctrlz == ADD ? "add" : ctrlz == ASIS ? "asis" : "remove")
  314. + " tablength=" + tablength
  315. + " encoding=" + (encoding == null ? "default" : encoding),
  316. Project.MSG_VERBOSE);
  317. DirectoryScanner ds = super.getDirectoryScanner(srcDir);
  318. String[] files = ds.getIncludedFiles();
  319. for (int i = 0; i < files.length; i++) {
  320. processFile(files[i]);
  321. }
  322. }
  323. /**
  324. * Creates a Reader reading from a given file an taking the user
  325. * defined encoding into account.
  326. */
  327. private Reader getReader(File f) throws IOException {
  328. return (encoding == null) ? new FileReader(f)
  329. : new InputStreamReader(new FileInputStream(f), encoding);
  330. }
  331. private void processFile(String file) throws BuildException {
  332. File srcFile = new File(srcDir, file);
  333. File destD = destDir == null ? srcDir : destDir;
  334. File tmpFile = null;
  335. BufferedWriter outWriter;
  336. OneLiner.BufferLine line;
  337. // read the contents of the file
  338. OneLiner lines = new OneLiner(srcFile);
  339. try {
  340. // Set up the output Writer
  341. try {
  342. tmpFile = fileUtils.createTempFile("fixcrlf", "", null);
  343. tmpFile.deleteOnExit();
  344. Writer writer = (encoding == null) ? new FileWriter(tmpFile)
  345. : new OutputStreamWriter(new FileOutputStream(tmpFile),
  346. encoding);
  347. outWriter = new BufferedWriter(writer);
  348. } catch (IOException e) {
  349. throw new BuildException(e);
  350. }
  351. while (lines.hasMoreElements()) {
  352. // In-line states
  353. int endComment;
  354. try {
  355. line = (OneLiner.BufferLine) lines.nextElement();
  356. } catch (NoSuchElementException e) {
  357. throw new BuildException(e);
  358. }
  359. String lineString = line.getLineString();
  360. int linelen = line.length();
  361. // Note - all of the following processing NOT done for
  362. // tabs ASIS
  363. if (tabs == ASIS) {
  364. // Just copy the body of the line across
  365. try {
  366. outWriter.write(lineString);
  367. } catch (IOException e) {
  368. throw new BuildException(e);
  369. } // end of try-catch
  370. } else { // (tabs != ASIS)
  371. while (line.getNext() < linelen) {
  372. switch (lines.getState()) {
  373. case NOTJAVA:
  374. notInConstant(line, line.length(), outWriter);
  375. break;
  376. case IN_MULTI_COMMENT:
  377. endComment
  378. = lineString.indexOf("*/", line.getNext());
  379. if (endComment >= 0) {
  380. // End of multiLineComment on this line
  381. endComment += 2; // Include the end token
  382. lines.setState(LOOKING);
  383. } else {
  384. endComment = linelen;
  385. }
  386. notInConstant(line, endComment, outWriter);
  387. break;
  388. case IN_SINGLE_COMMENT:
  389. notInConstant(line, line.length(), outWriter);
  390. lines.setState(LOOKING);
  391. break;
  392. case IN_CHAR_CONST:
  393. case IN_STR_CONST:
  394. // Got here from LOOKING by finding an
  395. // opening "\'" next points to that quote
  396. // character.
  397. // Find the end of the constant. Watch
  398. // out for backslashes. Literal tabs are
  399. // left unchanged, and the column is
  400. // adjusted accordingly.
  401. int begin = line.getNext();
  402. char terminator = (lines.getState() == IN_STR_CONST
  403. ? '\"'
  404. : '\'');
  405. endOfCharConst(line, terminator);
  406. while (line.getNext() < line.getLookahead()) {
  407. if (line.getNextCharInc() == '\t') {
  408. line.setColumn(line.getColumn()
  409. + tablength
  410. - (line.getColumn() % tablength));
  411. } else {
  412. line.incColumn();
  413. }
  414. }
  415. // Now output the substring
  416. try {
  417. outWriter.write(line.substring(begin,
  418. line.getNext()));
  419. } catch (IOException e) {
  420. throw new BuildException(e);
  421. }
  422. lines.setState(LOOKING);
  423. break;
  424. case LOOKING:
  425. nextStateChange(line);
  426. notInConstant(line, line.getLookahead(), outWriter);
  427. break;
  428. } // end of switch (state)
  429. } // end of while (line.getNext() < linelen)
  430. } // end of else (tabs != ASIS)
  431. if (!("".equals(line.getEol())) || fixlast) {
  432. try {
  433. outWriter.write(eolstr);
  434. } catch (IOException e) {
  435. throw new BuildException(e);
  436. } // end of try-catch
  437. } //end if non-blank original eol or fixlast
  438. } // end of while (lines.hasNext())
  439. try {
  440. // Handle CTRLZ
  441. if (ctrlz == ASIS) {
  442. outWriter.write(lines.getEofStr());
  443. } else if (ctrlz == ADD) {
  444. outWriter.write(CTRLZ);
  445. }
  446. } catch (IOException e) {
  447. throw new BuildException(e);
  448. } finally {
  449. try {
  450. outWriter.close();
  451. } catch (IOException e) {
  452. throw new BuildException(e);
  453. }
  454. }
  455. try {
  456. lines.close();
  457. lines = null;
  458. } catch (IOException e) {
  459. throw new BuildException("Unable to close source file "
  460. + srcFile);
  461. }
  462. File destFile = new File(destD, file);
  463. boolean destIsWrong = true;
  464. if (destFile.exists()) {
  465. // Compare the destination with the temp file
  466. log("destFile exists", Project.MSG_DEBUG);
  467. if (!fileUtils.contentEquals(destFile, tmpFile)) {
  468. log(destFile + " is being written", Project.MSG_DEBUG);
  469. } else {
  470. log(destFile + " is not written, as the contents "
  471. + "are identical", Project.MSG_DEBUG);
  472. destIsWrong = false;
  473. }
  474. }
  475. if (destIsWrong) {
  476. fileUtils.rename(tmpFile, destFile);
  477. tmpFile = null;
  478. }
  479. } catch (IOException e) {
  480. throw new BuildException(e);
  481. } finally {
  482. try {
  483. if (lines != null) {
  484. lines.close();
  485. }
  486. } catch (IOException io) {
  487. log("Error closing " + srcFile, Project.MSG_ERR);
  488. } // end of catch
  489. if (tmpFile != null) {
  490. tmpFile.delete();
  491. }
  492. } // end of finally
  493. }
  494. /**
  495. * Scan a BufferLine for the next state changing token: the beginning
  496. * of a single or multi-line comment, a character or a string constant.
  497. *
  498. * As a side-effect, sets the buffer state to the next state, and sets
  499. * field lookahead to the first character of the state-changing token, or
  500. * to the next eol character.
  501. *
  502. * @param bufline BufferLine containing the string
  503. * to be processed
  504. * @exception org.apache.tools.ant.BuildException
  505. * Thrown when end of line is reached
  506. * before the terminator is found.
  507. */
  508. private void nextStateChange(OneLiner.BufferLine bufline)
  509. throws BuildException {
  510. int eol = bufline.length();
  511. int ptr = bufline.getNext();
  512. // Look for next single or double quote, double slash or slash star
  513. while (ptr < eol) {
  514. switch (bufline.getChar(ptr++)) {
  515. case '\'':
  516. bufline.setState(IN_CHAR_CONST);
  517. bufline.setLookahead(--ptr);
  518. return;
  519. case '\"':
  520. bufline.setState(IN_STR_CONST);
  521. bufline.setLookahead(--ptr);
  522. return;
  523. case '/':
  524. if (ptr < eol) {
  525. if (bufline.getChar(ptr) == '*') {
  526. bufline.setState(IN_MULTI_COMMENT);
  527. bufline.setLookahead(--ptr);
  528. return;
  529. } else if (bufline.getChar(ptr) == '/') {
  530. bufline.setState(IN_SINGLE_COMMENT);
  531. bufline.setLookahead(--ptr);
  532. return;
  533. }
  534. }
  535. break;
  536. } // end of switch (bufline.getChar(ptr++))
  537. } // end of while (ptr < eol)
  538. // Eol is the next token
  539. bufline.setLookahead(ptr);
  540. }
  541. /**
  542. * Scan a BufferLine forward from the 'next' pointer
  543. * for the end of a character constant. Set 'lookahead' pointer to the
  544. * character following the terminating quote.
  545. *
  546. * @param bufline BufferLine containing the string
  547. * to be processed
  548. * @param terminator The constant terminator
  549. *
  550. * @exception org.apache.tools.ant.BuildException
  551. * Thrown when end of line is reached
  552. * before the terminator is found.
  553. */
  554. private void endOfCharConst(OneLiner.BufferLine bufline, char terminator)
  555. throws BuildException {
  556. int ptr = bufline.getNext();
  557. int eol = bufline.length();
  558. char c;
  559. ptr++; // skip past initial quote
  560. while (ptr < eol) {
  561. if ((c = bufline.getChar(ptr++)) == '\\') {
  562. ptr++;
  563. } else {
  564. if (c == terminator) {
  565. bufline.setLookahead(ptr);
  566. return;
  567. }
  568. }
  569. } // end of while (ptr < eol)
  570. // Must have fallen through to the end of the line
  571. throw new BuildException("endOfCharConst: unterminated char constant");
  572. }
  573. /**
  574. * Process a BufferLine string which is not part of a string constant.
  575. * The start position of the string is given by the 'next' field.
  576. * Sets the 'next' and 'column' fields in the BufferLine.
  577. *
  578. * @param bufline BufferLine containing the string
  579. * to be processed
  580. * @param end Index just past the end of the
  581. * string
  582. * @param outWriter Sink for the processed string
  583. */
  584. private void notInConstant(OneLiner.BufferLine bufline, int end,
  585. BufferedWriter outWriter) {
  586. // N.B. both column and string index are zero-based
  587. // Process a string not part of a constant;
  588. // i.e. convert tabs<->spaces as required
  589. // This is NOT called for ASIS tab handling
  590. int nextTab;
  591. int nextStop;
  592. int tabspaces;
  593. String line = bufline.substring(bufline.getNext(), end);
  594. int place = 0; // Zero-based
  595. int col = bufline.getColumn(); // Zero-based
  596. // process sequences of white space
  597. // first convert all tabs to spaces
  598. linebuf = new StringBuffer();
  599. while ((nextTab = line.indexOf((int) '\t', place)) >= 0) {
  600. linebuf.append(line.substring(place, nextTab)); // copy to the TAB
  601. col += nextTab - place;
  602. tabspaces = tablength - (col % tablength);
  603. linebuf.append(spaces.substring(0, tabspaces));
  604. col += tabspaces;
  605. place = nextTab + 1;
  606. } // end of while
  607. linebuf.append(line.substring(place, line.length()));
  608. // if converting to spaces, all finished
  609. String linestring = new String(linebuf.substring(0));
  610. if (tabs == REMOVE) {
  611. try {
  612. outWriter.write(linestring);
  613. } catch (IOException e) {
  614. throw new BuildException(e);
  615. } // end of try-catch
  616. } else { // tabs == ADD
  617. int tabCol;
  618. linebuf2 = new StringBuffer();
  619. place = 0;
  620. col = bufline.getColumn();
  621. int placediff = col - 0;
  622. // for the length of the string, cycle through the tab stop
  623. // positions, checking for a space preceded by at least one
  624. // other space at the tab stop. if so replace the longest possible
  625. // preceding sequence of spaces with a tab.
  626. nextStop = col + (tablength - col % tablength);
  627. if (nextStop - col < 2) {
  628. linebuf2.append(linestring.substring(
  629. place, nextStop - placediff));
  630. place = nextStop - placediff;
  631. nextStop += tablength;
  632. }
  633. for (; nextStop - placediff <= linestring.length();
  634. nextStop += tablength) {
  635. for (tabCol = nextStop;
  636. --tabCol - placediff >= place
  637. && linestring.charAt(tabCol - placediff) == ' ';) {
  638. ; // Loop for the side-effects
  639. }
  640. // tabCol is column index of the last non-space character
  641. // before the next tab stop
  642. if (nextStop - tabCol > 2) {
  643. linebuf2.append(linestring.substring(
  644. place, ++tabCol - placediff));
  645. linebuf2.append('\t');
  646. } else {
  647. linebuf2.append(linestring.substring(
  648. place, nextStop - placediff));
  649. } // end of else
  650. place = nextStop - placediff;
  651. } // end of for (nextStop ... )
  652. // pick up that last bit, if any
  653. linebuf2.append(linestring.substring(place, linestring.length()));
  654. try {
  655. outWriter.write(linebuf2.substring(0));
  656. } catch (IOException e) {
  657. throw new BuildException(e);
  658. } // end of try-catch
  659. } // end of else tabs == ADD
  660. // Set column position as modified by this method
  661. bufline.setColumn(bufline.getColumn() + linestring.length());
  662. bufline.setNext(end);
  663. }
  664. class OneLiner implements Enumeration {
  665. private int state = javafiles ? LOOKING : NOTJAVA;
  666. private StringBuffer eolStr = new StringBuffer(LINEBUFLEN);
  667. private StringBuffer eofStr = new StringBuffer();
  668. private BufferedReader reader;
  669. private StringBuffer line = new StringBuffer();
  670. private boolean reachedEof = false;
  671. private File srcFile;
  672. public OneLiner(File srcFile)
  673. throws BuildException {
  674. this.srcFile = srcFile;
  675. try {
  676. reader = new BufferedReader
  677. (getReader(srcFile), INBUFLEN);
  678. nextLine();
  679. } catch (IOException e) {
  680. throw new BuildException(srcFile + ": " + e.getMessage(),
  681. e, getLocation());
  682. }
  683. }
  684. protected void nextLine()
  685. throws BuildException {
  686. int ch = -1;
  687. int eolcount = 0;
  688. eolStr = new StringBuffer();
  689. line = new StringBuffer();
  690. try {
  691. ch = reader.read();
  692. while (ch != -1 && ch != '\r' && ch != '\n') {
  693. line.append((char) ch);
  694. ch = reader.read();
  695. }
  696. if (ch == -1 && line.length() == 0) {
  697. // Eof has been reached
  698. reachedEof = true;
  699. return;
  700. }
  701. switch ((char) ch) {
  702. case '\r':
  703. // Check for \r, \r\n and \r\r\n
  704. // Regard \r\r not followed by \n as two lines
  705. ++eolcount;
  706. eolStr.append('\r');
  707. reader.mark(2);
  708. switch ((ch = reader.read())) {
  709. case '\r':
  710. if ((char) (ch = reader.read()) == '\n') {
  711. eolcount += 2;
  712. eolStr.append("\r\n");
  713. } else {
  714. reader.reset();
  715. }
  716. break;
  717. case '\n':
  718. ++eolcount;
  719. eolStr.append('\n');
  720. break;
  721. case -1:
  722. // don't reposition when we've reached the end
  723. // of the stream
  724. break;
  725. default:
  726. reader.reset();
  727. break;
  728. } // end of switch ((char)(ch = reader.read()))
  729. break;
  730. case '\n':
  731. ++eolcount;
  732. eolStr.append('\n');
  733. break;
  734. } // end of switch ((char) ch)
  735. // if at eolcount == 0 and trailing characters of string
  736. // are CTRL-Zs, set eofStr
  737. if (eolcount == 0) {
  738. int i = line.length();
  739. while (--i >= 0 && line.charAt(i) == CTRLZ) {
  740. // keep searching for the first ^Z
  741. }
  742. if (i < line.length() - 1) {
  743. // Trailing characters are ^Zs
  744. // Construct new line and eofStr
  745. eofStr.append(line.toString().substring(i + 1));
  746. if (i < 0) {
  747. line.setLength(0);
  748. reachedEof = true;
  749. } else {
  750. line.setLength(i + 1);
  751. }
  752. }
  753. } // end of if (eolcount == 0)
  754. } catch (IOException e) {
  755. throw new BuildException(srcFile + ": " + e.getMessage(),
  756. e, getLocation());
  757. }
  758. }
  759. public String getEofStr() {
  760. return eofStr.substring(0);
  761. }
  762. public int getState() {
  763. return state;
  764. }
  765. public void setState(int state) {
  766. this.state = state;
  767. }
  768. public boolean hasMoreElements() {
  769. return !reachedEof;
  770. }
  771. public Object nextElement()
  772. throws NoSuchElementException {
  773. if (!hasMoreElements()) {
  774. throw new NoSuchElementException("OneLiner");
  775. }
  776. BufferLine tmpLine =
  777. new BufferLine(line.toString(), eolStr.substring(0));
  778. nextLine();
  779. return tmpLine;
  780. }
  781. public void close() throws IOException {
  782. if (reader != null) {
  783. reader.close();
  784. }
  785. }
  786. class BufferLine {
  787. private int next = 0;
  788. private int column = 0;
  789. private int lookahead = UNDEF;
  790. private String line;
  791. private String eolStr;
  792. public BufferLine(String line, String eolStr)
  793. throws BuildException {
  794. next = 0;
  795. column = 0;
  796. this.line = line;
  797. this.eolStr = eolStr;
  798. }
  799. public int getNext() {
  800. return next;
  801. }
  802. public void setNext(int next) {
  803. this.next = next;
  804. }
  805. public int getLookahead() {
  806. return lookahead;
  807. }
  808. public void setLookahead(int lookahead) {
  809. this.lookahead = lookahead;
  810. }
  811. public char getChar(int i) {
  812. return line.charAt(i);
  813. }
  814. public char getNextChar() {
  815. return getChar(next);
  816. }
  817. public char getNextCharInc() {
  818. return getChar(next++);
  819. }
  820. public int getColumn() {
  821. return column;
  822. }
  823. public void setColumn(int col) {
  824. column = col;
  825. }
  826. public int incColumn() {
  827. return column++;
  828. }
  829. public int length() {
  830. return line.length();
  831. }
  832. public int getEolLength() {
  833. return eolStr.length();
  834. }
  835. public String getLineString() {
  836. return line;
  837. }
  838. public String getEol() {
  839. return eolStr;
  840. }
  841. public String substring(int begin) {
  842. return line.substring(begin);
  843. }
  844. public String substring(int begin, int end) {
  845. return line.substring(begin, end);
  846. }
  847. public void setState(int state) {
  848. OneLiner.this.setState(state);
  849. }
  850. public int getState() {
  851. return OneLiner.this.getState();
  852. }
  853. }
  854. }
  855. /**
  856. * Enumerated attribute with the values "asis", "add" and "remove".
  857. */
  858. public static class AddAsisRemove extends EnumeratedAttribute {
  859. public String[] getValues() {
  860. return new String[] {"add", "asis", "remove"};
  861. }
  862. }
  863. /**
  864. * Enumerated attribute with the values "asis", "cr", "lf" and "crlf".
  865. */
  866. public static class CrLf extends EnumeratedAttribute {
  867. /**
  868. * @see EnumeratedAttribute#getValues
  869. */
  870. public String[] getValues() {
  871. return new String[] {"asis", "cr", "lf", "crlf",
  872. "mac", "unix", "dos"};
  873. }
  874. }
  875. }