/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.runtime.client.admin;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TeiidOptionsUtil {
private static final String PREFIX = "OPTIONS (";
public static final String UUID = "UUID";
private static final char S_QUOTE = '\'';
private static final char D_QUOTE = '"';
private static final List< Character > INVALID_ID_CHARS = Arrays.asList( new Character[] {'/', ':', '[', ']', '|', '*'} ); // from JCR spec
private static boolean isValidIdentifierCharacter( final char c ) {
return !INVALID_ID_CHARS.contains( c );
}
public static String filterUuidsFromOptions(String ddl) throws Exception {
// find first index
StringBuilder sb = new StringBuilder();
int indexOfOptions = ddl.indexOf(PREFIX);
String remainingDdl = ddl;
while (indexOfOptions > -1 ) {
// Add everything prior to "OPTIONS ("
sb.append(remainingDdl.substring(0, indexOfOptions));
remainingDdl = remainingDdl.substring(indexOfOptions);
// Now parse all Options
Map< String, String > options = new HashMap<String, String>();
remainingDdl = parse(remainingDdl, options, true);
String filteredOptions = getOptionsClause(options, true);
if( filteredOptions != null ) {
sb.append(filteredOptions);
}
indexOfOptions = remainingDdl.indexOf(PREFIX);
}
sb.append(remainingDdl);
return sb.toString();
}
private static String getOptionsClause(Map<String, String> options, boolean removeUuid) {
StringBuilder sb = new StringBuilder();
sb.append(PREFIX);
int nValues = options.size();
int count = 0;
boolean hasOptions = false;
for( String key : options.keySet() ) {
// Skip UUID property
if( key.equalsIgnoreCase(UUID) ) continue;
sb.append(key).append(" ").append(options.get(key));
hasOptions = true;
count++;
if( count < nValues-1 ) sb.append(", ");
}
if( hasOptions ) return sb.toString();
return null;
}
protected static String parse( final String inputString,
final Map< String, String > options ) throws Exception {
return parse( inputString, options, false);
}
/**
* @param inputString
* the text being parsed (cannot be empty and must start with <code>OPTIONS (</code>
* @param options
* an empty map where the option/value pairs will be returned
* @return the remainder of the input string that is not part of the OPTIONS clause (can be empty)
* @throws Exception
* if an error occurs
*/
private static String parse( final String inputString,
final Map< String, String > options,
boolean keepQuotes ) throws Exception {
if (( inputString == null ) || !inputString.startsWith( PREFIX )) {
throw new Exception( "OPTIONS clause must start with " + PREFIX );
}
if (( options == null ) || !options.isEmpty()) {
throw new Exception( "Options map is null or not empty" );
}
String text = inputString.trim();
text = inputString.substring( PREFIX.length() );
if (text.isEmpty()) {
throw new Exception( "OPTIONS clause did not have closing paren" );
}
String[] optionResult = null;
String[] valueResult = null;
boolean keepGoing = true;
int i = -1;
do {
i = -1;
optionResult = parseIdentifier( text, keepQuotes );
valueResult = parseValue( optionResult[1], keepQuotes );
options.put( optionResult[0], valueResult[0] );
text = valueResult[1];
// remove whitespace and a comma in order to get to next option
// options must be separated by a comma
// end if find end of OPTIONS which is a right paren
for (final char c : text.toCharArray()) {
++i;
if (c == ')') {
keepGoing = false;
break;
}
if (c == ',') {
break;
}
if (!Character.isWhitespace( c )) {
throw new Exception( "OPTIONS clause need commas separating options" );
}
}
if (i == text.length() - 1) {
keepGoing = false;
} else {
text = text.substring( i + 1 );
}
} while (keepGoing);
// remove the ending right paren
if (text.charAt( i ) != ')') {
throw new Exception( "OPTIONS clause does not have ending right paren" );
}
return text.substring( ++i );
}
protected static String[] parseIdentifier( final String inputString ) throws Exception {
return parseIdentifier(inputString, false);
}
private static String[] parseIdentifier( final String inputString, boolean keepQuotes ) throws Exception {
if (( inputString == null ) || inputString.trim().isEmpty()) {
throw new Exception( "Cannot parse identifier when input string is empty" );
}
String remainder = inputString.trim();
String id = StringConstants.EMPTY_STRING;
boolean foundBeginningSingleQuote = false;
boolean foundEndingSingleQuote = false;
boolean foundBeginningDoubleQuote = false;
boolean foundEndingDoubleQuote = false;
int i = 0;
PARSING: for (final char c : remainder.toCharArray()) {
if (Character.isWhitespace( c )) {
if (!id.isEmpty()) {
++i;
break PARSING; // done parsing found identifier
}
} else if (!isValidIdentifierCharacter( c )) {
throw new Exception( "Option identifier has invalid character: " + c );
}
switch (c) {
case S_QUOTE:
if (foundBeginningSingleQuote) {
foundEndingSingleQuote = true;
id += c;
++i;
break PARSING; // done parsing found single quoted identifier
}
if (id.isEmpty() && !foundBeginningSingleQuote) {
foundBeginningSingleQuote = true;
id += c;
break;
}
throw new Exception( "Found single quote embedded in identifier" );
case D_QUOTE:
if (foundBeginningDoubleQuote) {
foundEndingDoubleQuote = true;
id += c;
++i;
break PARSING; // done parsing found double quoted identifier
}
if (id.isEmpty() && !foundBeginningDoubleQuote) {
foundBeginningDoubleQuote = true;
id += c;
break;
}
throw new Exception( "Found double quote embedded in identifier" );
default:
id += c;
break;
}
++i;
}
if (id.isEmpty()) {
throw new Exception( "No identifier found" );
}
if (foundBeginningSingleQuote && !foundEndingSingleQuote) {
throw new Exception( "Identifier does not have an ending single quote" );
}
if (foundBeginningDoubleQuote && !foundEndingDoubleQuote) {
throw new Exception( "Identifier does not have an ending double quote" );
}
if( keepQuotes ) {
if( foundBeginningSingleQuote && foundEndingSingleQuote ) {
return new String[] { S_QUOTE + id + S_QUOTE, remainder.substring( i )};
} else if( foundBeginningDoubleQuote && foundEndingDoubleQuote ) {
return new String[] { D_QUOTE + id + D_QUOTE, remainder.substring( i )};
}
}
return new String[] {id, remainder.substring( i )};
}
private static String[] parseValue( final String inputString ) throws Exception {
return parseValue( inputString, false );
}
private static String[] parseValue( final String inputString, boolean keepQuotes ) throws Exception {
if (( inputString == null ) || inputString.isEmpty()) {
throw new Exception( "Options value is null or empty" );
}
boolean foundSingleFirst = false;
boolean foundDoubleFirst = false;
// find first non-whitespace character
int startIndex = 0;
for (final char c : inputString.toCharArray()) {
if (Character.isWhitespace( c )) {
++startIndex;
continue;
}
break;
}
// value will either be single quoted or not have quotes
if (inputString.charAt( startIndex ) == '\'') {
String value = inputString.substring( startIndex ); // now find ending single quote
// walk through string to figure out where value ends
boolean foundDelimiter = false;
int numSingle = 0;
int numDouble = 0;
int i = 0;
for (final char c : value.toCharArray()) {
// only look at quoting
if (c == S_QUOTE) {
++numSingle;
if( foundDoubleFirst == false && foundSingleFirst == false ) {
foundSingleFirst = true;
}
// value can only end in a single quote
// make sure double quotes are matched
// look at next char to see if end of value
if (( value.length() - 1 ) == i && ( i != 0 )) {
++i;
break; // single quote is last character
}
final char nextChar = value.charAt( i + 1 );
if (Character.isWhitespace( nextChar ) || ( nextChar == ',' ) || ( nextChar == ')' )) {
if (( ( numSingle % 2 ) == 0 ) && ( ( numDouble % 2 ) == 0 )) {
++i;
foundDelimiter = true;
break; // have even number single and double quotes so have value
}
// else keep looking for ending single quote
}
} else if (c == D_QUOTE) {
++numDouble;
if( foundDoubleFirst == false && foundSingleFirst == false ) {
foundDoubleFirst = true;
}
}
++i;
}
int endIndex = ( i + startIndex );
String leftOver = null;
if (foundDelimiter) {
leftOver = inputString.substring( endIndex );
} else {
leftOver = StringConstants.EMPTY_STRING;
}
return new String[] {inputString.substring( startIndex, endIndex ), leftOver};
} else { // unquoted value
boolean foundDelimiter = false;
int endIndex = 0;
for (int i = startIndex; i < inputString.length(); ++i) {
char c = inputString.charAt( i );
// find delimiter
if (Character.isWhitespace( c ) || ( c == ',' ) || ( c == ')' )) {
endIndex = i;
foundDelimiter = true;
break; // found unquoted value
}
if (( c == '\'' ) || ( c == '"' )) {
throw new Exception( "Unquoted values cannot have quotes" );
}
endIndex = i;
}
String leftOver = null;
if (foundDelimiter) {
leftOver = inputString.substring( endIndex );
} else {
leftOver = StringConstants.EMPTY_STRING;
++endIndex;
}
if( keepQuotes ) {
if( foundSingleFirst ) {
return new String[] {S_QUOTE + inputString.substring( startIndex, endIndex ) + S_QUOTE, leftOver};
}
if( foundDoubleFirst ) {
return new String[] {D_QUOTE + inputString.substring( startIndex, endIndex ) + D_QUOTE, leftOver};
}
}
return new String[] {inputString.substring( startIndex, endIndex ), leftOver};
}
}
}