/*
* Copyright © Red Gate Software Ltd 2010-2020
*
* 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.flywaydb.core.internal.parser;
import org.flywaydb.core.internal.sqlscript.Delimiter;
import java.io.FilterReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
public class PeekingReader extends FilterReader {
private int[] peekBuffer = new int[256];
private int peekMax = 0;
private int peekBufferOffset = 0;
private boolean supportsPeekingMultipleLines = true;
PeekingReader(Reader in, boolean supportsPeekingMultipleLines) {
super(in);
this.supportsPeekingMultipleLines = supportsPeekingMultipleLines;
}
@Override
public int read() throws IOException {
peekBufferOffset++;
return super.read();
}
Swallows the next character.
/**
* Swallows the next character.
*/
public void swallow() throws IOException {
//noinspection ResultOfMethodCallIgnored
read();
}
Swallows the next n characters.
/**
* Swallows the next n characters.
*/
public void swallow(int n) throws IOException {
for (int i = 0; i < n; i++) {
//noinspection ResultOfMethodCallIgnored
read();
}
}
private int peek() throws IOException {
if (peekBufferOffset >= peekMax) {
refillPeekBuffer();
}
return peekBuffer[peekBufferOffset];
}
private void refillPeekBuffer() throws IOException {
mark(peekBuffer.length);
peekMax = peekBuffer.length;
peekBufferOffset = 0;
for (int i = 0; i < peekBuffer.length; i++) {
int read = super.read();
peekBuffer[i] = read;
if (!supportsPeekingMultipleLines && read == '\n') {
peekMax = i;
break;
}
}
reset();
}
Peek ahead in the stream to see if the next character matches this one.
Params: - c – The character to match.
Returns: true
if it does, false
if not.
/**
* Peek ahead in the stream to see if the next character matches this one.
*
* @param c The character to match.
* @return {@code true} if it does, {@code false} if not.
*/
public boolean peek(char c) throws IOException {
int r = peek();
return r != -1 && c == (char) r;
}
Peek ahead in the stream to see if the next character matches either of these.
Params: - c1 – The first character to match.
- c2 – The second character to match.
Returns: true
if it does, false
if not.
/**
* Peek ahead in the stream to see if the next character matches either of these.
*
* @param c1 The first character to match.
* @param c2 The second character to match.
* @return {@code true} if it does, {@code false} if not.
*/
public boolean peek(char c1, char c2) throws IOException {
int r = peek();
return r != -1 && (c1 == (char) r || c2 == (char) r);
}
Peek ahead in the stream to see if the next character is numeric.
Returns: true
if it is, false
if not.
/**
* Peek ahead in the stream to see if the next character is numeric.
*
* @return {@code true} if it is, {@code false} if not.
*/
public boolean peekNumeric() throws IOException {
int r = peek();
return isNumeric(r);
}
private boolean isNumeric(int r) {
return r != -1 && (char) r >= '0' && (char) r <= '9';
}
Peek ahead in the stream to see if the next character is whitespace.
Returns: true
if it is, false
if not.
/**
* Peek ahead in the stream to see if the next character is whitespace.
*
* @return {@code true} if it is, {@code false} if not.
*/
public boolean peekWhitespace() throws IOException {
int r = peek();
return isWhitespace(r);
}
private boolean isWhitespace(int r) {
return r != -1 && Character.isWhitespace((char) r);
}
Peek ahead in the stream to see if the next character could be a character part of a keyword or identifier.
Returns: true
if it is, false
if not.
/**
* Peek ahead in the stream to see if the next character could be a character part of a keyword or identifier.
*
* @return {@code true} if it is, {@code false} if not.
*/
public boolean peekKeywordPart(ParserContext context) throws IOException {
int r = peek();
return isKeywordPart(r, context);
}
private boolean isKeywordPart(int r, ParserContext context) {
return r != -1 && ((char) r == '_' || (char) r == '$' || Character.isLetterOrDigit((char) r) || context.isLetter((char)r));
}
Peek ahead in the stream to see if the next characters match this string exactly.
Params: - str – The string to match.
Returns: true
if they do, false
if not.
/**
* Peek ahead in the stream to see if the next characters match this string exactly.
*
* @param str The string to match.
* @return {@code true} if they do, {@code false} if not.
*/
public boolean peek(String str) throws IOException {
return str.equals(peek(str.length()));
}
Peek ahead in the stream to look at this number of characters ahead in the reader.
Params: - numChars – The number of characters.
Returns: The characters.
/**
* Peek ahead in the stream to look at this number of characters ahead in the reader.
*
* @param numChars The number of characters.
* @return The characters.
*/
public String peek(int numChars) throws IOException {
return peek(numChars, false);
}
Peek ahead in the stream to look at this number of characters ahead in the reader.
Params: - numChars – The number of characters.
- peekMultipleLines – Whether the peek should go across lines or not
Returns: The characters.
/**
* Peek ahead in the stream to look at this number of characters ahead in the reader.
*
* @param numChars The number of characters.
* @param peekMultipleLines Whether the peek should go across lines or not
* @return The characters.
*/
public String peek(int numChars, boolean peekMultipleLines) throws IOException {
// If we need to peek beyond the physical size of the peek buffer - eg. we have encountered a very
// long string literal - then expand the buffer to be big enough to contain it.
if (numChars >= peekBuffer.length) {
resizePeekBuffer(numChars);
}
if (peekBufferOffset + numChars >= peekMax) {
refillPeekBuffer();
}
StringBuilder result = new StringBuilder();
int prevR = -1;
for (int i = 0; i < numChars; i++) {
int r = peekBuffer[peekBufferOffset + i];
if (r == -1) {
break;
} else if (peekBufferOffset + i > peekMax) {
break;
} else if (!peekMultipleLines && prevR == '\n') {
break;
}
result.append((char) r);
prevR = r;
}
if (result.length() == 0) {
return null;
}
return result.toString();
}
Return the next non-whitespace character
Returns: The character
/**
* Return the next non-whitespace character
* @return The character
*/
public char peekNextNonWhitespace() throws IOException {
int i = 1;
String c = peek(i++, true);
String lastc = c;
while (c.trim().isEmpty()) {
c = peek(i++, true);
if (c.equals(lastc)) {
// if the peek is the same as the last peek, then dont loop forever, even if its still empty.
break;
}
lastc = c;
}
return c.charAt(c.length()-1);
}
private void resizePeekBuffer(int newSize) {
peekBuffer = Arrays.copyOf(peekBuffer, newSize + peekBufferOffset);
}
Swallows all characters in this stream until any of these delimiting characters has been encountered.
Params: - delimiter1 – The first delimiting character.
- delimiter2 – The second delimiting character.
/**
* Swallows all characters in this stream until any of these delimiting characters has been encountered.
*
* @param delimiter1 The first delimiting character.
* @param delimiter2 The second delimiting character.
*/
public void swallowUntilExcluding(char delimiter1, char delimiter2) throws IOException {
do {
if (peek(delimiter1, delimiter2)) {
break;
}
int r = read();
if (r == -1) {
break;
}
} while (true);
}
Reads all characters in this stream until any of these delimiting characters has been encountered.
Params: - delimiter1 – The first delimiting character.
- delimiter2 – The second delimiting character.
Returns: The string read, without the delimiting characters.
/**
* Reads all characters in this stream until any of these delimiting characters has been encountered.
*
* @param delimiter1 The first delimiting character.
* @param delimiter2 The second delimiting character.
* @return The string read, without the delimiting characters.
*/
public String readUntilExcluding(char delimiter1, char delimiter2) throws IOException {
StringBuilder result = new StringBuilder();
do {
if (peek(delimiter1, delimiter2)) {
break;
}
int r = read();
if (r == -1) {
break;
} else {
result.append((char) r);
}
} while (true);
return result.toString();
}
Swallows all characters in this stream until this delimiting character has been encountered, taking into account
this escape character for the delimiting character.
Params: - delimiter – The delimiting character.
- selfEscape – Whether the delimiter can escape itself by being present twice.
/**
* Swallows all characters in this stream until this delimiting character has been encountered, taking into account
* this escape character for the delimiting character.
*
* @param delimiter The delimiting character.
* @param selfEscape Whether the delimiter can escape itself by being present twice.
*/
public void swallowUntilExcludingWithEscape(char delimiter, boolean selfEscape) throws IOException {
swallowUntilExcludingWithEscape(delimiter, selfEscape, (char) 0);
}
Swallows all characters in this stream until this delimiting character has been encountered, taking into account
this escape character for the delimiting character.
Params: - delimiter – The delimiting character.
- selfEscape – Whether the delimiter can escape itself by being present twice.
- escape – A separate escape character.
/**
* Swallows all characters in this stream until this delimiting character has been encountered, taking into account
* this escape character for the delimiting character.
*
* @param delimiter The delimiting character.
* @param selfEscape Whether the delimiter can escape itself by being present twice.
* @param escape A separate escape character.
*/
public void swallowUntilExcludingWithEscape(char delimiter, boolean selfEscape, char escape) throws IOException {
do {
int r = read();
if (r == -1) {
break;
}
char c = (char) r;
if (escape != 0 && c == escape) {
swallow();
continue;
}
if (c == delimiter) {
if (selfEscape && peek(delimiter)) {
swallow();
continue;
}
break;
}
} while (true);
}
Reads all characters in this stream until this delimiting character has been encountered, taking into account
this escape character for the delimiting character.
Params: - delimiter – The delimiting character.
- selfEscape – Whether the delimiter can escape itself by being present twice.
Returns: The string read, without the delimiting character.
/**
* Reads all characters in this stream until this delimiting character has been encountered, taking into account
* this escape character for the delimiting character.
*
* @param delimiter The delimiting character.
* @param selfEscape Whether the delimiter can escape itself by being present twice.
* @return The string read, without the delimiting character.
*/
public String readUntilExcludingWithEscape(char delimiter, boolean selfEscape) throws IOException {
return readUntilExcludingWithEscape(delimiter, selfEscape, (char) 0);
}
Reads all characters in this stream until this delimiting character has been encountered, taking into account
this escape character for the delimiting character.
Params: - delimiter – The delimiting character.
- selfEscape – Whether the delimiter can escape itself by being present twice.
- escape – A separate escape character.
Returns: The string read, without the delimiting character.
/**
* Reads all characters in this stream until this delimiting character has been encountered, taking into account
* this escape character for the delimiting character.
*
* @param delimiter The delimiting character.
* @param selfEscape Whether the delimiter can escape itself by being present twice.
* @param escape A separate escape character.
* @return The string read, without the delimiting character.
*/
public String readUntilExcludingWithEscape(char delimiter, boolean selfEscape, char escape) throws IOException {
StringBuilder result = new StringBuilder();
do {
int r = read();
if (r == -1) {
break;
}
char c = (char) r;
if (escape != 0 && c == escape) {
int r2 = read();
if (r2 == -1) {
result.append(escape);
break;
}
char c2 = (char) r2;
result.append(c2);
continue;
}
if (c == delimiter) {
if (selfEscape && peek(delimiter)) {
result.append(delimiter);
continue;
}
break;
}
result.append(c);
} while (true);
return result.toString();
}
Swallows all characters in this stream until this delimiting string has been encountered.
Params: - str – The delimiting string.
/**
* Swallows all characters in this stream until this delimiting string has been encountered.
*
* @param str The delimiting string.
*/
public void swallowUntilExcluding(String str) throws IOException {
do {
if (peek(str)) {
break;
}
int r = read();
if (r == -1) {
break;
}
} while (true);
}
Reads all characters in this stream until any of the delimiting strings is encountered.
Params: - strings – The delimiting strings.
Returns: The string read, without the delimiting string.
/**
* Reads all characters in this stream until any of the delimiting strings is encountered.
*
* @param strings The delimiting strings.
* @return The string read, without the delimiting string.
*/
public String readUntilExcluding(String... strings) throws IOException {
StringBuilder result = new StringBuilder();
do {
for (String str : strings) {
if (peek(str)) {
return result.toString();
}
}
int r = read();
if (r == -1) {
break;
} else {
result.append((char) r);
}
} while (true);
return result.toString();
}
Reads all characters in this stream until any of this delimiting character has been encountered.
Params: - delimiter – The delimiting character.
Returns: The string read, including the delimiting characters.
/**
* Reads all characters in this stream until any of this delimiting character has been encountered.
*
* @param delimiter The delimiting character.
* @return The string read, including the delimiting characters.
*/
public String readUntilIncluding(char delimiter) throws IOException {
StringBuilder result = new StringBuilder();
do {
int r = read();
if (r == -1) {
break;
}
char c = (char) r;
result.append(c);
if (c == delimiter) {
break;
}
} while (true);
return result.toString();
}
Reads all characters in this stream until the delimiting sequence is encountered.
Params: - delimiterSequence – The delimiting sequence.
Returns: The string read, including the delimiting characters.
/**
* Reads all characters in this stream until the delimiting sequence is encountered.
*
* @param delimiterSequence The delimiting sequence.
* @return The string read, including the delimiting characters.
*/
public String readUntilIncluding(String delimiterSequence) throws IOException {
StringBuilder result = new StringBuilder();
do {
int r = read();
if (r == -1) {
break;
}
char c = (char) r;
result.append(c);
if (result.toString().endsWith(delimiterSequence)) {
break;
}
} while (true);
return result.toString();
}
Reads all characters in this stream as long as they can be part of a keyword.
Params: - delimiter – The current delimiter.
Returns: The string read.
/**
* Reads all characters in this stream as long as they can be part of a keyword.
*
* @param delimiter The current delimiter.
* @return The string read.
*/
public String readKeywordPart(Delimiter delimiter, ParserContext context) throws IOException {
StringBuilder result = new StringBuilder();
do {
if ((delimiter == null || !peek(delimiter.getDelimiter())) && peekKeywordPart(context)) {
result.append((char) read());
} else {
break;
}
} while (true);
return result.toString();
}
Swallows all characters in this stream as long as they can be part of a numeric constant.
/**
* Swallows all characters in this stream as long as they can be part of a numeric constant.
*/
public void swallowNumeric() throws IOException {
do {
if (!peekNumeric()) {
return;
}
swallow();
} while (true);
}
Reads all characters in this stream as long as they can be part of a numeric constant.
Returns: The string read.
/**
* Reads all characters in this stream as long as they can be part of a numeric constant.
*
* @return The string read.
*/
public String readNumeric() throws IOException {
StringBuilder result = new StringBuilder();
do {
if (peekNumeric()) {
result.append((char) read());
} else {
break;
}
} while (true);
return result.toString();
}
Reads all characters in this stream as long as they are whitespace.
Returns: The string read.
/**
* Reads all characters in this stream as long as they are whitespace.
*
* @return The string read.
*/
public String readWhitespace() throws IOException {
StringBuilder result = new StringBuilder();
do {
if (peekWhitespace()) {
result.append((char) read());
} else {
break;
}
} while (true);
return result.toString();
}
}