package net.naonedbus.sql;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/**
* LiteScript is a very simple script parser for SQL files. <br />
* It provides conditional block that allow you to executes queries depending on
* the database old and new versions.
*
* @author Romain Guefveneu
* @version 1.0
*/
public class LiteScript {
private static final String COMMENT_TOKEN = "--";
private static final String CONDITION_TOKEN = "IF";
private static final String BLOCK_END_TOKEN = "END";
private static final String QUERY_END_TOKEN = ";";
private static final String OLD_VERSION_TOKEN = "OLD_VERSION";
private static final String NEW_VERSION_TOKEN = "NEW_VERSION";
private static final String GREATER_THAN = ">";
private static final String LESSER_THAN = "<";
private static final String EQUALS = "=";
final StringBuilder mStringBuilder;
private int mOldVersion;
private int mNewVersion;
public LiteScript() {
mStringBuilder = new StringBuilder();
}
/**
* @param newVersion
* The new version of the database
*/
public void setOldVersion(final int oldVersion) {
mOldVersion = oldVersion;
}
/**
* @param newVersion
* The new version of the database
*/
public void setNewVersion(final int newVersion) {
mNewVersion = newVersion;
}
/**
* Get queries that matches the corresponding versions.
*
* @param inputStream
* the stream to read
* @return a list of sql queries
* @throws IOException
*/
public List<String> getQueries(final InputStream inputStream) throws IOException {
final BufferedReader buffer = new BufferedReader(new InputStreamReader(inputStream));
final List<String> queries = new ArrayList<String>();
String line;
while ((line = buffer.readLine()) != null) {
line = line.trim();
readLine(buffer, line, queries);
}
buffer.close();
return queries;
}
/**
* Read a line. <br />
* If the line is not a complete sql query (i.e. not ending with a
* semi-colon), it will be added to {@link #mStringBuilder}.
*
* @param buffer
* the script buffer
* @param line
* the line to read
* @param queries
* the list of previous queries
* @throws IOException
*/
private void readLine(final BufferedReader buffer, final String line, final List<String> queries)
throws IOException {
if (line.length() == 0)
return;
if (line.startsWith(COMMENT_TOKEN))
return;
if (line.startsWith(CONDITION_TOKEN)) {
readConditionalBlock(buffer, line, queries);
} else if (!line.endsWith(QUERY_END_TOKEN)) {
mStringBuilder.append(line).append(' ');
} else {
if (mStringBuilder.length() > 0) {
mStringBuilder.append(line);
queries.add(mStringBuilder.toString());
} else {
queries.add(line);
}
mStringBuilder.setLength(0);
}
}
/**
* Read a conditional block. <br />
* If the condition is {@code true}, the block queries will be added to the
* query list.
*
* @param buffer
* the script buffer
* @param condition
* the condition line to evaluate
* @param queries
* the list of previous queries
* @throws IOException
*/
private void readConditionalBlock(final BufferedReader buffer, final String condition, final List<String> queries)
throws IOException {
String line;
final boolean isTrue = eval(condition);
while ((line = buffer.readLine()) != null) {
line = line.trim();
if (line.length() == 0)
continue;
if (line.startsWith(BLOCK_END_TOKEN))
break;
if (isTrue)
readLine(buffer, line, queries);
}
}
/**
* Evaluate an expression. <br />
* The expression must folow this pattern :
*
* <pre>
* IF [OLD_VERSION|NEW_VERSION] [>|<|=] [0-9*]
* </pre>
*
* @param expression
* the expression to evaluate
* @return {@code true} if the expression is true, {@code false} otherwise.
*/
private boolean eval(final String expression) {
final String[] tokens = fastSplit(expression, ' ');
final String token = tokens[1];
final String operator = tokens[2];
final int compareTo = Integer.valueOf(tokens[3]);
final int compareFrom;
if (OLD_VERSION_TOKEN.equals(token))
compareFrom = mOldVersion;
else if (NEW_VERSION_TOKEN.equals(token))
compareFrom = mNewVersion;
else
compareFrom = compareTo;
if (GREATER_THAN.equals(operator))
return compareFrom > compareTo;
else if (EQUALS.equals(operator))
return compareFrom == compareTo;
else if (LESSER_THAN.equals(operator))
return compareFrom < compareTo;
else
return false;
}
/**
* Split a string.
*
* @param string
* The string to split
* @param delimiter
* the delimiting char
* @return the array of strings computed by splitting this string around
* matches of the given regular expression
*/
private static String[] fastSplit(final String string, final char delimiter) {
final String[] temp = new String[string.length() / 2];
int wordCount = 0;
int i = 0;
int j = string.indexOf(delimiter); // First substring
while (j >= 0) {
temp[wordCount++] = string.substring(i, j);
i = j + 1;
j = string.indexOf(delimiter, i); // Rest of substrings
}
temp[wordCount++] = string.substring(i); // Last substring
final String[] result = new String[wordCount];
System.arraycopy(temp, 0, result, 0, wordCount);
return result;
}
}