001    /**
002     * Copyright 2010-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community 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     * http://www.opensource.org/licenses/ecl2.php
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    package org.kuali.common.jdbc.reader;
017    
018    import java.io.BufferedReader;
019    import java.io.IOException;
020    import java.util.List;
021    
022    import org.apache.commons.lang3.StringUtils;
023    import org.kuali.common.jdbc.reader.model.Comments;
024    import org.kuali.common.jdbc.reader.model.Delimiter;
025    import org.kuali.common.jdbc.reader.model.DelimiterMode;
026    import org.kuali.common.jdbc.reader.model.LineSeparator;
027    import org.kuali.common.jdbc.sql.model.SqlMetaData;
028    import org.kuali.common.util.Assert;
029    
030    public final class DefaultSqlReader implements SqlReader {
031    
032            public DefaultSqlReader() {
033                    this(Delimiter.DEFAULT_DELIMITER, LineSeparator.DEFAULT_VALUE, DEFAULT_TRIM, Comments.DEFAULT_COMMENTS);
034            }
035    
036            public DefaultSqlReader(String delimiter) {
037                    this(new Delimiter(delimiter), LineSeparator.DEFAULT_VALUE, DEFAULT_TRIM, Comments.DEFAULT_COMMENTS);
038            }
039    
040            public DefaultSqlReader(DelimiterMode delimiterMode) {
041                    this(new Delimiter(delimiterMode), LineSeparator.DEFAULT_VALUE, DEFAULT_TRIM, Comments.DEFAULT_COMMENTS);
042            }
043    
044            public DefaultSqlReader(String delimiter, DelimiterMode delimiterMode) {
045                    this(new Delimiter(delimiter, delimiterMode), LineSeparator.DEFAULT_VALUE, DEFAULT_TRIM, Comments.DEFAULT_COMMENTS);
046            }
047    
048            public DefaultSqlReader(Delimiter delimiter, LineSeparator lineSeparator, boolean trim, Comments comments) {
049                    Assert.noNulls(delimiter, lineSeparator, comments);
050                    this.delimiter = delimiter;
051                    this.lineSeparator = lineSeparator;
052                    this.trim = trim;
053                    this.comments = comments;
054                    this.lineSeparatorLength = this.lineSeparator.getValue().length();
055                    this.delimiterLength = delimiter.getValue().length();
056            }
057    
058            public static final boolean DEFAULT_TRIM = true;
059    
060            private final Delimiter delimiter;
061            private final LineSeparator lineSeparator;
062            private final boolean trim;
063            private final Comments comments;
064            private final int lineSeparatorLength;
065            private final int delimiterLength;
066    
067            /**
068             * Extract one complete SQL statement from the BufferedReader. Return <code>null</code> after all SQL statements have been read.
069             */
070            @Override
071            public String getSql(BufferedReader reader) throws IOException {
072                    // Extract one line of text from the file
073                    String line = reader.readLine();
074    
075                    // Trim all whitespace
076                    String trimmedLine = StringUtils.trimToNull(line);
077    
078                    // Begin a new SQL statement
079                    StringBuilder sb = new StringBuilder();
080    
081                    // Iterate until we have exhausted the BufferedReader
082                    while (line != null) {
083    
084                            // Examine the trimmed line to determine if we have hit the end of a SQL statement
085                            // The only methods used to determine the end of a SQL statement are
086                            // 1 - the delimiter being on a line all on it's own with nothing else but whitespace
087                            // 2 - the delimiter being at the end of a line after whitespace is trimmed off
088                            if (isEndOfSqlStatement(trimmedLine, delimiter)) {
089                                    // We hit the end of a SQL statement, return what we've got so far
090                                    return getReturnValue(sb.toString() + trimmedLine, trim, lineSeparator);
091                            }
092    
093                            // If this is a comment (and we are ignoring comments) skip this line
094                            if (!ignore(comments, sb, trimmedLine)) {
095                                    // Otherwise append the line and add back in the line separator that was removed by readLine()
096                                    sb.append(line + lineSeparator.getValue());
097                            }
098    
099                            // Read another line of text from the file
100                            line = reader.readLine();
101    
102                            // Trim all whitespace
103                            trimmedLine = StringUtils.trimToNull(line);
104                    }
105    
106                    // There might be SQL at the end of the file
107                    // The trailing SQL might not be terminated by the delimiter
108                    String result = getReturnValue(sb.toString(), trim, lineSeparator);
109    
110                    if (result == null) {
111                            // If there is no SQL at the end, return null
112                            return null;
113                    } else {
114                            // Otherwise return the final SQL statement
115                            return result;
116                    }
117            }
118    
119            /**
120             * Calculate total number of SQL statements + aggregate size
121             */
122            @Override
123            public SqlMetaData getMetaData(BufferedReader reader) throws IOException {
124                    long count = 0; // Track number of individual SQL statements
125                    long size = 0; // Track overall size of the combined SQL statements
126    
127                    // Read a line of text from the file
128                    String line = reader.readLine();
129    
130                    // Trim all whitespace
131                    String trimmedLine = StringUtils.trimToNull(line);
132    
133                    // Iterate until we have exhausted the BufferedReader
134                    while (line != null) {
135    
136                            // Add the length of the current line to the overall size total
137                            size += line.length();
138    
139                            // If this line terminates the SQL statement, increment the overall count
140                            if (isEndOfSqlStatement(trimmedLine, delimiter)) {
141                                    count++;
142                            }
143    
144                            // Read the next line from the BufferedReader
145                            line = reader.readLine();
146    
147                            // Trim all whitespace
148                            trimmedLine = StringUtils.trimToNull(line);
149                    }
150    
151                    // Return total count and overall size
152                    return new SqlMetaData(count, size);
153            }
154    
155            protected String getReturnValue(String sql, boolean trim, LineSeparator lineSeparator) {
156    
157                    // If the SQL ends with the delimiter, remove it
158                    if (StringUtils.endsWith(sql, delimiter.getValue())) {
159                            int endIndex = sql.length() - delimiterLength;
160                            sql = StringUtils.substring(sql, 0, endIndex);
161                    }
162    
163                    // Trim all whitespace on either side of the SQL statement
164                    if (trim) {
165                            sql = StringUtils.trimToNull(sql);
166                    }
167    
168                    if (sql == null) {
169                            // If the SQL is nothing but whitespace, return null
170                            return null;
171                    } else if (StringUtils.endsWith(sql, lineSeparator.getValue())) {
172                            // If the SQL ends with the line separator, remove it
173                            int endIndex = sql.length() - lineSeparatorLength;
174                            return StringUtils.substring(sql, 0, endIndex);
175                    } else {
176                            // Otherwise return the SQL as is
177                            return sql;
178                    }
179            }
180    
181            protected boolean isEndOfSqlStatement(String trimmedLine, Delimiter delimiter) {
182                    switch (delimiter.getMode()) {
183                    case END_OF_LINE:
184                            return StringUtils.endsWith(trimmedLine, delimiter.getValue());
185                    case OWN_LINE:
186                            return StringUtils.equals(trimmedLine, delimiter.getValue());
187                    default:
188                            throw new IllegalArgumentException("Delimiter mode [" + delimiter.getMode() + "] is unknown");
189                    }
190            }
191    
192            protected boolean proceed(String line, String trimmedLine, Delimiter delimiter) {
193                    if (line == null) {
194                            return false;
195                    }
196                    boolean endOfSqlStatement = isEndOfSqlStatement(trimmedLine, delimiter);
197                    return !endOfSqlStatement;
198            }
199    
200            protected boolean ignore(Comments comments, StringBuilder sql, String trimmedLine) {
201                    if (!comments.isIgnore()) {
202                            return false;
203                    }
204                    if (!StringUtils.isBlank(sql.toString())) {
205                            return false;
206                    }
207                    return isSqlComment(trimmedLine, comments.getTokens());
208            }
209    
210            protected boolean isSqlComment(String trimmedLine, List<String> commentTokens) {
211                    for (String commentToken : commentTokens) {
212                            if (StringUtils.startsWith(trimmedLine, commentToken)) {
213                                    return true;
214                            }
215                    }
216                    return false;
217            }
218    
219            public Delimiter getDelimiter() {
220                    return delimiter;
221            }
222    
223            public LineSeparator getLineSeparator() {
224                    return lineSeparator;
225            }
226    
227            public boolean isTrim() {
228                    return trim;
229            }
230    
231            public Comments getComments() {
232                    return comments;
233            }
234    
235    }