001: package org.gomba;
002:
003: import java.util.Enumeration;
004: import java.util.Vector;
005:
006: import org.gomba.utils.convert.ConvertUtilsWrapper;
007:
008: /**
009: * An Expression is created from a string containing ${} parameters. The
010: * expression is evaluated by the <code>replaceParameters</code> method.
011: *
012: * <p>
013: * A ${} parameter has the following syntax:
014: * <code>domain.paramName JavaType defaultValue</code>. JavaType and
015: * defaultValue are optional.
016: * </p>
017: *
018: * <p>
019: * Examples:
020: *
021: * <pre>
022: * ${param.myParam}
023: * ${param.myParam java.lang.Integer}
024: * ${param.myParam java.lang.Integer 10}
025: * ${path.1}
026: * ${column.myColumn java.util.Date}
027: * </pre>
028: *
029: * </p>
030: *
031: * @author Flavio Tordini
032: * @version $Id: Expression.java,v 1.4 2004/11/29 17:27:38 flaviotordini Exp $
033: */
034: final class Expression {
035:
036: /**
037: * A String representing a null default value.
038: * Usage: ${param.myParam java.util.Date null}
039: */
040: private final static String DEFAULT_NULL_VALUE = "null";
041:
042: /**
043: * Vector of <code>String</code> with null elements corresponding to
044: * parameters.
045: */
046: private final Vector fragments;
047:
048: /**
049: * Vector of <code>ParameterDefinition</code> with null elements
050: * corresponding to fragments.
051: */
052: private final Vector parameters;
053:
054: /**
055: * Constructor
056: */
057: public Expression(String expression) throws Exception {
058: if (expression == null) {
059: throw new IllegalArgumentException(
060: "Expression cannot be null.");
061: }
062:
063: this .fragments = new Vector();
064: this .parameters = new Vector();
065: Expression.parseParameters(expression, this .fragments,
066: this .parameters);
067:
068: }
069:
070: /**
071: * Replaces <code>${domain.paramName}</code> expressions in the given
072: * query definition with the actual values.
073: *
074: * @param parameterResolver
075: * The object used to resolve parameters
076: *
077: * @exception Exception
078: * if the string contains an opening <code>${</code>
079: * without a closing <code>}</code>
080: * @return the original string with the properties replaced by question
081: * marks. If the original string contains only a ${} parameter, the
082: * typed value of that parameter is returned, otherwise a
083: * <code>String</code> is returned.
084: */
085: public Object replaceParameters(ParameterResolver parameterResolver)
086: throws Exception {
087:
088: // test if we have a single ${} expression
089: if (this .parameters.size() == 1 && this .fragments.size() == 1
090: && this .fragments.get(0) == null) {
091: ParameterDefinition parameterDefinition = (ParameterDefinition) this .parameters
092: .get(0);
093: Object parameterValue = parameterResolver
094: .getParameterValue(parameterDefinition);
095: return parameterValue;
096: }
097:
098: final StringBuffer sb = new StringBuffer();
099: final Enumeration i = this .fragments.elements();
100: final Enumeration j = this .parameters.elements();
101:
102: while (i.hasMoreElements()) {
103: String fragment = (String) i.nextElement();
104: if (fragment == null) {
105:
106: ParameterDefinition parameterDefinition = (ParameterDefinition) j
107: .nextElement();
108:
109: Object parameterValue = parameterResolver
110: .getParameterValue(parameterDefinition);
111:
112: if (parameterValue == null) {
113: throw new MissingParameterException(
114: "Missing parameter: " + parameterDefinition);
115: }
116:
117: sb.append(parameterValue);
118: } else {
119: sb.append(fragment);
120: }
121:
122: }
123:
124: return sb.toString();
125: }
126:
127: /**
128: * Parse parameters. Fill the fragments with string literals and parameters
129: * with <code>ParameterDefinition</code> objects. For each parameter a
130: * null value is placed is the fragments vector and viceversa.
131: */
132: public static void parseParameters(String expression,
133: Vector fragments, Vector parameters) throws Exception {
134: int prev = 0;
135: int pos;
136: //search for the next instance of $ from the 'prev' position
137: while ((pos = expression.indexOf("$", prev)) >= 0) {
138:
139: //if there was any text before this, add it as a fragment
140: //TODO, this check could be modified to go if pos>prev;
141: //seems like this current version could stick empty strings
142: //into the list
143: if (pos > 0) {
144: fragments.addElement(expression.substring(prev, pos));
145: }
146: //if we are at the end of the string, we tack on a $
147: //then move past it
148: if (pos == (expression.length() - 1)) {
149: fragments.addElement("$");
150: prev = pos + 1;
151: } else if (expression.charAt(pos + 1) != '{') {
152: //peek ahead to see if the next char is a property or not
153: //not a property: insert the char as a literal
154: /*
155: * fragments.addElement(value.substring(pos + 1, pos + 2)); prev =
156: * pos + 2;
157: */
158: if (expression.charAt(pos + 1) == '$') {
159: //backwards compatibility two $ map to one mode
160: fragments.addElement("$");
161: prev = pos + 2;
162: } else {
163: //new behaviour: $X maps to $X for all values of X!='$'
164: fragments.addElement(expression.substring(pos,
165: pos + 2));
166: prev = pos + 2;
167: }
168:
169: } else {
170: //property found, extract its name or bail on a typo
171: int endName = expression.indexOf('}', pos);
172: if (endName < 0) {
173: throw new Exception("Syntax error in parameter: "
174: + expression);
175: }
176: String propertyDef = expression.substring(pos + 2,
177: endName);
178:
179: ParameterDefinition parameterDefinition = parseParameterDefinition(propertyDef);
180:
181: fragments.addElement(null);
182: parameters.addElement(parameterDefinition);
183: prev = endName + 1;
184: }
185: }
186: //no more $ signs found
187: //if there is any tail to the file, append it
188: if (prev < expression.length()) {
189: fragments.addElement(expression.substring(prev));
190: }
191: }
192:
193: /**
194: * Parse a parameter definition.
195: *
196: * @param parameter
197: * A string with the following syntax:
198: * <code>domain.param JavaType defaultValue</code>. JavaType
199: * and defaultValue are optional.
200: */
201: private static final ParameterDefinition parseParameterDefinition(
202: String parameter) throws Exception {
203:
204: // let's split it!
205: boolean escape = false;
206: boolean quote = false;
207: int tokenCount = 0;
208: String[] tokens = new String[3];
209: StringBuffer token = new StringBuffer();
210: for (int i = 0; i < parameter.length(); i++) {
211: final char c = parameter.charAt(i);
212: switch (c) {
213: case '\\':
214: if (escape) {
215: token.append(c);
216: escape = false;
217: } else {
218: escape = true;
219: }
220: break;
221: case '"':
222: if (escape) {
223: token.append(c);
224: } else if (quote) {
225: quote = false;
226: } else {
227: quote = true;
228: }
229: escape = false;
230: break;
231: case ' ':
232: if (quote) {
233: token.append(c);
234: } else if (escape) {
235: token.append(c);
236: } else {
237: // ok! token complete
238: tokens[tokenCount] = token.toString();
239: token = new StringBuffer();
240: tokenCount++;
241: }
242: escape = false;
243: break;
244: default:
245: token.append(c);
246: escape = false;
247: }
248: }
249:
250: // the last token
251: if (token.length() > 0) {
252: tokens[tokenCount] = token.toString();
253: }
254:
255: // split domain.param
256: if (tokens[0] == null) {
257: throw new Exception("Missing domain.name in parameter: "
258: + parameter);
259: }
260: final String domainAndName = tokens[0];
261: final int domainSeparatorIndex = domainAndName.indexOf('.');
262: if (domainSeparatorIndex < 0
263: || domainSeparatorIndex == domainAndName.length() - 1) {
264: throw new Exception("Syntax error in parameter: "
265: + parameter);
266: }
267: final String domain = domainAndName.substring(0,
268: domainSeparatorIndex);
269: final String name = domainAndName
270: .substring(domainSeparatorIndex + 1);
271:
272: // java type
273: final Class type;
274: Object defaultValue = null;
275: boolean nullable = false;
276: if (tokens[1] != null) {
277: type = Class.forName(tokens[1]);
278:
279: // default value
280: if (tokens[2] != null) {
281: // nullable
282: if (DEFAULT_NULL_VALUE.equals(tokens[2])) {
283: nullable = true;
284: } else {
285: defaultValue = ConvertUtilsWrapper.convert(
286: tokens[2], type);
287: }
288: }
289:
290: } else {
291: type = null;
292: }
293:
294: ParameterDefinition parameterDefinition = new ParameterDefinition(
295: domain, name, type, defaultValue, nullable);
296:
297: return parameterDefinition;
298: }
299:
300: }
|