All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.tools.ant.taskdefs.FixCRLF Maven / Gradle / Ivy

There is a newer version: 1.0-rc5
Show newest version
/*
 * Copyright  2000-2005 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.tools.ant.taskdefs;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.condition.Os;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.util.FileUtils;

/**
 * Converts text source files to local OS formatting conventions, as
 * well as repair text files damaged by misconfigured or misguided editors or
 * file transfer programs.
 * 

* This task can take the following arguments: *

    *
  • srcdir *
  • destdir *
  • include *
  • exclude *
  • cr *
  • eol *
  • tab *
  • eof *
  • encoding *
* Of these arguments, only sourcedir is required. *

* When this task executes, it will scan the srcdir based on the include * and exclude properties. *

* This version generalises the handling of EOL characters, and allows * for CR-only line endings (which I suspect is the standard on Macs.) * Tab handling has also been generalised to accommodate any tabwidth * from 2 to 80, inclusive. Importantly, it will leave untouched any * literal TAB characters embedded within string or character constants. *

* Warning: do not run on binary files. * Caution: run with care on carefully formatted files. * This may sound obvious, but if you don't specify asis, presume that * your files are going to be modified. If "tabs" is "add" or "remove", * whitespace characters may be added or removed as necessary. Similarly, * for CR's - in fact "eol"="crlf" or cr="add" can result in cr * characters being removed in one special case accommodated, i.e., * CRCRLF is regarded as a single EOL to handle cases where other * programs have converted CRLF into CRCRLF. * * @since Ant 1.1 * * @ant.task category="filesystem" */ public class FixCRLF extends MatchingTask { private static final int UNDEF = -1; private static final int NOTJAVA = 0; private static final int LOOKING = 1; private static final int IN_CHAR_CONST = 2; private static final int IN_STR_CONST = 3; private static final int IN_SINGLE_COMMENT = 4; private static final int IN_MULTI_COMMENT = 5; private static final int ASIS = 0; private static final int CR = 1; private static final int LF = 2; private static final int CRLF = 3; private static final int ADD = 1; private static final int REMOVE = -1; private static final int SPACES = -1; private static final int TABS = 1; private static final int INBUFLEN = 8192; private static final int LINEBUFLEN = 200; private static final char CTRLZ = '\u001A'; private int tablength = 8; private String spaces = " "; private StringBuffer linebuf = new StringBuffer(1024); private StringBuffer linebuf2 = new StringBuffer(1024); private int eol; private String eolstr; private int ctrlz; private int tabs; private boolean javafiles = false; private boolean fixlast = true; private File srcDir; private File destDir = null; private FileUtils fileUtils = FileUtils.newFileUtils(); /** * Encoding to assume for the files */ private String encoding = null; /** * Defaults the properties based on the system type. *

  • Unix: eol="LF" tab="asis" eof="remove" *
  • Mac: eol="CR" tab="asis" eof="remove" *
  • DOS: eol="CRLF" tab="asis" eof="asis"
*/ public FixCRLF () { tabs = ASIS; if (Os.isFamily("mac")) { ctrlz = REMOVE; eol = CR; eolstr = "\r"; } else if (Os.isFamily("dos")) { ctrlz = ASIS; eol = CRLF; eolstr = "\r\n"; } else { ctrlz = REMOVE; eol = LF; eolstr = "\n"; } } /** * Set the source dir to find the source text files. */ public void setSrcdir(File srcDir) { this.srcDir = srcDir; } /** * Set the destination where the fixed files should be placed. * Default is to replace the original file. */ public void setDestdir(File destDir) { this.destDir = destDir; } /** * Set to true if modifying Java source files. */ public void setJavafiles(boolean javafiles) { this.javafiles = javafiles; } /** * Specify how EndOfLine characters are to be handled. * * @param attr valid values: *
    *
  • asis: leave line endings alone *
  • cr: convert line endings to CR *
  • lf: convert line endings to LF *
  • crlf: convert line endings to CRLF *
*/ public void setEol(CrLf attr) { String option = attr.getValue(); if (option.equals("asis")) { eol = ASIS; } else if (option.equals("cr") || option.equals("mac")) { eol = CR; eolstr = "\r"; } else if (option.equals("lf") || option.equals("unix")) { eol = LF; eolstr = "\n"; } else { // Must be "crlf" eol = CRLF; eolstr = "\r\n"; } } /** * Specify how carriage return (CR) characters are to be handled. * * @param attr valid values: *
    *
  • add: ensure that there is a CR before every LF *
  • asis: leave CR characters alone *
  • remove: remove all CR characters *
* * @deprecated use {@link #setEol setEol} instead. */ public void setCr(AddAsisRemove attr) { log("DEPRECATED: The cr attribute has been deprecated,", Project.MSG_WARN); log("Please use the eol attribute instead", Project.MSG_WARN); String option = attr.getValue(); CrLf c = new CrLf(); if (option.equals("remove")) { c.setValue("lf"); } else if (option.equals("asis")) { c.setValue("asis"); } else { // must be "add" c.setValue("crlf"); } setEol(c); } /** * Specify how tab characters are to be handled. * * @param attr valid values: *
    *
  • add: convert sequences of spaces which span a tab stop to tabs *
  • asis: leave tab and space characters alone *
  • remove: convert tabs to spaces *
*/ public void setTab(AddAsisRemove attr) { String option = attr.getValue(); if (option.equals("remove")) { tabs = SPACES; } else if (option.equals("asis")) { tabs = ASIS; } else { // must be "add" tabs = TABS; } } /** * Specify tab length in characters. * * @param tlength specify the length of tab in spaces, */ public void setTablength(int tlength) throws BuildException { if (tlength < 2 || tlength > 80) { throw new BuildException("tablength must be between 2 and 80", getLocation()); } tablength = tlength; StringBuffer sp = new StringBuffer(); for (int i = 0; i < tablength; i++) { sp.append(' '); } spaces = sp.toString(); } /** * Specify how DOS EOF (control-z) characters are to be handled. * * @param attr valid values: *
    *
  • add: ensure that there is an eof at the end of the file *
  • asis: leave eof characters alone *
  • remove: remove any eof character found at the end *
*/ public void setEof(AddAsisRemove attr) { String option = attr.getValue(); if (option.equals("remove")) { ctrlz = REMOVE; } else if (option.equals("asis")) { ctrlz = ASIS; } else { // must be "add" ctrlz = ADD; } } /** * Specifies the encoding Ant expects the files to be in - * defaults to the platforms default encoding. */ public void setEncoding(String encoding) { this.encoding = encoding; } /** * Specify whether a missing EOL will be added * to the final line of a file. */ public void setFixlast(boolean fixlast) { this.fixlast = fixlast; } /** * Executes the task. */ public void execute() throws BuildException { // first off, make sure that we've got a srcdir and destdir if (srcDir == null) { throw new BuildException("srcdir attribute must be set!"); } if (!srcDir.exists()) { throw new BuildException("srcdir does not exist!"); } if (!srcDir.isDirectory()) { throw new BuildException("srcdir is not a directory!"); } if (destDir != null) { if (!destDir.exists()) { throw new BuildException("destdir does not exist!"); } if (!destDir.isDirectory()) { throw new BuildException("destdir is not a directory!"); } } // log options used log("options:" + " eol=" + (eol == ASIS ? "asis" : eol == CR ? "cr" : eol == LF ? "lf" : "crlf") + " tab=" + (tabs == TABS ? "add" : tabs == ASIS ? "asis" : "remove") + " eof=" + (ctrlz == ADD ? "add" : ctrlz == ASIS ? "asis" : "remove") + " tablength=" + tablength + " encoding=" + (encoding == null ? "default" : encoding), Project.MSG_VERBOSE); DirectoryScanner ds = super.getDirectoryScanner(srcDir); String[] files = ds.getIncludedFiles(); for (int i = 0; i < files.length; i++) { processFile(files[i]); } } /** * Creates a Reader reading from a given file an taking the user * defined encoding into account. */ private Reader getReader(File f) throws IOException { return (encoding == null) ? new FileReader(f) : new InputStreamReader(new FileInputStream(f), encoding); } private void processFile(String file) throws BuildException { File srcFile = new File(srcDir, file); File destD = destDir == null ? srcDir : destDir; File tmpFile = null; BufferedWriter outWriter; OneLiner.BufferLine line; // read the contents of the file OneLiner lines = new OneLiner(srcFile); try { // Set up the output Writer try { tmpFile = fileUtils.createTempFile("fixcrlf", "", null); tmpFile.deleteOnExit(); Writer writer = (encoding == null) ? new FileWriter(tmpFile) : new OutputStreamWriter(new FileOutputStream(tmpFile), encoding); outWriter = new BufferedWriter(writer); } catch (IOException e) { throw new BuildException(e); } while (lines.hasMoreElements()) { // In-line states int endComment; try { line = (OneLiner.BufferLine) lines.nextElement(); } catch (NoSuchElementException e) { throw new BuildException(e); } String lineString = line.getLineString(); int linelen = line.length(); // Note - all of the following processing NOT done for // tabs ASIS if (tabs == ASIS) { // Just copy the body of the line across try { outWriter.write(lineString); } catch (IOException e) { throw new BuildException(e); } // end of try-catch } else { // (tabs != ASIS) while (line.getNext() < linelen) { switch (lines.getState()) { case NOTJAVA: notInConstant(line, line.length(), outWriter); break; case IN_MULTI_COMMENT: endComment = lineString.indexOf("*/", line.getNext()); if (endComment >= 0) { // End of multiLineComment on this line endComment += 2; // Include the end token lines.setState(LOOKING); } else { endComment = linelen; } notInConstant(line, endComment, outWriter); break; case IN_SINGLE_COMMENT: notInConstant(line, line.length(), outWriter); lines.setState(LOOKING); break; case IN_CHAR_CONST: case IN_STR_CONST: // Got here from LOOKING by finding an // opening "\'" next points to that quote // character. // Find the end of the constant. Watch // out for backslashes. Literal tabs are // left unchanged, and the column is // adjusted accordingly. int begin = line.getNext(); char terminator = (lines.getState() == IN_STR_CONST ? '\"' : '\''); endOfCharConst(line, terminator); while (line.getNext() < line.getLookahead()) { if (line.getNextCharInc() == '\t') { line.setColumn(line.getColumn() + tablength - (line.getColumn() % tablength)); } else { line.incColumn(); } } // Now output the substring try { outWriter.write(line.substring(begin, line.getNext())); } catch (IOException e) { throw new BuildException(e); } lines.setState(LOOKING); break; case LOOKING: nextStateChange(line); notInConstant(line, line.getLookahead(), outWriter); break; } // end of switch (state) } // end of while (line.getNext() < linelen) } // end of else (tabs != ASIS) if (!("".equals(line.getEol())) || fixlast) { try { outWriter.write(eolstr); } catch (IOException e) { throw new BuildException(e); } // end of try-catch } //end if non-blank original eol or fixlast } // end of while (lines.hasNext()) try { // Handle CTRLZ if (ctrlz == ASIS) { outWriter.write(lines.getEofStr()); } else if (ctrlz == ADD) { outWriter.write(CTRLZ); } } catch (IOException e) { throw new BuildException(e); } finally { try { outWriter.close(); } catch (IOException e) { throw new BuildException(e); } } try { lines.close(); lines = null; } catch (IOException e) { throw new BuildException("Unable to close source file " + srcFile); } File destFile = new File(destD, file); boolean destIsWrong = true; if (destFile.exists()) { // Compare the destination with the temp file log("destFile exists", Project.MSG_DEBUG); if (!fileUtils.contentEquals(destFile, tmpFile)) { log(destFile + " is being written", Project.MSG_DEBUG); } else { log(destFile + " is not written, as the contents " + "are identical", Project.MSG_DEBUG); destIsWrong = false; } } if (destIsWrong) { fileUtils.rename(tmpFile, destFile); tmpFile = null; } } catch (IOException e) { throw new BuildException(e); } finally { try { if (lines != null) { lines.close(); } } catch (IOException io) { log("Error closing " + srcFile, Project.MSG_ERR); } // end of catch if (tmpFile != null) { tmpFile.delete(); } } // end of finally } /** * Scan a BufferLine for the next state changing token: the beginning * of a single or multi-line comment, a character or a string constant. * * As a side-effect, sets the buffer state to the next state, and sets * field lookahead to the first character of the state-changing token, or * to the next eol character. * * @param bufline BufferLine containing the string * to be processed * @exception org.apache.tools.ant.BuildException * Thrown when end of line is reached * before the terminator is found. */ private void nextStateChange(OneLiner.BufferLine bufline) throws BuildException { int eol = bufline.length(); int ptr = bufline.getNext(); // Look for next single or double quote, double slash or slash star while (ptr < eol) { switch (bufline.getChar(ptr++)) { case '\'': bufline.setState(IN_CHAR_CONST); bufline.setLookahead(--ptr); return; case '\"': bufline.setState(IN_STR_CONST); bufline.setLookahead(--ptr); return; case '/': if (ptr < eol) { if (bufline.getChar(ptr) == '*') { bufline.setState(IN_MULTI_COMMENT); bufline.setLookahead(--ptr); return; } else if (bufline.getChar(ptr) == '/') { bufline.setState(IN_SINGLE_COMMENT); bufline.setLookahead(--ptr); return; } } break; } // end of switch (bufline.getChar(ptr++)) } // end of while (ptr < eol) // Eol is the next token bufline.setLookahead(ptr); } /** * Scan a BufferLine forward from the 'next' pointer * for the end of a character constant. Set 'lookahead' pointer to the * character following the terminating quote. * * @param bufline BufferLine containing the string * to be processed * @param terminator The constant terminator * * @exception org.apache.tools.ant.BuildException * Thrown when end of line is reached * before the terminator is found. */ private void endOfCharConst(OneLiner.BufferLine bufline, char terminator) throws BuildException { int ptr = bufline.getNext(); int eol = bufline.length(); char c; ptr++; // skip past initial quote while (ptr < eol) { if ((c = bufline.getChar(ptr++)) == '\\') { ptr++; } else { if (c == terminator) { bufline.setLookahead(ptr); return; } } } // end of while (ptr < eol) // Must have fallen through to the end of the line throw new BuildException("endOfCharConst: unterminated char constant"); } /** * Process a BufferLine string which is not part of a string constant. * The start position of the string is given by the 'next' field. * Sets the 'next' and 'column' fields in the BufferLine. * * @param bufline BufferLine containing the string * to be processed * @param end Index just past the end of the * string * @param outWriter Sink for the processed string */ private void notInConstant(OneLiner.BufferLine bufline, int end, BufferedWriter outWriter) { // N.B. both column and string index are zero-based // Process a string not part of a constant; // i.e. convert tabs<->spaces as required // This is NOT called for ASIS tab handling int nextTab; int nextStop; int tabspaces; String line = bufline.substring(bufline.getNext(), end); int place = 0; // Zero-based int col = bufline.getColumn(); // Zero-based // process sequences of white space // first convert all tabs to spaces linebuf = new StringBuffer(); while ((nextTab = line.indexOf((int) '\t', place)) >= 0) { linebuf.append(line.substring(place, nextTab)); // copy to the TAB col += nextTab - place; tabspaces = tablength - (col % tablength); linebuf.append(spaces.substring(0, tabspaces)); col += tabspaces; place = nextTab + 1; } // end of while linebuf.append(line.substring(place, line.length())); // if converting to spaces, all finished String linestring = new String(linebuf.substring(0)); if (tabs == REMOVE) { try { outWriter.write(linestring); } catch (IOException e) { throw new BuildException(e); } // end of try-catch } else { // tabs == ADD int tabCol; linebuf2 = new StringBuffer(); place = 0; col = bufline.getColumn(); int placediff = col - 0; // for the length of the string, cycle through the tab stop // positions, checking for a space preceded by at least one // other space at the tab stop. if so replace the longest possible // preceding sequence of spaces with a tab. nextStop = col + (tablength - col % tablength); if (nextStop - col < 2) { linebuf2.append(linestring.substring( place, nextStop - placediff)); place = nextStop - placediff; nextStop += tablength; } for (; nextStop - placediff <= linestring.length(); nextStop += tablength) { for (tabCol = nextStop; --tabCol - placediff >= place && linestring.charAt(tabCol - placediff) == ' ';) { ; // Loop for the side-effects } // tabCol is column index of the last non-space character // before the next tab stop if (nextStop - tabCol > 2) { linebuf2.append(linestring.substring( place, ++tabCol - placediff)); linebuf2.append('\t'); } else { linebuf2.append(linestring.substring( place, nextStop - placediff)); } // end of else place = nextStop - placediff; } // end of for (nextStop ... ) // pick up that last bit, if any linebuf2.append(linestring.substring(place, linestring.length())); try { outWriter.write(linebuf2.substring(0)); } catch (IOException e) { throw new BuildException(e); } // end of try-catch } // end of else tabs == ADD // Set column position as modified by this method bufline.setColumn(bufline.getColumn() + linestring.length()); bufline.setNext(end); } class OneLiner implements Enumeration { private int state = javafiles ? LOOKING : NOTJAVA; private StringBuffer eolStr = new StringBuffer(LINEBUFLEN); private StringBuffer eofStr = new StringBuffer(); private BufferedReader reader; private StringBuffer line = new StringBuffer(); private boolean reachedEof = false; private File srcFile; public OneLiner(File srcFile) throws BuildException { this.srcFile = srcFile; try { reader = new BufferedReader (getReader(srcFile), INBUFLEN); nextLine(); } catch (IOException e) { throw new BuildException(srcFile + ": " + e.getMessage(), e, getLocation()); } } protected void nextLine() throws BuildException { int ch = -1; int eolcount = 0; eolStr = new StringBuffer(); line = new StringBuffer(); try { ch = reader.read(); while (ch != -1 && ch != '\r' && ch != '\n') { line.append((char) ch); ch = reader.read(); } if (ch == -1 && line.length() == 0) { // Eof has been reached reachedEof = true; return; } switch ((char) ch) { case '\r': // Check for \r, \r\n and \r\r\n // Regard \r\r not followed by \n as two lines ++eolcount; eolStr.append('\r'); reader.mark(2); switch ((ch = reader.read())) { case '\r': if ((char) (ch = reader.read()) == '\n') { eolcount += 2; eolStr.append("\r\n"); } else { reader.reset(); } break; case '\n': ++eolcount; eolStr.append('\n'); break; case -1: // don't reposition when we've reached the end // of the stream break; default: reader.reset(); break; } // end of switch ((char)(ch = reader.read())) break; case '\n': ++eolcount; eolStr.append('\n'); break; } // end of switch ((char) ch) // if at eolcount == 0 and trailing characters of string // are CTRL-Zs, set eofStr if (eolcount == 0) { int i = line.length(); while (--i >= 0 && line.charAt(i) == CTRLZ) { // keep searching for the first ^Z } if (i < line.length() - 1) { // Trailing characters are ^Zs // Construct new line and eofStr eofStr.append(line.toString().substring(i + 1)); if (i < 0) { line.setLength(0); reachedEof = true; } else { line.setLength(i + 1); } } } // end of if (eolcount == 0) } catch (IOException e) { throw new BuildException(srcFile + ": " + e.getMessage(), e, getLocation()); } } public String getEofStr() { return eofStr.substring(0); } public int getState() { return state; } public void setState(int state) { this.state = state; } public boolean hasMoreElements() { return !reachedEof; } public Object nextElement() throws NoSuchElementException { if (!hasMoreElements()) { throw new NoSuchElementException("OneLiner"); } BufferLine tmpLine = new BufferLine(line.toString(), eolStr.substring(0)); nextLine(); return tmpLine; } public void close() throws IOException { if (reader != null) { reader.close(); } } class BufferLine { private int next = 0; private int column = 0; private int lookahead = UNDEF; private String line; private String eolStr; public BufferLine(String line, String eolStr) throws BuildException { next = 0; column = 0; this.line = line; this.eolStr = eolStr; } public int getNext() { return next; } public void setNext(int next) { this.next = next; } public int getLookahead() { return lookahead; } public void setLookahead(int lookahead) { this.lookahead = lookahead; } public char getChar(int i) { return line.charAt(i); } public char getNextChar() { return getChar(next); } public char getNextCharInc() { return getChar(next++); } public int getColumn() { return column; } public void setColumn(int col) { column = col; } public int incColumn() { return column++; } public int length() { return line.length(); } public int getEolLength() { return eolStr.length(); } public String getLineString() { return line; } public String getEol() { return eolStr; } public String substring(int begin) { return line.substring(begin); } public String substring(int begin, int end) { return line.substring(begin, end); } public void setState(int state) { OneLiner.this.setState(state); } public int getState() { return OneLiner.this.getState(); } } } /** * Enumerated attribute with the values "asis", "add" and "remove". */ public static class AddAsisRemove extends EnumeratedAttribute { public String[] getValues() { return new String[] {"add", "asis", "remove"}; } } /** * Enumerated attribute with the values "asis", "cr", "lf" and "crlf". */ public static class CrLf extends EnumeratedAttribute { /** * @see EnumeratedAttribute#getValues */ public String[] getValues() { return new String[] {"asis", "cr", "lf", "crlf", "mac", "unix", "dos"}; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy