001/*
002 * Copyright 2006-2007 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.batch.item.file.separator;
018
019import org.springframework.util.StringUtils;
020
021/**
022 * A {@link RecordSeparatorPolicy} that treats all lines as record endings, as
023 * long as they do not have unterminated quotes, and do not end in a
024 * continuation marker.
025 * 
026 * @author Dave Syer
027 * 
028 */
029public class DefaultRecordSeparatorPolicy extends SimpleRecordSeparatorPolicy {
030
031        private static final String QUOTE = "\"";
032
033        private static final String CONTINUATION = "\\";
034
035        private String quoteCharacter = QUOTE;
036
037        private String continuation = CONTINUATION;
038
039        /**
040         * Default constructor.
041         */
042        public DefaultRecordSeparatorPolicy() {
043                this(QUOTE, CONTINUATION);
044        }
045
046        /**
047         * Convenient constructor with quote character as parameter.
048         *
049         * @param quoteCharacter value used to indicate a quoted string
050         */
051        public DefaultRecordSeparatorPolicy(String quoteCharacter) {
052                this(quoteCharacter, CONTINUATION);
053        }
054
055        /**
056         * Convenient constructor with quote character and continuation marker as
057         * parameters.
058         *
059         * @param quoteCharacter value used to indicate a quoted string
060         * @param continuation value used to indicate a line continuation
061         */
062        public DefaultRecordSeparatorPolicy(String quoteCharacter, String continuation) {
063                super();
064                this.continuation = continuation;
065                this.quoteCharacter = quoteCharacter;
066        }
067
068        /**
069         * Public setter for the quoteCharacter. Defaults to double quote mark.
070         * 
071         * @param quoteCharacter the quoteCharacter to set
072         */
073        public void setQuoteCharacter(String quoteCharacter) {
074                this.quoteCharacter = quoteCharacter;
075        }
076
077        /**
078         * Public setter for the continuation. Defaults to back slash.
079         * 
080         * @param continuation the continuation to set
081         */
082        public void setContinuation(String continuation) {
083                this.continuation = continuation;
084        }
085
086        /**
087         * Return true if the line does not have unterminated quotes (delimited by
088         * "), and does not end with a continuation marker ('\'). The test for the
089         * continuation marker ignores whitespace at the end of the line.
090         * 
091         * @see org.springframework.batch.item.file.separator.RecordSeparatorPolicy#isEndOfRecord(java.lang.String)
092         */
093    @Override
094        public boolean isEndOfRecord(String line) {
095                return !isQuoteUnterminated(line) && !isContinued(line);
096        }
097
098        /**
099         * If we are in an unterminated quote, add a line separator. Otherwise
100         * remove the continuation marker (plus whitespace at the end) if it is
101         * there.
102         * 
103         * @see org.springframework.batch.item.file.separator.SimpleRecordSeparatorPolicy#preProcess(java.lang.String)
104         */
105    @Override
106        public String preProcess(String line) {
107                if (isQuoteUnterminated(line)) {
108                        return line + "\n";
109                }
110                if (isContinued(line)) {
111                        return line.substring(0, line.lastIndexOf(continuation));
112                }
113                return line;
114        }
115
116        /**
117         * Determine if the current line (or buffered concatenation of lines)
118         * contains an unterminated quote, indicating that the record is continuing
119         * onto the next line.
120         * 
121         * @param line
122         * @return
123         */
124        private boolean isQuoteUnterminated(String line) {
125                return StringUtils.countOccurrencesOf(line, quoteCharacter) % 2 != 0;
126        }
127
128        /**
129         * Determine if the current line (or buffered concatenation of lines) ends
130         * with the continuation marker, indicating that the record is continuing
131         * onto the next line.
132         * 
133         * @param line
134         * @return
135         */
136        private boolean isContinued(String line) {
137                if (line == null) {
138                        return false;
139                }
140                return line.trim().endsWith(continuation);
141        }
142}