001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.io.Reader;
023: import java.io.FileReader;
024: import java.io.IOException;
025: import java.io.BufferedReader;
026: import java.io.FileInputStream;
027: import java.io.InputStreamReader;
028: import java.util.Vector;
029: import java.util.Enumeration;
030: import java.util.NoSuchElementException;
031: import org.apache.tools.ant.Project;
032: import org.apache.tools.ant.BuildException;
033: import org.apache.tools.ant.DirectoryScanner;
034: import org.apache.tools.ant.filters.FixCrLfFilter;
035: import org.apache.tools.ant.filters.ChainableReader;
036: import org.apache.tools.ant.types.FilterChain;
037: import org.apache.tools.ant.types.EnumeratedAttribute;
038: import org.apache.tools.ant.util.FileUtils;
039:
040: /**
041: * Converts text source files to local OS formatting conventions, as
042: * well as repair text files damaged by misconfigured or misguided editors or
043: * file transfer programs.
044: * <p>
045: * This task can take the following arguments:
046: * <ul>
047: * <li>srcdir
048: * <li>destdir
049: * <li>include
050: * <li>exclude
051: * <li>cr
052: * <li>eol
053: * <li>tab
054: * <li>eof
055: * <li>encoding
056: * <li>targetencoding
057: * </ul>
058: * Of these arguments, only <b>sourcedir</b> is required.
059: * <p>
060: * When this task executes, it will scan the srcdir based on the include
061: * and exclude properties.
062: * <p>
063: * This version generalises the handling of EOL characters, and allows
064: * for CR-only line endings (the standard on Mac systems prior to OS X).
065: * Tab handling has also been generalised to accommodate any tabwidth
066: * from 2 to 80, inclusive. Importantly, it will leave untouched any
067: * literal TAB characters embedded within string or character constants.
068: * <p>
069: * <em>Warning:</em> do not run on binary files.
070: * <em>Caution:</em> run with care on carefully formatted files.
071: * This may sound obvious, but if you don't specify asis, presume that
072: * your files are going to be modified. If "tabs" is "add" or "remove",
073: * whitespace characters may be added or removed as necessary. Similarly,
074: * for CR's - in fact "eol"="crlf" or cr="add" can result in cr
075: * characters being removed in one special case accommodated, i.e.,
076: * CRCRLF is regarded as a single EOL to handle cases where other
077: * programs have converted CRLF into CRCRLF.
078: *
079: * @since Ant 1.1
080: *
081: * @ant.task category="filesystem"
082: */
083:
084: public class FixCRLF extends MatchingTask implements ChainableReader {
085:
086: /** error string for using srcdir and file */
087: public static final String ERROR_FILE_AND_SRCDIR = "srcdir and file are mutually exclusive";
088:
089: private static final FileUtils FILE_UTILS = FileUtils
090: .getFileUtils();
091:
092: private boolean preserveLastModified = false;
093: private File srcDir;
094: private File destDir = null;
095: private File file;
096: private FixCrLfFilter filter = new FixCrLfFilter();
097: private Vector fcv = null;
098:
099: /**
100: * Encoding to assume for the files
101: */
102: private String encoding = null;
103:
104: /**
105: * Encoding to use for output files
106: */
107: private String outputEncoding = null;
108:
109: /**
110: * Chain this task as a reader.
111: * @param rdr Reader to chain.
112: * @return a Reader.
113: * @since Ant 1.7?
114: */
115: public final Reader chain(final Reader rdr) {
116: return filter.chain(rdr);
117: }
118:
119: /**
120: * Set the source dir to find the source text files.
121: * @param srcDir the source directory.
122: */
123: public void setSrcdir(File srcDir) {
124: this .srcDir = srcDir;
125: }
126:
127: /**
128: * Set the destination where the fixed files should be placed.
129: * Default is to replace the original file.
130: * @param destDir the destination directory.
131: */
132: public void setDestdir(File destDir) {
133: this .destDir = destDir;
134: }
135:
136: /**
137: * Set to true if modifying Java source files.
138: * @param javafiles whether modifying Java files.
139: */
140: public void setJavafiles(boolean javafiles) {
141: filter.setJavafiles(javafiles);
142: }
143:
144: /**
145: * Set a single file to convert.
146: * @since Ant 1.6.3
147: * @param file the file to convert.
148: */
149: public void setFile(File file) {
150: this .file = file;
151: }
152:
153: /**
154: * Specify how EndOfLine characters are to be handled.
155: *
156: * @param attr valid values:
157: * <ul>
158: * <li>asis: leave line endings alone
159: * <li>cr: convert line endings to CR
160: * <li>lf: convert line endings to LF
161: * <li>crlf: convert line endings to CRLF
162: * </ul>
163: */
164: public void setEol(CrLf attr) {
165: filter.setEol(FixCrLfFilter.CrLf.newInstance(attr.getValue()));
166: }
167:
168: /**
169: * Specify how carriage return (CR) characters are to be handled.
170: *
171: * @param attr valid values:
172: * <ul>
173: * <li>add: ensure that there is a CR before every LF
174: * <li>asis: leave CR characters alone
175: * <li>remove: remove all CR characters
176: * </ul>
177: *
178: * @deprecated since 1.4.x.
179: * Use {@link #setEol setEol} instead.
180: */
181: public void setCr(AddAsisRemove attr) {
182: log("DEPRECATED: The cr attribute has been deprecated,",
183: Project.MSG_WARN);
184: log("Please use the eol attribute instead", Project.MSG_WARN);
185: String option = attr.getValue();
186: CrLf c = new CrLf();
187: if (option.equals("remove")) {
188: c.setValue("lf");
189: } else if (option.equals("asis")) {
190: c.setValue("asis");
191: } else {
192: // must be "add"
193: c.setValue("crlf");
194: }
195: setEol(c);
196: }
197:
198: /**
199: * Specify how tab characters are to be handled.
200: *
201: * @param attr valid values:
202: * <ul>
203: * <li>add: convert sequences of spaces which span a tab stop to tabs
204: * <li>asis: leave tab and space characters alone
205: * <li>remove: convert tabs to spaces
206: * </ul>
207: */
208: public void setTab(AddAsisRemove attr) {
209: filter.setTab(FixCrLfFilter.AddAsisRemove.newInstance(attr
210: .getValue()));
211: }
212:
213: /**
214: * Specify tab length in characters.
215: *
216: * @param tlength specify the length of tab in spaces.
217: * @throws BuildException on error.
218: */
219: public void setTablength(int tlength) throws BuildException {
220: try {
221: filter.setTablength(tlength);
222: } catch (IOException e) {
223: throw new BuildException(e);
224: }
225: }
226:
227: /**
228: * Specify how DOS EOF (control-z) characters are to be handled.
229: *
230: * @param attr valid values:
231: * <ul>
232: * <li>add: ensure that there is an eof at the end of the file
233: * <li>asis: leave eof characters alone
234: * <li>remove: remove any eof character found at the end
235: * </ul>
236: */
237: public void setEof(AddAsisRemove attr) {
238: filter.setEof(FixCrLfFilter.AddAsisRemove.newInstance(attr
239: .getValue()));
240: }
241:
242: /**
243: * Specifies the encoding Ant expects the files to be
244: * in--defaults to the platforms default encoding.
245: * @param encoding String encoding name.
246: */
247: public void setEncoding(String encoding) {
248: this .encoding = encoding;
249: }
250:
251: /**
252: * Specifies the encoding that the files are
253: * to be written in--same as input encoding by default.
254: * @param outputEncoding String outputEncoding name.
255: */
256: public void setOutputEncoding(String outputEncoding) {
257: this .outputEncoding = outputEncoding;
258: }
259:
260: /**
261: * Specify whether a missing EOL will be added
262: * to the final line of a file.
263: * @param fixlast whether to fix the last line.
264: */
265: public void setFixlast(boolean fixlast) {
266: filter.setFixlast(fixlast);
267: }
268:
269: /**
270: * Set whether to preserve the last modified time as the original files.
271: * @param preserve true if timestamps should be preserved.
272: * @since Ant 1.6.3
273: */
274: public void setPreserveLastModified(boolean preserve) {
275: preserveLastModified = preserve;
276: }
277:
278: /**
279: * Executes the task.
280: * @throws BuildException on error.
281: */
282: public void execute() throws BuildException {
283: // first off, make sure that we've got a srcdir and destdir
284: validate();
285:
286: // log options used
287: String enc = encoding == null ? "default" : encoding;
288: log("options:" + " eol=" + filter.getEol().getValue() + " tab="
289: + filter.getTab().getValue() + " eof="
290: + filter.getEof().getValue() + " tablength="
291: + filter.getTablength() + " encoding=" + enc
292: + " outputencoding="
293: + (outputEncoding == null ? enc : outputEncoding),
294: Project.MSG_VERBOSE);
295:
296: DirectoryScanner ds = super .getDirectoryScanner(srcDir);
297: String[] files = ds.getIncludedFiles();
298:
299: for (int i = 0; i < files.length; i++) {
300: processFile(files[i]);
301: }
302: }
303:
304: private void validate() throws BuildException {
305: if (file != null) {
306: if (srcDir != null) {
307: throw new BuildException(ERROR_FILE_AND_SRCDIR);
308: }
309: //patch file into the fileset
310: fileset.setFile(file);
311: //set our parent dir
312: srcDir = file.getParentFile();
313: }
314: if (srcDir == null) {
315: throw new BuildException("srcdir attribute must be set!");
316: }
317: if (!srcDir.exists()) {
318: throw new BuildException("srcdir does not exist!");
319: }
320: if (!srcDir.isDirectory()) {
321: throw new BuildException("srcdir is not a directory!");
322: }
323: if (destDir != null) {
324: if (!destDir.exists()) {
325: throw new BuildException("destdir does not exist!");
326: }
327: if (!destDir.isDirectory()) {
328: throw new BuildException("destdir is not a directory!");
329: }
330: }
331: }
332:
333: private void processFile(String file) throws BuildException {
334: File srcFile = new File(srcDir, file);
335: long lastModified = srcFile.lastModified();
336: File destD = destDir == null ? srcDir : destDir;
337:
338: if (fcv == null) {
339: FilterChain fc = new FilterChain();
340: fc.add(filter);
341: fcv = new Vector(1);
342: fcv.add(fc);
343: }
344: File tmpFile = FILE_UTILS.createTempFile("fixcrlf", "", null);
345: tmpFile.deleteOnExit();
346: try {
347: FILE_UTILS.copyFile(srcFile, tmpFile, null, fcv, false,
348: false, encoding, outputEncoding == null ? encoding
349: : outputEncoding, getProject());
350:
351: File destFile = new File(destD, file);
352:
353: boolean destIsWrong = true;
354: if (destFile.exists()) {
355: // Compare the destination with the temp file
356: log("destFile exists", Project.MSG_DEBUG);
357: destIsWrong = !FILE_UTILS.contentEquals(destFile,
358: tmpFile);
359: log(
360: destFile
361: + (destIsWrong ? " is being written"
362: : " is not written, as the contents are identical"),
363: Project.MSG_DEBUG);
364: }
365: if (destIsWrong) {
366: FILE_UTILS.rename(tmpFile, destFile);
367: if (preserveLastModified) {
368: log("preserved lastModified", Project.MSG_DEBUG);
369: FILE_UTILS.setFileLastModified(destFile,
370: lastModified);
371: }
372: tmpFile = null;
373: }
374: } catch (IOException e) {
375: throw new BuildException(e);
376: }
377: }
378:
379: /**
380: * Deprecated, the functionality has been moved to filters.FixCrLfFilter.
381: * @deprecated since 1.7.0.
382: */
383: protected class OneLiner implements Enumeration {
384: private static final int UNDEF = -1;
385: private static final int NOTJAVA = 0;
386: private static final int LOOKING = 1;
387: private static final int INBUFLEN = 8192;
388: private static final int LINEBUFLEN = 200;
389: private static final char CTRLZ = '\u001A';
390:
391: private int state = filter.getJavafiles() ? LOOKING : NOTJAVA;
392:
393: private StringBuffer eolStr = new StringBuffer(LINEBUFLEN);
394: private StringBuffer eofStr = new StringBuffer();
395:
396: private BufferedReader reader;
397: private StringBuffer line = new StringBuffer();
398: private boolean reachedEof = false;
399: private File srcFile;
400:
401: /**
402: * Constructor.
403: * @param srcFile the file to read.
404: * @throws BuildException if there is an error.
405: */
406: public OneLiner(File srcFile) throws BuildException {
407: this .srcFile = srcFile;
408: try {
409: reader = new BufferedReader(
410: ((encoding == null) ? new FileReader(srcFile)
411: : new InputStreamReader(
412: new FileInputStream(srcFile),
413: encoding)), INBUFLEN);
414:
415: nextLine();
416: } catch (IOException e) {
417: throw new BuildException(srcFile + ": "
418: + e.getMessage(), e, getLocation());
419: }
420: }
421:
422: /**
423: * Move to the next line.
424: * @throws BuildException if there is an error.
425: */
426: protected void nextLine() throws BuildException {
427: int ch = -1;
428: int eolcount = 0;
429:
430: eolStr = new StringBuffer();
431: line = new StringBuffer();
432:
433: try {
434: ch = reader.read();
435: while (ch != -1 && ch != '\r' && ch != '\n') {
436: line.append((char) ch);
437: ch = reader.read();
438: }
439:
440: if (ch == -1 && line.length() == 0) {
441: // Eof has been reached
442: reachedEof = true;
443: return;
444: }
445:
446: switch ((char) ch) {
447: case '\r':
448: // Check for \r, \r\n and \r\r\n
449: // Regard \r\r not followed by \n as two lines
450: ++eolcount;
451: eolStr.append('\r');
452: reader.mark(2);
453: ch = reader.read();
454: switch (ch) {
455: case '\r':
456: ch = reader.read();
457: if ((char) (ch) == '\n') {
458: eolcount += 2;
459: eolStr.append("\r\n");
460: } else {
461: reader.reset();
462: }
463: break;
464: case '\n':
465: ++eolcount;
466: eolStr.append('\n');
467: break;
468: case -1:
469: // don't reposition when we've reached the end
470: // of the stream
471: break;
472: default:
473: reader.reset();
474: break;
475: } // end of switch ((char)(ch = reader.read()))
476: break;
477:
478: case '\n':
479: ++eolcount;
480: eolStr.append('\n');
481: break;
482: default:
483: // Fall tru
484: } // end of switch ((char) ch)
485:
486: // if at eolcount == 0 and trailing characters of string
487: // are CTRL-Zs, set eofStr
488: if (eolcount == 0) {
489: int i = line.length();
490: while (--i >= 0 && line.charAt(i) == CTRLZ) {
491: // keep searching for the first ^Z
492: }
493: if (i < line.length() - 1) {
494: // Trailing characters are ^Zs
495: // Construct new line and eofStr
496: eofStr.append(line.toString().substring(i + 1));
497: if (i < 0) {
498: line.setLength(0);
499: reachedEof = true;
500: } else {
501: line.setLength(i + 1);
502: }
503: }
504:
505: } // end of if (eolcount == 0)
506:
507: } catch (IOException e) {
508: throw new BuildException(srcFile + ": "
509: + e.getMessage(), e, getLocation());
510: }
511: }
512:
513: /**
514: * get the eof string.
515: * @return the eof string.
516: */
517: public String getEofStr() {
518: return eofStr.substring(0);
519: }
520:
521: /**
522: * get the state.
523: * @return the state.
524: */
525: public int getState() {
526: return state;
527: }
528:
529: /**
530: * Set the state.
531: * @param state the value to use.
532: */
533: public void setState(int state) {
534: this .state = state;
535: }
536:
537: /**
538: * @return true if there is more elements.
539: */
540: public boolean hasMoreElements() {
541: return !reachedEof;
542: }
543:
544: /**
545: * get the next element.
546: * @return the next element.
547: * @throws NoSuchElementException if there is no more.
548: */
549: public Object nextElement() throws NoSuchElementException {
550: if (!hasMoreElements()) {
551: throw new NoSuchElementException("OneLiner");
552: }
553: BufferLine tmpLine = new BufferLine(line.toString(), eolStr
554: .substring(0));
555: nextLine();
556: return tmpLine;
557: }
558:
559: /**
560: * Close the reader.
561: * @throws IOException if there is an error.
562: */
563: public void close() throws IOException {
564: if (reader != null) {
565: reader.close();
566: }
567: }
568:
569: class BufferLine {
570: private int next = 0;
571: private int column = 0;
572: private int lookahead = UNDEF;
573: private String line;
574: private String eolStr;
575:
576: public BufferLine(String line, String eolStr)
577: throws BuildException {
578: next = 0;
579: column = 0;
580: this .line = line;
581: this .eolStr = eolStr;
582: }
583:
584: public int getNext() {
585: return next;
586: }
587:
588: public void setNext(int next) {
589: this .next = next;
590: }
591:
592: public int getLookahead() {
593: return lookahead;
594: }
595:
596: public void setLookahead(int lookahead) {
597: this .lookahead = lookahead;
598: }
599:
600: public char getChar(int i) {
601: return line.charAt(i);
602: }
603:
604: public char getNextChar() {
605: return getChar(next);
606: }
607:
608: public char getNextCharInc() {
609: return getChar(next++);
610: }
611:
612: public int getColumn() {
613: return column;
614: }
615:
616: public void setColumn(int col) {
617: column = col;
618: }
619:
620: public int incColumn() {
621: return column++;
622: }
623:
624: public int length() {
625: return line.length();
626: }
627:
628: public int getEolLength() {
629: return eolStr.length();
630: }
631:
632: public String getLineString() {
633: return line;
634: }
635:
636: public String getEol() {
637: return eolStr;
638: }
639:
640: public String substring(int begin) {
641: return line.substring(begin);
642: }
643:
644: public String substring(int begin, int end) {
645: return line.substring(begin, end);
646: }
647:
648: public void setState(int state) {
649: OneLiner.this .setState(state);
650: }
651:
652: public int getState() {
653: return OneLiner.this .getState();
654: }
655: }
656: }
657:
658: /**
659: * Enumerated attribute with the values "asis", "add" and "remove".
660: */
661: public static class AddAsisRemove extends EnumeratedAttribute {
662: /** {@inheritDoc}. */
663: public String[] getValues() {
664: return new String[] { "add", "asis", "remove" };
665: }
666: }
667:
668: /**
669: * Enumerated attribute with the values "asis", "cr", "lf" and "crlf".
670: */
671: public static class CrLf extends EnumeratedAttribute {
672: /**
673: * @see EnumeratedAttribute#getValues
674: */
675: /** {@inheritDoc}. */
676: public String[] getValues() {
677: return new String[] { "asis", "cr", "lf", "crlf", "mac",
678: "unix", "dos" };
679: }
680: }
681:
682: }
|