001: /*
002: * Created on Feb 25, 2005
003: */
004: package net.sourceforge.orbroker;
005:
006: import java.util.ArrayList;
007: import java.util.Iterator;
008: import java.util.List;
009: import java.util.Map;
010: import java.util.SortedMap;
011: import java.util.TreeMap;
012:
013: /**
014: * This class represents an immutable parsed statement.
015: * @author Nils Kilden-Pedersen
016: */
017: final class SQLParser {
018: private static final String[] BREAK_CHARS = new String[] { " ",
019: ",", "(", ")", "=", ">", "<", "|", "-", "+", "*", "/", "'",
020: ";", "\"", "^", "%", "$", "#", "{", "}" };
021:
022: /**
023: * Is parameter name valid?
024: * @param parameterName
025: * @throws ConfigurationException
026: */
027: private static void checkParameterName(String parameterName)
028: throws ConfigurationException {
029: if (parameterName.indexOf(':') > -1) {
030: throw new ConfigurationException(
031: "Invalid parameter name. Cannot contain colon.");
032: }
033: if (parameterName.indexOf('\'') > -1) {
034: throw new ConfigurationException(
035: "Invalid parameter name. Cannot contain apostrophe.");
036: }
037: if (parameterName.indexOf('?') > -1) {
038: throw new ConfigurationException(
039: "Invalid parameter name. Cannot contain question mark.");
040: }
041:
042: }
043:
044: /**
045: * Count number of times a char appears in a string.
046: * @param string
047: * @param theChar
048: * @param end
049: * @return number of times char appear in string.
050: */
051: private static int countChar(StringBuffer string, char theChar,
052: int end) {
053: int count = 0;
054: for (int i = 0; i < end; i++) {
055: if (string.charAt(i) == theChar) {
056: count++;
057: }
058: }
059: return count;
060: }
061:
062: /**
063: * Find the smallest number in array between floor and ceiling, both
064: * inclusive. If none exist, ceiling will be returned.
065: *
066: * @param array
067: * @param floor
068: * @param ceiling
069: * @return smallest number in array between floor and ceiling.
070: */
071: private static int findLowest(int[] array, int floor, int ceiling) {
072: int least = ceiling;
073: for (int i = 0; i < array.length; i++) {
074: if (array[i] < least && array[i] >= floor) {
075: least = array[i];
076: }
077: }
078: return least;
079: }
080:
081: /**
082: * Parse the SQL statement containing host parameters. Note their position
083: * and replace with '?' for PreparedStatement. Return list with parameter names.
084: * @param statement
085: * @return Parameter list
086: * @throws ConfigurationException
087: */
088: private static List findParameters(final StringBuffer statement)
089: throws ConfigurationException {
090:
091: ArrayList parameterNames = new ArrayList();
092: int[] possibleBreaks = new int[BREAK_CHARS.length];
093: int str = 0;
094: while ((str = statement.indexOf(":", str)) > -1) {
095: if (isInsideQuotes(statement, str)) {
096: str++;
097: continue;
098: }
099: for (int i = 0; i < possibleBreaks.length; i++) {
100: possibleBreaks[i] = statement.indexOf(BREAK_CHARS[i],
101: str);
102: }
103:
104: int end = findLowest(possibleBreaks, 0, statement.length());
105: String name = statement.substring(str + 1, end);
106:
107: checkParameterName(name);
108:
109: parameterNames.add(name);
110: statement.replace(str, end, "?");
111: }
112: return parameterNames;
113: }
114:
115: /**
116: * Discover replacement properties in statement.
117: * String position of those values will be put into private
118: * list.
119: * @param statement
120: * @return Text replacement positions
121: */
122: private static SortedMap findTextReplacements(StringBuffer statement) {
123: TreeMap textReplacements = new TreeMap();
124: int str = 0;
125: while ((str = statement.indexOf("{{", str)) > -1) {
126: int end = statement.indexOf("}}", str);
127: String replacementKey = statement.substring(str + 2, end);
128: statement.replace(str, end + 2, "");
129: textReplacements.put(new Integer(str), replacementKey);
130: }
131: return textReplacements;
132: }
133:
134: /**
135: * @param string
136: * @param end
137: * @return <code>true</code> if the end of string is inside quotes.
138: */
139: private static boolean isInsideQuotes(StringBuffer string, int end) {
140: return countChar(string, '\'', end) % 2 != 0;
141: }
142:
143: private final List parameterNames;
144: private final SortedMap replacementPositions;
145: private final String sqlStatement;
146: private final ImmutableSQL immutableSQL;
147:
148: SQLParser(StringBuffer staticSql) {
149: this .parameterNames = findParameters(staticSql);
150: this .replacementPositions = findTextReplacements(staticSql);
151: this .sqlStatement = staticSql.toString();
152: if (this .replacementPositions.isEmpty()) {
153: this .immutableSQL = new ImmutableSQL(this .sqlStatement,
154: this .parameterNames);
155: } else {
156: this .immutableSQL = null;
157: }
158: }
159:
160: /**
161: * Insert replacement strings.
162: * @param statement
163: * @param replacements
164: * @return Finished statement
165: */
166: ImmutableSQL getRunnableStatement(TextReplacements replacements) {
167: if (this .immutableSQL != null) {
168: return this .immutableSQL;
169: }
170:
171: StringBuffer sql = new StringBuffer(this .sqlStatement);
172: Iterator properties = this .replacementPositions.entrySet()
173: .iterator();
174: int valueLengths = 0;
175: while (properties.hasNext()) {
176: Map.Entry entry = (Map.Entry) properties.next();
177: Integer position = (Integer) entry.getKey();
178: String key = (String) entry.getValue();
179: String value = replacements.getProperty(key);
180: if (value == null) {
181: String msg = "Text replacement '" + entry.getValue()
182: + "' has not been set.";
183: throw new BrokerException(msg);
184: }
185: sql.insert(position.intValue() + valueLengths, value);
186: valueLengths += value.length();
187: }
188: return new ImmutableSQL(sql.toString(), this.parameterNames);
189: }
190:
191: }
|