001/**
002 * Java Web Archive Toolkit - Software to read and validate ARC, WARC
003 * and GZip files. (http://jwat.org/)
004 * Copyright 2011-2012 Netarkivet.dk (http://netarkivet.dk/)
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jwat.common;
019
020import java.io.IOException;
021
022/**
023 * A small class to detect and count occurrences of either LF or CRLF pairs in
024 * a push back input stream, one method each. Implemented as an object instance
025 * so the caller can access the status information.
026 *
027 * A misplaced CR or LF is reported when the character is in the wrong order or
028 * simply not expected. Example LFCR has both CR and LF misplaced. If we are
029 * looking for LF's a CRLF pair has a misplaced CR.
030 *
031 * @author nicl
032 */
033public class NewlineParser {
034
035    /** Did the reader detect a missing CR while parsing newlines. */
036    public boolean bMissingCr = false;
037
038    /** Did the reader detect a missing LF while parsing newlines. */
039    public boolean bMissingLf = false;
040
041    /** Did the reader detect a misplaced CR while parsing newlines. */
042    public boolean bMisplacedCr = false;
043
044    /** Did the reader detect a misplaced LF while parsing newlines. */
045    public boolean bMisplacedLf = false;
046
047    /**
048     * Looks forward in the input stream and counts the number of newlines
049     * found. Non newlines characters are pushed back onto the input stream.
050     * @param in data input stream
051     * @param diagnostics object used to report errors and/or warnings
052     * @return newlines found in input stream
053     * @throws IOException if an error occurs while reading data
054     */
055    public int parseLFs(ByteCountingPushBackInputStream in, Diagnostics diagnostics) throws IOException {
056        bMissingCr = false;
057        bMissingLf = false;
058        bMisplacedCr = false;
059        bMisplacedLf = false;
060        int newlines = 0;
061        byte[] buffer = new byte[2];
062        boolean bLoop = true;
063        while (bLoop) {
064            int read = in.read(buffer);
065            switch (read) {
066            case 1:
067                if (buffer[0] == '\n') {
068                    ++newlines;
069                } else if ((buffer[0] == '\r')) {
070                    ++newlines;
071                    bMissingLf = true;
072                    bMisplacedCr = true;
073                } else {
074                    in.unread(buffer[0]);
075                    bLoop = false;
076                }
077                break;
078            case 2:
079                if (buffer[0] == '\n') {
080                    if (buffer[1] == '\n') {
081                        newlines += 2;
082                    } else  if (buffer[1] != '\r') {
083                        ++newlines;
084                        in.unread(buffer[1]);
085                    } else {
086                        ++newlines;
087                        bMisplacedCr = true;
088                    }
089                } else if (buffer[0] == '\r') {
090                    if (buffer[1] == '\n') {
091                        ++newlines;
092                        bMisplacedCr = true;
093                        bMisplacedLf = true;
094                    } else {
095                        ++newlines;
096                        bMisplacedCr = true;
097                        bMissingLf = true;
098                        in.unread(buffer[1]);
099                    }
100                } else {
101                    in.unread(buffer);
102                    bLoop = false;
103                }
104                break;
105            default:
106                bLoop = false;
107                break;
108            }
109        }
110        if (diagnostics != null) {
111            if (bMissingLf) {
112                diagnostics.addWarning(new Diagnosis(DiagnosisType.ERROR_EXPECTED,
113                        "Missing LF", "Sequence of LFs"));
114            }
115            if (bMisplacedCr) {
116                diagnostics.addWarning(new Diagnosis(DiagnosisType.ERROR_EXPECTED,
117                        "Misplaced CR", "Sequence of LFs"));
118            }
119            if (bMisplacedLf) {
120                diagnostics.addWarning(new Diagnosis(DiagnosisType.ERROR_EXPECTED,
121                        "Misplaced LF", "Sequence of LFs"));
122            }
123        }
124        return newlines;
125    }
126
127    /**
128     * Looks forward in the input stream and counts the number of newlines
129     * found. Non newlines characters are pushed back onto the input stream.
130     * @param in data input stream
131     * @param diagnostics object used to report errors and/or warnings
132     * @return newlines found in input stream
133     * @throws IOException if an error occurs while reading data
134     */
135    public int parseCRLFs(ByteCountingPushBackInputStream in, Diagnostics diagnostics) throws IOException {
136        bMissingCr = false;
137        bMissingLf = false;
138        bMisplacedCr = false;
139        bMisplacedLf = false;
140        int newlines = 0;
141        byte[] buffer = new byte[2];
142        boolean bLoop = true;
143        while (bLoop) {
144            int read = in.read(buffer);
145            // Switch on the number of bytes we were able to read (0-2).
146            switch (read) {
147            case 1:
148                if (buffer[0] == '\n') {
149                    ++newlines;
150                    bMissingCr = true;
151                } else if ((buffer[0] == '\r')) {
152                    ++newlines;
153                    bMissingLf = true;
154                } else {
155                    in.unread(buffer[0]);
156                    bLoop = false;
157                }
158                break;
159            case 2:
160                if (buffer[0] == '\r') {
161                    if (buffer[1] == '\n') {
162                        ++newlines;
163                    } else {
164                        ++newlines;
165                        bMissingLf = true;
166                        in.unread(buffer[1]);
167                    }
168                } else if (buffer[0] == '\n') {
169                    if (buffer[1] == '\r') {
170                        ++newlines;
171                        bMisplacedCr = true;
172                        bMisplacedLf = true;
173                    } else {
174                        ++newlines;
175                        bMissingCr = true;
176                        in.unread(buffer[1]);
177                    }
178                } else {
179                    in.unread(buffer);
180                    bLoop = false;
181                }
182                break;
183            default:
184                bLoop = false;
185                break;
186            }
187        }
188        if (diagnostics != null) {
189            if (bMissingCr) {
190                diagnostics.addWarning(new Diagnosis(DiagnosisType.ERROR_EXPECTED,
191                        "Missing CR", "Sequence of CRLFs"));
192            }
193            if (bMissingLf) {
194                diagnostics.addWarning(new Diagnosis(DiagnosisType.ERROR_EXPECTED,
195                        "Missing LF", "Sequence of CRLFs"));
196            }
197            if (bMisplacedCr) {
198                diagnostics.addWarning(new Diagnosis(DiagnosisType.ERROR_EXPECTED,
199                        "Misplaced CR", "Sequence of CRLFs"));
200            }
201            if (bMisplacedLf) {
202                diagnostics.addWarning(new Diagnosis(DiagnosisType.ERROR_EXPECTED,
203                        "Misplaced LF", "Sequence of CRLFs"));
204            }
205        }
206        return newlines;
207    }
208
209}