001: package net.sf.invicta.template;
002:
003: import java.util.ArrayList;
004: import java.util.HashMap;
005: import java.util.Iterator;
006: import java.util.List;
007: import java.util.Map;
008: import java.util.StringTokenizer;
009:
010: import net.sf.invicta.InvictaException;
011: import net.sf.invicta.handler.InvictaHandler;
012:
013: import sun.misc.Service;
014:
015: /**
016: * TemplateProcessor facility.
017: * Based on TLA generic Template processing mechanism.
018: * TODO: Add support for an escape char (\).
019: */
020: public class TemplateProcessor {
021: protected static final char HANDLER_SEPERATOR = ';';
022: protected static final String EXTRA_SEPERATOR = ";";
023: protected static final String EXTRA_MAP_SEPERATOR = "=";
024: protected static final char ESCAPE = '%';
025: protected static final char PLACEHOLDER_START = '{';
026: protected static final char PLACEHOLDER_END = '}';
027:
028: protected static Map handlers = new HashMap();
029:
030: static {
031: Iterator handlersIt = Service.providers(InvictaHandler.class);
032: while (handlersIt.hasNext()) {
033: InvictaHandler handler = (InvictaHandler) handlersIt.next();
034: handlers.put(handler.getName(), handler);
035: }
036: }
037:
038: /**
039: * Retrieve TemplateHandler object for the given handler name.
040: */
041: protected static InvictaHandler getHandler(String handler) {
042: return (InvictaHandler) handlers.get(handler);
043: }
044:
045: /**
046: * Generic mechanism for creating part of the templates result. The template
047: * is compiled into an array (list) of instructions that are executed
048: * sequentially. The object retuned by each instruction is appended to the
049: * result. Can throw an exception in case of error.
050: * @author tla
051: */
052: protected static abstract class Instruction {
053: public abstract Object handle(Object[][] parameters,
054: Object context) throws InvictaException,
055: InvictaException;
056: }
057:
058: /**
059: * Static instruction that is consturcted with a text string that should be
060: * added to the result in this specific location.
061: * @author tla
062: */
063: protected static class StaticInstruction extends Instruction {
064: private String text;
065:
066: public StaticInstruction(String text) {
067: this .text = text;
068: }
069:
070: public Object handle(Object[][] parameters, Object context)
071: throws InvictaException {
072: return this .text;
073: }
074: }
075:
076: /**
077: * Dynamic instruction that is resolved using the supplied parameters.
078: * The key is used to find the parameter to use in this instruction (can
079: * be null, when no parameter is needed). TemplateHandler, if supplied is called
080: * with the parameter matched by the key (or null if there is no key). The
081: * extra information is passed as a parameter to the handler. The format if
082: * given is converted into a MessageFormat formating instruction and called
083: * on the result of the handler/key.
084: * @author tla
085: */
086: protected static class DynamicInstruction extends Instruction {
087: protected String key = null;
088: protected int position = -1;
089: protected List extra = null;
090:
091: public DynamicInstruction(String key, List extra) {
092: this .key = key;
093: // If the key is an int convert it and use it for optimized a
094: // access in case of position based placeholder.
095: try {
096: this .position = Integer.parseInt(key);
097: } catch (NumberFormatException e) {
098: // Ignore!
099: }
100: this .extra = extra;
101: }
102:
103: public Object handle(Object[][] parameters, Object context)
104: throws InvictaException {
105: Object object = null;
106:
107: boolean found = false;
108: // If a key is not empty, find the parameter matching the key.
109: if (!"".equals(key)) {
110: if (parameters == null)
111: throw new InvictaTemplateException(
112: "Parameter "
113: + key
114: + " is needed, but no parameters were given");
115: if (position >= 0 && position < parameters.length) {
116: // If the key is a number, position will contain it in
117: // number form. If it is a valid index into parameters use
118: // it as a hint of where to start looking for. This is an
119: // optimization of position based parameters.
120: int index = position;
121: for (int i = 0; i < parameters.length; i++) {
122: Object[] pair = parameters[index];
123: if (key.equals(pair[0])) {
124: object = pair[1];
125: found = true;
126: break;
127: }
128: index = (index + 1) % parameters.length;
129: }
130: } else {
131: // Find the parameter by sequentially comparing the keys
132: // of the parameters.
133: for (int i = 0; i < parameters.length; i++) {
134: Object[] pair = parameters[i];
135: if (key.equals(pair[0])) {
136: object = pair[1];
137: found = true;
138: break;
139: }
140: }
141: }
142: }
143:
144: if (!found) {
145:
146: // Find the handler for this name.
147: InvictaHandler handler = (InvictaHandler) getHandler(key);
148:
149: if (handler == null)
150: throw new InvictaTemplateException(
151: "Can't find parameter or handler named "
152: + key);
153:
154: Map extraMap = lookForExtraMap(extra);
155:
156: if (extraMap == null)
157: object = handler.handle(context, extra);
158: else
159: object = handler.handle(context, extraMap);
160:
161: }
162:
163: // Nulls are not allowed. If the handler wants to allow nulls, they
164: // must be converted to something else (e.g. empty string).
165: if (object == null)
166: throw new InvictaTemplateException(
167: "Got null result for '" + key + "'");
168:
169: return object;
170: }
171:
172: /**
173: * @param extra
174: * @return
175: */
176: private Map lookForExtraMap(List extra) {
177: Map extraMap = new HashMap();
178: for (Iterator iter = extra.iterator(); iter.hasNext();) {
179: String param = (String) iter.next();
180: int equal = param.indexOf(EXTRA_MAP_SEPERATOR);
181: if (equal == -1)
182: return null;
183: extraMap.put(param.substring(0, equal), param
184: .substring(equal + 1));
185: }
186:
187: return extraMap;
188: }
189:
190: }
191:
192: /**
193: * The template compiled into an sequence of Instruction objects.
194: */
195: protected Instruction[] compiledFormat;
196:
197: /**
198: * The original template supplied.
199: */
200: protected String template;
201:
202: /**
203: * Context for the current template format
204: */
205: protected Object context;
206:
207: /**
208: * TemplateProcessor constructor using the given template.
209: * @param template
210: * @throws InvictaTemplateExceptioneption A runtime exception throws in case the
211: * template parsing fails.
212: */
213: public TemplateProcessor(String template)
214: throws InvictaTemplateException {
215: this (template, null);
216: }
217:
218: /**
219: * TemplateProcessor constructor using the given template.
220: * @param template
221: * @throws InvictaTemplateExceptioneption A runtime exception throws in case the
222: * template parsing fails.
223: */
224: public TemplateProcessor(String template, Object context)
225: throws InvictaTemplateException {
226: this .template = template;
227: this .context = context;
228:
229: ArrayList compiledFormat = new ArrayList();
230:
231: int length = template.length();
232: int position = 0; // Current position in the template.
233: try {
234: while (position < length) {
235: // Find the next escape sequence.
236: int beginIndex = template.indexOf(ESCAPE, position);
237: // If not found or found as the last character of the string.
238: // The rest of the string should be added as is.
239: if ((beginIndex == -1) || (beginIndex == length - 1)) {
240: compiledFormat.add(new StaticInstruction(template
241: .substring(position)));
242: break;
243: }
244:
245: // If the next character is not the openning character, this
246: // is not really a placeholder, the string up to this character
247: // is added as is.
248: int nextIndex = beginIndex + 1;
249: char nextChar = template.charAt(nextIndex);
250: if (nextChar != PLACEHOLDER_START) {
251: if (nextChar == ESCAPE) {
252: // This is an escape sequence. Skip the second escape
253: // character.
254:
255: // Do Nothing.
256: } else {
257: // Otherwise skip the second character too.
258: nextIndex++;
259: }
260:
261: compiledFormat.add(new StaticInstruction(template
262: .substring(position, nextIndex)));
263:
264: position = beginIndex + 2;
265: continue;
266: }
267:
268: // Find the end of the placeholder. Scream if not found.
269: int endIndex = template.indexOf(PLACEHOLDER_END,
270: beginIndex);
271: if (endIndex == -1) {
272: throw new InvictaTemplateException(
273: "Can't find end of placeholder");
274: }
275:
276: // If there are characters before the beginning of the placeholder
277: // add them now.
278: if (position != beginIndex) {
279: compiledFormat.add(new StaticInstruction(template
280: .substring(position, beginIndex)));
281: }
282:
283: String key = template.substring(beginIndex + 2,
284: endIndex);
285:
286: List extra = new ArrayList();
287:
288: // find possible extra information for handler.
289: int semicolon = key.indexOf(HANDLER_SEPERATOR);
290: if (semicolon != -1) {
291: String extraString = key.substring(semicolon + 1);
292: StringTokenizer st = new StringTokenizer(
293: extraString, EXTRA_SEPERATOR);
294: while (st.hasMoreTokens()) {
295: String extraParam = st.nextToken().trim();
296: extra.add(extraParam);
297: }
298:
299: key = key.substring(0, semicolon);
300: }
301:
302: DynamicInstruction instruction = new DynamicInstruction(
303: key, extra);
304:
305: compiledFormat.add(instruction);
306:
307: // Jump to the end of the placeholder.
308: position = endIndex + 1;
309: }
310:
311: // We save the compiled instruction list as an array for speed.
312: this .compiledFormat = (Instruction[]) compiledFormat
313: .toArray(new Instruction[compiledFormat.size()]);
314: } catch (Throwable problem) {
315: // A problem was found when parsing the template. Wrap with an
316: // appropriate exception. The format attempt is the given template
317: // since we couldn't even parse it.
318: throw new InvictaTemplateException("Can't parse template: "
319: + problem.getMessage(), problem, template);
320:
321: }
322: }
323:
324: /**
325: * Format the template represented by this object.
326: * @param parameters The parameters to use when formatting the template.
327: * Should be an array of arrays. The first element of each inner array is
328: * the key name and the second is the value to use for that key.
329: * @param buffer The formatted string is appended to this buffer.
330: * @throws InvictaTemplateExceptioneption A runtime exception throws in case the
331: * formatting fails.
332: */
333: public void format(Object[][] parameters, StringBuffer buffer)
334: throws InvictaTemplateException {
335: Throwable problem = null;
336: for (int i = 0; i < compiledFormat.length; i++) {
337: Instruction instruction = compiledFormat[i];
338: try {
339: buffer.append(instruction.handle(parameters, context));
340: } catch (Throwable t) {
341: problem = t;
342: }
343: }
344: if (problem != null) {
345: throw new InvictaTemplateException(
346: "Can't format template: " + problem.getMessage(),
347: problem, buffer.toString());
348: }
349: }
350:
351: /**
352: * Format the template represented by this object.
353: * @param parameters The parameters to use when formatting the template.
354: * Should be an array of arrays. The first element of each inner array is
355: * the key name and the second is the value to use for that key.
356: * @return String The formatted String.
357: * @throws InvictaTemplateExceptioneption A runtime exception throws in case the
358: * formatting fails.
359: */
360: public String format(Object[][] parameters)
361: throws InvictaTemplateException {
362: StringBuffer buffer = new StringBuffer();
363: format(parameters, buffer);
364: return buffer.toString();
365: }
366:
367: static protected String[] conversion = new String[] { "0", "1",
368: "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12",
369: "13", "14", "15", "16", "17", "18", "19" };
370:
371: /**
372: * Transform position based parameters to key based parameters by making
373: * each position into a key of the same value.
374: * @param array The array to transform
375: * @return Object[][] key based parameters in the format expected by
376: * TemplateProcessor
377: */
378: public static Object[][] positionToKey(Object[] array) {
379: if (array == null)
380: return null;
381: Object[][] parameters = new Object[array.length][2];
382: for (int i = 0; i < array.length; i++) {
383: parameters[i][0] = conversion[i];
384: parameters[i][1] = array[i];
385: }
386: return parameters;
387: }
388:
389: /**
390: * Utility method for constructing a method and formatting together.
391: * @param template TemplateProcessor format to use.
392: * @param parameters Parameters used to format the template, can be null.
393: * @param buffer The buffer to append the formatted String to.
394: * @throws InvictaTemplateExceptioneption A runtime exception throws in case the
395: * formatting fails.
396: */
397: public static void format(String template, Object[][] parameters,
398: StringBuffer buffer) throws InvictaTemplateException {
399: new TemplateProcessor(template).format(parameters, buffer);
400: }
401:
402: /**
403: * Utility method for constructing a method and formatting together.
404: * @param template TemplateProcessor format to use.
405: * @param parameters Parameters used to format the template, can be null.
406: * @return String The formatted String.
407: * @throws InvictaTemplateExceptioneption A runtime exception throws in case the
408: * formatting fails.
409: */
410: public static String format(String template, Object[][] parameters)
411: throws InvictaTemplateException {
412: return new TemplateProcessor(template).format(parameters);
413: }
414:
415: /**
416: * Returns the context.
417: * @return Object
418: */
419: public Object getContext() {
420: return context;
421: }
422:
423: /**
424: * Sets the context.
425: * @param context The context to set
426: */
427: public void setContext(Object context) {
428: this.context = context;
429: }
430:
431: }
|