0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018: package org.apache.tools.ant.filters;
0019:
0020: import java.io.IOException;
0021: import java.io.Reader;
0022: import org.apache.tools.ant.BuildException;
0023: import org.apache.tools.ant.taskdefs.condition.Os;
0024: import org.apache.tools.ant.types.EnumeratedAttribute;
0025:
0026: /**
0027: * Converts text to local OS formatting conventions, as well as repair text
0028: * damaged by misconfigured or misguided editors or file transfer programs.
0029: * <p>
0030: * This filter can take the following arguments:
0031: * <ul>
0032: * <li>eof
0033: * <li>eol
0034: * <li>fixlast
0035: * <li>javafiles
0036: * <li>tab
0037: * <li>tablength
0038: * </ul>
0039: * None of which are required.
0040: * <p>
0041: * This version generalises the handling of EOL characters, and allows for
0042: * CR-only line endings (the standard on Mac systems prior to OS X). Tab
0043: * handling has also been generalised to accommodate any tabwidth from 2 to 80,
0044: * inclusive. Importantly, it can leave untouched any literal TAB characters
0045: * embedded within Java string or character constants.
0046: * <p>
0047: * <em>Caution:</em> run with care on carefully formatted files. This may
0048: * sound obvious, but if you don't specify asis, presume that your files are
0049: * going to be modified. If "tabs" is "add" or "remove", whitespace characters
0050: * may be added or removed as necessary. Similarly, for EOLs, eol="asis"
0051: * actually means convert to your native O/S EOL convention while eol="crlf" or
0052: * cr="add" can result in CR characters being removed in one special case
0053: * accommodated, i.e., CRCRLF is regarded as a single EOL to handle cases where
0054: * other programs have converted CRLF into CRCRLF.
0055: *
0056: * <P>
0057: * Example:
0058: *
0059: * <pre>
0060: * <<fixcrlf tab="add" eol="crlf" eof="asis"/>
0061: * </pre>
0062: *
0063: * Or:
0064: *
0065: * <pre>
0066: * <filterreader classname="org.apache.tools.ant.filters.FixCrLfFilter">
0067: * <param eol="crlf" tab="asis"/>
0068: * </filterreader>
0069: * </pre>
0070: *
0071: */
0072: public final class FixCrLfFilter extends BaseParamFilterReader
0073: implements ChainableReader {
0074: private static final char CTRLZ = '\u001A';
0075:
0076: private int tabLength = 8;
0077:
0078: private CrLf eol;
0079:
0080: private AddAsisRemove ctrlz;
0081:
0082: private AddAsisRemove tabs;
0083:
0084: private boolean javafiles = false;
0085:
0086: private boolean fixlast = true;
0087:
0088: private boolean initialized = false;
0089:
0090: /**
0091: * Constructor for "dummy" instances.
0092: *
0093: * @see BaseFilterReader#BaseFilterReader()
0094: */
0095: public FixCrLfFilter() {
0096: super ();
0097: }
0098:
0099: /**
0100: * Create a new filtered reader.
0101: *
0102: * @param in
0103: * A Reader object providing the underlying stream. Must not be
0104: * <code>null</code>.
0105: * @throws IOException on error.
0106: */
0107: public FixCrLfFilter(final Reader in) throws IOException {
0108: super (in);
0109: }
0110:
0111: // Instance initializer: Executes just after the super() call in this
0112: // class's constructor.
0113: {
0114: tabs = AddAsisRemove.ASIS;
0115: if (Os.isFamily("mac") && !Os.isFamily("unix")) {
0116: ctrlz = AddAsisRemove.REMOVE;
0117: setEol(CrLf.MAC);
0118: } else if (Os.isFamily("dos")) {
0119: ctrlz = AddAsisRemove.ASIS;
0120: setEol(CrLf.DOS);
0121: } else {
0122: ctrlz = AddAsisRemove.REMOVE;
0123: setEol(CrLf.UNIX);
0124: }
0125: }
0126:
0127: /**
0128: * Create a new FixCrLfFilter using the passed in Reader for instantiation.
0129: *
0130: * @param rdr
0131: * A Reader object providing the underlying stream. Must not be
0132: * <code>null</code>.
0133: *
0134: * @return a new filter based on this configuration, but filtering the
0135: * specified reader.
0136: */
0137: public Reader chain(final Reader rdr) {
0138: try {
0139: FixCrLfFilter newFilter = new FixCrLfFilter(rdr);
0140:
0141: newFilter.setJavafiles(getJavafiles());
0142: newFilter.setEol(getEol());
0143: newFilter.setTab(getTab());
0144: newFilter.setTablength(getTablength());
0145: newFilter.setEof(getEof());
0146: newFilter.setFixlast(getFixlast());
0147: newFilter.initInternalFilters();
0148:
0149: return newFilter;
0150: } catch (IOException e) {
0151: throw new BuildException(e);
0152: }
0153: }
0154:
0155: /**
0156: * Get how DOS EOF (control-z) characters are being handled.
0157: *
0158: * @return values:
0159: * <ul>
0160: * <li>add: ensure that there is an eof at the end of the file
0161: * <li>asis: leave eof characters alone
0162: * <li>remove: remove any eof character found at the end
0163: * </ul>
0164: */
0165: public AddAsisRemove getEof() {
0166: // Return copy so that the call must call setEof() to change the state
0167: // of fixCRLF
0168: return ctrlz.newInstance();
0169: }
0170:
0171: /**
0172: * Get how EndOfLine characters are being handled.
0173: *
0174: * @return values:
0175: * <ul>
0176: * <li>asis: convert line endings to your O/S convention
0177: * <li>cr: convert line endings to CR
0178: * <li>lf: convert line endings to LF
0179: * <li>crlf: convert line endings to CRLF
0180: * </ul>
0181: */
0182: public CrLf getEol() {
0183: // Return copy so that the call must call setEol() to change the state
0184: // of fixCRLF
0185: return eol.newInstance();
0186: }
0187:
0188: /**
0189: * Get whether a missing EOL be added to the final line of the stream.
0190: *
0191: * @return true if a filtered file will always end with an EOL
0192: */
0193: public boolean getFixlast() {
0194: return fixlast;
0195: }
0196:
0197: /**
0198: * Get whether the stream is to be treated as though it contains Java
0199: * source.
0200: * <P>
0201: * This attribute is only used in assocation with the "<i><b>tab</b></i>"
0202: * attribute. Tabs found in Java literals are protected from changes by this
0203: * filter.
0204: *
0205: * @return true if whitespace in Java character and string literals is
0206: * ignored.
0207: */
0208: public boolean getJavafiles() {
0209: return javafiles;
0210: }
0211:
0212: /**
0213: * Return how tab characters are being handled.
0214: *
0215: * @return values:
0216: * <ul>
0217: * <li>add: convert sequences of spaces which span a tab stop to
0218: * tabs
0219: * <li>asis: leave tab and space characters alone
0220: * <li>remove: convert tabs to spaces
0221: * </ul>
0222: */
0223: public AddAsisRemove getTab() {
0224: // Return copy so that the caller must call setTab() to change the state
0225: // of fixCRLF.
0226: return tabs.newInstance();
0227: }
0228:
0229: /**
0230: * Get the tab length to use.
0231: *
0232: * @return the length of tab in spaces
0233: */
0234: public int getTablength() {
0235: return tabLength;
0236: }
0237:
0238: private static String calculateEolString(CrLf eol) {
0239: // Calculate the EOL string per the current config
0240: if (eol == CrLf.ASIS) {
0241: return System.getProperty("line.separator");
0242: }
0243: if (eol == CrLf.CR || eol == CrLf.MAC) {
0244: return "\r";
0245: }
0246: if (eol == CrLf.CRLF || eol == CrLf.DOS) {
0247: return "\r\n";
0248: }
0249: // assume (eol == CrLf.LF || eol == CrLf.UNIX)
0250: return "\n";
0251: }
0252:
0253: /**
0254: * Wrap the input stream with the internal filters necessary to perform the
0255: * configuration settings.
0256: */
0257: private void initInternalFilters() {
0258:
0259: // If I'm removing an EOF character, do so first so that the other
0260: // filters don't see that character.
0261: in = (ctrlz == AddAsisRemove.REMOVE) ? new RemoveEofFilter(in)
0262: : in;
0263:
0264: // Change all EOL characters to match the calculated EOL string. If
0265: // configured to do so, append a trailing EOL so that the file ends on
0266: // a EOL.
0267: in = new NormalizeEolFilter(in, calculateEolString(eol),
0268: getFixlast());
0269:
0270: if (tabs != AddAsisRemove.ASIS) {
0271: // If filtering Java source, prevent changes to whitespace in
0272: // character and string literals.
0273: if (getJavafiles()) {
0274: in = new MaskJavaTabLiteralsFilter(in);
0275: }
0276: // Add/Remove tabs
0277: in = (tabs == AddAsisRemove.ADD) ? (Reader) new AddTabFilter(
0278: in, getTablength())
0279: : (Reader) new RemoveTabFilter(in, getTablength());
0280: }
0281: // Add missing EOF character
0282: in = (ctrlz == AddAsisRemove.ADD) ? new AddEofFilter(in) : in;
0283: initialized = true;
0284: }
0285:
0286: /**
0287: * Return the next character in the filtered stream.
0288: *
0289: * @return the next character in the resulting stream, or -1 if the end of
0290: * the resulting stream has been reached.
0291: *
0292: * @exception IOException
0293: * if the underlying stream throws an IOException during
0294: * reading.
0295: */
0296: public synchronized int read() throws IOException {
0297: if (!initialized) {
0298: initInternalFilters();
0299: }
0300: return in.read();
0301: }
0302:
0303: /**
0304: * Specify how DOS EOF (control-z) characters are to be handled.
0305: *
0306: * @param attr
0307: * valid values:
0308: * <ul>
0309: * <li>add: ensure that there is an eof at the end of the file
0310: * <li>asis: leave eof characters alone
0311: * <li>remove: remove any eof character found at the end
0312: * </ul>
0313: */
0314: public void setEof(AddAsisRemove attr) {
0315: ctrlz = attr.resolve();
0316: }
0317:
0318: /**
0319: * Specify how end of line (EOL) characters are to be handled.
0320: *
0321: * @param attr
0322: * valid values:
0323: * <ul>
0324: * <li>asis: convert line endings to your O/S convention
0325: * <li>cr: convert line endings to CR
0326: * <li>lf: convert line endings to LF
0327: * <li>crlf: convert line endings to CRLF
0328: * </ul>
0329: */
0330: public void setEol(CrLf attr) {
0331: eol = attr.resolve();
0332: }
0333:
0334: /**
0335: * Specify whether a missing EOL will be added to the final line of input.
0336: *
0337: * @param fixlast
0338: * if true a missing EOL will be appended.
0339: */
0340: public void setFixlast(boolean fixlast) {
0341: this .fixlast = fixlast;
0342: }
0343:
0344: /**
0345: * Indicate whether this stream contains Java source.
0346: *
0347: * This attribute is only used in assocation with the "<i><b>tab</b></i>"
0348: * attribute.
0349: *
0350: * @param javafiles
0351: * set to true to prevent this filter from changing tabs found in
0352: * Java literals.
0353: */
0354: public void setJavafiles(boolean javafiles) {
0355: this .javafiles = javafiles;
0356: }
0357:
0358: /**
0359: * Specify how tab characters are to be handled.
0360: *
0361: * @param attr
0362: * valid values:
0363: * <ul>
0364: * <li>add: convert sequences of spaces which span a tab stop to
0365: * tabs
0366: * <li>asis: leave tab and space characters alone
0367: * <li>remove: convert tabs to spaces
0368: * </ul>
0369: */
0370: public void setTab(AddAsisRemove attr) {
0371: tabs = attr.resolve();
0372: }
0373:
0374: /**
0375: * Specify tab length in characters.
0376: *
0377: * @param tabLength
0378: * specify the length of tab in spaces. Valid values are between
0379: * 2 and 80 inclusive. The default for this parameter is 8.
0380: * @throws IOException on error.
0381: */
0382: public void setTablength(int tabLength) throws IOException {
0383: if (tabLength < 2 || tabLength > 80) {
0384: throw new IOException("tablength must be between 2 and 80");
0385: }
0386: this .tabLength = tabLength;
0387: }
0388:
0389: /**
0390: * This filter reader redirects all read I/O methods through its own read()
0391: * method.
0392: *
0393: * <P>
0394: * The input stream is already buffered by the copy task so this doesn't
0395: * significantly impact performance while it makes writing the individual
0396: * fix filters much easier.
0397: * </P>
0398: */
0399: private static class SimpleFilterReader extends Reader {
0400: private Reader in;
0401:
0402: private int[] preempt = new int[16];
0403:
0404: private int preemptIndex = 0;
0405:
0406: public SimpleFilterReader(Reader in) {
0407: this .in = in;
0408: }
0409:
0410: public void push(char c) {
0411: push((int) c);
0412: }
0413:
0414: public void push(int c) {
0415: try {
0416: preempt[preemptIndex++] = c;
0417: } catch (ArrayIndexOutOfBoundsException e) {
0418: int[] p2 = new int[preempt.length * 2];
0419: System.arraycopy(preempt, 0, p2, 0, preempt.length);
0420: preempt = p2;
0421: push(c);
0422: }
0423: }
0424:
0425: public void push(char[] cs, int start, int length) {
0426: for (int i = start + length - 1; i >= start;) {
0427: push(cs[i--]);
0428: }
0429: }
0430:
0431: public void push(char[] cs) {
0432: push(cs, 0, cs.length);
0433: }
0434:
0435: public void push(String s) {
0436: push(s.toCharArray());
0437: }
0438:
0439: /**
0440: * Does this filter want to block edits on the last character returned
0441: * by read()?
0442: */
0443: public boolean editsBlocked() {
0444: return in instanceof SimpleFilterReader
0445: && ((SimpleFilterReader) in).editsBlocked();
0446: }
0447:
0448: public int read() throws java.io.IOException {
0449: return preemptIndex > 0 ? preempt[--preemptIndex] : in
0450: .read();
0451: }
0452:
0453: public void close() throws java.io.IOException {
0454: in.close();
0455: }
0456:
0457: public void reset() throws IOException {
0458: in.reset();
0459: }
0460:
0461: public boolean markSupported() {
0462: return in.markSupported();
0463: }
0464:
0465: public boolean ready() throws java.io.IOException {
0466: return in.ready();
0467: }
0468:
0469: public void mark(int i) throws java.io.IOException {
0470: in.mark(i);
0471: }
0472:
0473: public long skip(long i) throws java.io.IOException {
0474: return in.skip(i);
0475: }
0476:
0477: public int read(char[] buf) throws java.io.IOException {
0478: return read(buf, 0, buf.length);
0479: }
0480:
0481: public int read(char[] buf, int start, int length)
0482: throws java.io.IOException {
0483: int count = 0;
0484: int c = 0;
0485:
0486: while (length-- > 0 && (c = this .read()) != -1) {
0487: buf[start++] = (char) c;
0488: count++;
0489: }
0490: // if at EOF with no characters in the buffer, return EOF
0491: return (count == 0 && c == -1) ? -1 : count;
0492: }
0493: }
0494:
0495: private static class MaskJavaTabLiteralsFilter extends
0496: SimpleFilterReader {
0497: private boolean editsBlocked = false;
0498:
0499: private static final int JAVA = 1;
0500:
0501: private static final int IN_CHAR_CONST = 2;
0502:
0503: private static final int IN_STR_CONST = 3;
0504:
0505: private static final int IN_SINGLE_COMMENT = 4;
0506:
0507: private static final int IN_MULTI_COMMENT = 5;
0508:
0509: private static final int TRANS_TO_COMMENT = 6;
0510:
0511: private static final int TRANS_FROM_MULTI = 8;
0512:
0513: private int state;
0514:
0515: public MaskJavaTabLiteralsFilter(Reader in) {
0516: super (in);
0517: state = JAVA;
0518: }
0519:
0520: public boolean editsBlocked() {
0521: return editsBlocked || super .editsBlocked();
0522: }
0523:
0524: public int read() throws IOException {
0525: int this Char = super .read();
0526: // Mask, block from being edited, all characters in constants.
0527: editsBlocked = (state == IN_CHAR_CONST || state == IN_STR_CONST);
0528:
0529: switch (state) {
0530: case JAVA:
0531: // The current character is always emitted.
0532: switch (this Char) {
0533: case '\'':
0534: state = IN_CHAR_CONST;
0535: break;
0536: case '"':
0537: state = IN_STR_CONST;
0538: break;
0539: case '/':
0540: state = TRANS_TO_COMMENT;
0541: break;
0542: default:
0543: // Fall tru
0544: }
0545: break;
0546: case IN_CHAR_CONST:
0547: switch (this Char) {
0548: case '\'':
0549: state = JAVA;
0550: break;
0551: default:
0552: // Fall tru
0553: }
0554: break;
0555: case IN_STR_CONST:
0556: switch (this Char) {
0557: case '"':
0558: state = JAVA;
0559: break;
0560: default:
0561: // Fall tru
0562: }
0563: break;
0564: case IN_SINGLE_COMMENT:
0565: // The current character is always emitted.
0566: switch (this Char) {
0567: case '\n':
0568: case '\r': // EOL
0569: state = JAVA;
0570: break;
0571: default:
0572: // Fall tru
0573: }
0574: break;
0575: case IN_MULTI_COMMENT:
0576: // The current character is always emitted.
0577: switch (this Char) {
0578: case '*':
0579: state = TRANS_FROM_MULTI;
0580: break;
0581: default:
0582: // Fall tru
0583: }
0584: break;
0585: case TRANS_TO_COMMENT:
0586: // The current character is always emitted.
0587: switch (this Char) {
0588: case '*':
0589: state = IN_MULTI_COMMENT;
0590: break;
0591: case '/':
0592: state = IN_SINGLE_COMMENT;
0593: break;
0594: case '\'':
0595: state = IN_CHAR_CONST;
0596: break;
0597: case '"':
0598: state = IN_STR_CONST;
0599: break;
0600: default:
0601: state = JAVA;
0602: }
0603: break;
0604: case TRANS_FROM_MULTI:
0605: // The current character is always emitted.
0606: switch (this Char) {
0607: case '/':
0608: state = JAVA;
0609: break;
0610: default:
0611: // Fall tru
0612: }
0613: break;
0614: default:
0615: // Fall tru
0616: }
0617: return this Char;
0618: }
0619: }
0620:
0621: private static class NormalizeEolFilter extends SimpleFilterReader {
0622: private boolean previousWasEOL;
0623:
0624: private boolean fixLast;
0625:
0626: private int normalizedEOL = 0;
0627:
0628: private char[] eol = null;
0629:
0630: public NormalizeEolFilter(Reader in, String eolString,
0631: boolean fixLast) {
0632: super (in);
0633: eol = eolString.toCharArray();
0634: this .fixLast = fixLast;
0635: }
0636:
0637: public int read() throws IOException {
0638: int this Char = super .read();
0639:
0640: if (normalizedEOL == 0) {
0641: int numEOL = 0;
0642: boolean atEnd = false;
0643: switch (this Char) {
0644: case CTRLZ:
0645: int c = super .read();
0646: if (c == -1) {
0647: atEnd = true;
0648: if (fixLast && !previousWasEOL) {
0649: numEOL = 1;
0650: push(this Char);
0651: }
0652: } else {
0653: push(c);
0654: }
0655: break;
0656: case -1:
0657: atEnd = true;
0658: if (fixLast && !previousWasEOL) {
0659: numEOL = 1;
0660: }
0661: break;
0662: case '\n':
0663: // EOL was "\n"
0664: numEOL = 1;
0665: break;
0666: case '\r':
0667: numEOL = 1;
0668: int c1 = super .read();
0669: int c2 = super .read();
0670:
0671: if (c1 == '\r' && c2 == '\n') {
0672: // EOL was "\r\r\n"
0673: } else if (c1 == '\r') {
0674: // EOL was "\r\r" - handle as two consecutive "\r" and
0675: // "\r"
0676: numEOL = 2;
0677: push(c2);
0678: } else if (c1 == '\n') {
0679: // EOL was "\r\n"
0680: push(c2);
0681: } else {
0682: // EOL was "\r"
0683: push(c2);
0684: push(c1);
0685: }
0686: default:
0687: // Fall tru
0688: }
0689: if (numEOL > 0) {
0690: while (numEOL-- > 0) {
0691: push(eol);
0692: normalizedEOL += eol.length;
0693: }
0694: previousWasEOL = true;
0695: this Char = read();
0696: } else if (!atEnd) {
0697: previousWasEOL = false;
0698: }
0699: } else {
0700: normalizedEOL--;
0701: }
0702: return this Char;
0703: }
0704: }
0705:
0706: private static class AddEofFilter extends SimpleFilterReader {
0707: private int lastChar = -1;
0708:
0709: public AddEofFilter(Reader in) {
0710: super (in);
0711: }
0712:
0713: public int read() throws IOException {
0714: int this Char = super .read();
0715:
0716: // if source is EOF but last character was NOT ctrl-z, return ctrl-z
0717: if (this Char == -1) {
0718: if (lastChar != CTRLZ) {
0719: lastChar = CTRLZ;
0720: return lastChar;
0721: }
0722: } else {
0723: lastChar = this Char;
0724: }
0725: return this Char;
0726: }
0727: }
0728:
0729: private static class RemoveEofFilter extends SimpleFilterReader {
0730: private int lookAhead = -1;
0731:
0732: public RemoveEofFilter(Reader in) {
0733: super (in);
0734:
0735: try {
0736: lookAhead = in.read();
0737: } catch (IOException e) {
0738: lookAhead = -1;
0739: }
0740: }
0741:
0742: public int read() throws IOException {
0743: int lookAhead2 = super .read();
0744:
0745: // If source at EOF and lookAhead is ctrl-z, return EOF (NOT ctrl-z)
0746: if (lookAhead2 == -1 && lookAhead == CTRLZ) {
0747: return -1;
0748: }
0749: // Return current look-ahead
0750: int i = lookAhead;
0751: lookAhead = lookAhead2;
0752: return i;
0753: }
0754: }
0755:
0756: private static class AddTabFilter extends SimpleFilterReader {
0757: private int columnNumber = 0;
0758:
0759: private int tabLength = 0;
0760:
0761: public AddTabFilter(Reader in, int tabLength) {
0762: super (in);
0763: this .tabLength = tabLength;
0764: }
0765:
0766: public int read() throws IOException {
0767: int c = super .read();
0768:
0769: switch (c) {
0770: case '\r':
0771: case '\n':
0772: columnNumber = 0;
0773: break;
0774: case ' ':
0775: columnNumber++;
0776: if (!editsBlocked()) {
0777: int colNextTab = ((columnNumber + tabLength - 1) / tabLength)
0778: * tabLength;
0779: int countSpaces = 1;
0780: int numTabs = 0;
0781:
0782: scanWhitespace: while ((c = super .read()) != -1) {
0783: switch (c) {
0784: case ' ':
0785: if (++columnNumber == colNextTab) {
0786: numTabs++;
0787: countSpaces = 0;
0788: colNextTab += tabLength;
0789: } else {
0790: countSpaces++;
0791: }
0792: break;
0793: case '\t':
0794: columnNumber = colNextTab;
0795: numTabs++;
0796: countSpaces = 0;
0797: colNextTab += tabLength;
0798: break;
0799: default:
0800: push(c);
0801: break scanWhitespace;
0802: }
0803: }
0804: while (countSpaces-- > 0) {
0805: push(' ');
0806: columnNumber--;
0807: }
0808: while (numTabs-- > 0) {
0809: push('\t');
0810: columnNumber -= tabLength;
0811: }
0812: c = super .read();
0813: switch (c) {
0814: case ' ':
0815: columnNumber++;
0816: break;
0817: case '\t':
0818: columnNumber += tabLength;
0819: break;
0820: default:
0821: // Fall tru
0822: }
0823: }
0824: break;
0825: case '\t':
0826: columnNumber = ((columnNumber + tabLength - 1) / tabLength)
0827: * tabLength;
0828: break;
0829: default:
0830: columnNumber++;
0831: }
0832: return c;
0833: }
0834: }
0835:
0836: private static class RemoveTabFilter extends SimpleFilterReader {
0837: private int columnNumber = 0;
0838:
0839: private int tabLength = 0;
0840:
0841: public RemoveTabFilter(Reader in, int tabLength) {
0842: super (in);
0843:
0844: this .tabLength = tabLength;
0845: }
0846:
0847: public int read() throws IOException {
0848: int c = super .read();
0849:
0850: switch (c) {
0851: case '\r':
0852: case '\n':
0853: columnNumber = 0;
0854: break;
0855: case '\t':
0856: int width = tabLength - columnNumber % tabLength;
0857:
0858: if (!editsBlocked()) {
0859: for (; width > 1; width--) {
0860: push(' ');
0861: }
0862: c = ' ';
0863: }
0864: columnNumber += width;
0865: break;
0866: default:
0867: columnNumber++;
0868: }
0869: return c;
0870: }
0871: }
0872:
0873: /**
0874: * Enumerated attribute with the values "asis", "add" and "remove".
0875: */
0876: public static class AddAsisRemove extends EnumeratedAttribute {
0877: private static final AddAsisRemove ASIS = newInstance("asis");
0878:
0879: private static final AddAsisRemove ADD = newInstance("add");
0880:
0881: private static final AddAsisRemove REMOVE = newInstance("remove");
0882:
0883: /** {@inheritDoc}. */
0884: public String[] getValues() {
0885: return new String[] { "add", "asis", "remove" };
0886: }
0887:
0888: /**
0889: * Equality depending in the index.
0890: * @param other the object to test equality against.
0891: * @return true if the object has the same index as this.
0892: */
0893: public boolean equals(Object other) {
0894: return other instanceof AddAsisRemove
0895: && getIndex() == ((AddAsisRemove) other).getIndex();
0896: }
0897:
0898: /**
0899: * Hashcode depending on the index.
0900: * @return the index as the hashcode.
0901: */
0902: public int hashCode() {
0903: return getIndex();
0904: }
0905:
0906: AddAsisRemove resolve() throws IllegalStateException {
0907: if (this .equals(ASIS)) {
0908: return ASIS;
0909: }
0910: if (this .equals(ADD)) {
0911: return ADD;
0912: }
0913: if (this .equals(REMOVE)) {
0914: return REMOVE;
0915: }
0916: throw new IllegalStateException("No replacement for "
0917: + this );
0918: }
0919:
0920: // Works like clone() but doesn't show up in the Javadocs
0921: private AddAsisRemove newInstance() {
0922: return newInstance(getValue());
0923: }
0924:
0925: /**
0926: * Create an instance of this enumerated value based on the string value.
0927: * @param value the value to use.
0928: * @return an enumerated instance.
0929: */
0930: public static AddAsisRemove newInstance(String value) {
0931: AddAsisRemove a = new AddAsisRemove();
0932: a.setValue(value);
0933: return a;
0934: }
0935: }
0936:
0937: /**
0938: * Enumerated attribute with the values "asis", "cr", "lf" and "crlf".
0939: */
0940: public static class CrLf extends EnumeratedAttribute {
0941: private static final CrLf ASIS = newInstance("asis");
0942:
0943: private static final CrLf CR = newInstance("cr");
0944:
0945: private static final CrLf CRLF = newInstance("crlf");
0946:
0947: private static final CrLf DOS = newInstance("dos");
0948:
0949: private static final CrLf LF = newInstance("lf");
0950:
0951: private static final CrLf MAC = newInstance("mac");
0952:
0953: private static final CrLf UNIX = newInstance("unix");
0954:
0955: /**
0956: * @see EnumeratedAttribute#getValues
0957: */
0958: /** {@inheritDoc}. */
0959: public String[] getValues() {
0960: return new String[] { "asis", "cr", "lf", "crlf", "mac",
0961: "unix", "dos" };
0962: }
0963:
0964: /**
0965: * Equality depending in the index.
0966: * @param other the object to test equality against.
0967: * @return true if the object has the same index as this.
0968: */
0969: public boolean equals(Object other) {
0970: return other instanceof CrLf
0971: && getIndex() == ((CrLf) other).getIndex();
0972: }
0973:
0974: /**
0975: * Hashcode depending on the index.
0976: * @return the index as the hashcode.
0977: */
0978: public int hashCode() {
0979: return getIndex();
0980: }
0981:
0982: CrLf resolve() {
0983: if (this .equals(ASIS)) {
0984: return ASIS;
0985: }
0986: if (this .equals(CR) || this .equals(MAC)) {
0987: return CR;
0988: }
0989: if (this .equals(CRLF) || this .equals(DOS)) {
0990: return CRLF;
0991: }
0992: if (this .equals(LF) || this .equals(UNIX)) {
0993: return LF;
0994: }
0995: throw new IllegalStateException("No replacement for "
0996: + this );
0997: }
0998:
0999: // Works like clone() but doesn't show up in the Javadocs
1000: private CrLf newInstance() {
1001: return newInstance(getValue());
1002: }
1003:
1004: /**
1005: * Create an instance of this enumerated value based on the string value.
1006: * @param value the value to use.
1007: * @return an enumerated instance.
1008: */
1009: public static CrLf newInstance(String value) {
1010: CrLf c = new CrLf();
1011: c.setValue(value);
1012: return c;
1013: }
1014: }
1015: }
|