package org.simpleflatmapper.jdbc.named; import java.util.ArrayList; import java.util.List; public final class NamedSqlQueryParser { final List<Symbol> symbols = new ArrayList<Symbol>(10); final List<TupleBuilder> tuples = new ArrayList<TupleBuilder>(); int lastMarkStart = -1; int lastMarkEnd = -1; private final Callback callback; public NamedSqlQueryParser(Callback callback) { this.callback = callback; } public void parse(CharSequence cs) { for(int i = 0; i < cs.length(); i++) { char c = cs.charAt(i); processChar(cs, i, c); } if(lastMarkStart != -1) { processSymbol(cs, cs.length()); } } private void processChar(CharSequence cs, int i, char c) { if (c == '(') { openFunction(cs, i); } else if (c == ')') { closeFunction(cs, i); } else if (isSpaceOrSymbol(c)) { if (lastMarkStart != -1 && lastMarkEnd == -1) { lastMarkEnd = i; } } else { if(lastMarkEnd != -1) { processSymbol(cs, i); } if (lastMarkStart == -1) { lastMarkStart = i; } } } private void closeFunction(CharSequence cs, int i) { if (lastMarkStart != -1) { processSymbol(cs, i); } // end of tuples TupleBuilder topTuple = tuples.remove(this.tuples.size() - 1); Tuple tuple = topTuple.toTuple(i + 1); if (tuples.isEmpty()) { symbols.add(tuple); } else { tuples.get(this.tuples.size() - 1).add(tuple); } } private void openFunction(CharSequence cs, int i) { // new tuples Symbol word = consumeSymbol(cs, i); int start = word.getPosition().getStart(); if (start == -1) { start = i; } tuples.add(new TupleBuilder(word, start)); } private void processSymbol(CharSequence cs, int length) { Symbol symbol = consumeSymbol(cs, length); if (tuples.isEmpty()) { symbols.add(symbol); } else { tuples.get(this.tuples.size() - 1).add(symbol); } } private boolean isSpaceOrSymbol(char c) { return c == ' ' || c == '=' || c == '<' || c == '>' || c == '!' || c == ','; } private Symbol consumeSymbol(CharSequence cs, int i) { String name = getName(cs, i); Position position = consumePosition(); if (name.startsWith(":")) { NamedParameter namedParameter = new NamedParameter(name.substring(1), position); callback.param(namedParameter); return namedParameter; } else if (name.equals("?")) { Parameter parameter = new Parameter(position); Word extrapolateName = findName(parameter); callback.param(new NamedParameter(extrapolateName.getName(), position)); return parameter; } return new Word(name, position); } private Word findName(Parameter parameter) { if (symbols.isEmpty()) { throw new IllegalArgumentException("Cannot find name for ? at " + parameter.getPosition().getStart()); } if (!tuples.isEmpty()) { Symbol s = symbols.get(symbols.size() - 1); if (s instanceof Tuple) { Word w = lookForArg(0, (Tuple)s); if (w != null) { return w; } } } for(int i = symbols.size() - 1; i >= 0; i--) { Symbol s = symbols.get(i); if (Word.class.isInstance(s)) { return (Word) s; } else if (Tuple.class.isInstance(s)) { Word word = getFirstArgument((Tuple)s); if (word != null) return word; } } throw new IllegalArgumentException("Cannot find name for ? at " + parameter.getPosition().getStart()); } private Word getFirstArgument(Tuple arg) { if (arg.size() == 0) return null; Symbol s = arg.getSymbol(0); if (s instanceof Word) { return (Word) s; } else if (s instanceof Tuple) { return getFirstArgument((Tuple)s); } return null; } private Word lookForArg(int stackLevel, Tuple s) { TupleBuilder tupleBuilder = tuples.get(stackLevel); int position = tupleBuilder.size(); if (s.size() > position) { Symbol symbol = s.getSymbol(position); if (Word.class.isInstance(symbol)) { return (Word) symbol; } else if (stackLevel + 1< tuples.size() && symbol instanceof Tuple) { return lookForArg(stackLevel + 1, (Tuple) symbol); } } return null; } private Position consumePosition() { Position position = new Position(lastMarkStart, lastMarkEnd); lastMarkEnd = -1; lastMarkStart = -1; return position; } private String getName(CharSequence cs, int i) { String name; if (lastMarkStart != -1) { if (lastMarkEnd == -1) { lastMarkEnd = i; } name = cs.subSequence(lastMarkStart, lastMarkEnd).toString(); } else { name = ""; } return name; } public interface Callback { void param(NamedParameter namedParameter); } }