package net.sourceforge.mayfly.parser; import net.sourceforge.mayfly.MayflyInternalException; import net.sourceforge.mayfly.util.ImmutableByteArray; import org.apache.commons.lang.StringEscapeUtils; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; public class Substitutor { public static List substitute(List tokens, List parameters) { List result = new ArrayList(); Iterator parameterIterator = parameters.iterator(); for (Iterator tokenIterator = tokens.iterator(); tokenIterator.hasNext();) { Token token = (Token) tokenIterator.next(); if (token.type == TokenType.PARAMETER) { Object value = parameterIterator.next(); result.addAll(tokensFromValue(value, token)); } else { result.add(token); } } return result; } public static int parameterCount(List tokens) { int count = 0; for (Iterator iter = tokens.iterator(); iter.hasNext();) { Token token = (Token) iter.next(); if (token.type == TokenType.PARAMETER) { ++count; } } return count; } private static Token tokenFromValue(Object value, Token oldToken) { if (value instanceof Number) { Number numberValue = (Number) value; return new TextToken(TokenType.NUMBER, numberValue.toString(), oldToken); } else if (value instanceof String) { String stringValue = (String) value; return new TextToken(TokenType.QUOTED_STRING, "'" + StringEscapeUtils.escapeSql(stringValue) + "'", oldToken); } else if (value instanceof ImmutableByteArray) { ImmutableByteArray binaryValue = (ImmutableByteArray) value; return new BinaryToken(binaryValue, oldToken.location); } else if (value == null) { return new TextToken(TokenType.KEYWORD_null, "null", oldToken); } else { throw new MayflyInternalException( "Don't know how to substitute a " + value.getClass().getName()); } } private static List tokensFromValue(Object value, Token oldToken) { if (value instanceof BigDecimal) { /* Here we pay the price for wanting to be able to someday do MySQL-style token-pasting games. If a parameter were like in (most) other SQL dialects, we'd be doing the whole thing at the Literal level instead, and avoiding this silliness. */ List result = new ArrayList(); BigDecimal decimal = (BigDecimal) value; if (decimal.signum() == -1) { result.add(new TextToken(TokenType.MINUS, "-", oldToken)); } decimal = decimal.abs(); BigInteger integerPart = decimal.toBigInteger(); result.add(new TextToken(TokenType.NUMBER, integerPart.toString(), oldToken)); BigDecimal fractionalPart = decimal.subtract( new BigDecimal(integerPart.toString())); result.add(new TextToken(TokenType.PERIOD, ".", oldToken)); String fractionalString = fractionalPart.toString(); if (!fractionalString.startsWith("0.")) { throw new MayflyInternalException( "Lost in number un-parsing, " + "fractional string was " + fractionalString, oldToken.location); } result.add(new TextToken(TokenType.NUMBER, fractionalString.substring(2), oldToken)); return Collections.unmodifiableList(result); } else { return Collections.singletonList(tokenFromValue(value, oldToken)); } } }