001: package org.jicengine.element;
002:
003: import org.jicengine.operation.StaticValue;
004: import org.jicengine.operation.OperationException;
005: import org.jicengine.operation.VariableValueOperation;
006: import org.jicengine.operation.Operation;
007: import org.jicengine.operation.EmptyOperation;
008: import org.jicengine.expression.LJEParser;
009: import org.jicengine.expression.ClassParser;
010: import org.jicengine.expression.SyntaxException;
011: import java.util.*;
012:
013: /**
014: * <p>
015: * A class that makes it possible to create Element-objects little by little, as
016: * is needed if the JIC-file is parsed with a SAX-parser.
017: * </p>
018: * <p>
019: * ElementCompiler parses String-typed attributes and other data to corresponding
020: * objects. After all the data of an XML-element has been processed, the resulting
021: * runtime-Element can be obtained with method <code>createElement()</code>.
022: * </p>
023: * <p>
024: *
025: * </p>
026: *
027: * <h4> Element Life cycle </h4>
028: *
029: * <p>
030: * Copyright (C) 2004 Timo Laitinen
031: * </p>
032: * @author .timo
033: */
034:
035: public abstract class ElementCompiler {
036:
037: public static final String ATTR_NAME_ACTION = "action";
038: public static final String ATTR_NAME_CLASS = "class";
039: public static final String ATTR_NAME_VARIABLES = "vars";
040: public static final String ATTR_NAME_CONSTRUCTOR_ARGUMENTS = "args";
041: public static final String ATTR_NAME_TRACE = "trace";
042: public static final String ATTR_NAME_TYPE = "type";
043: public static final String ATTR_NAME_INSTANCE = "instance";
044: public static final String ATTR_NAME_OVERRIDABLE_BY = "overridable-by";
045: public static final String ATTR_NAME_IF = "if";
046:
047: public ElementCompiler() {
048: }
049:
050: private ElementImpl element;
051: private String constructorArguments;
052: private boolean constructorDerivedFromClassInformation = false;
053:
054: public ElementCompiler(String name, Location location) {
055: this .element = new ElementImpl(name, location);
056: }
057:
058: public Element createElement() throws ElementException {
059: return getElement().toRuntimeElement();
060: }
061:
062: public String getName() {
063: return getElement().getName();
064: }
065:
066: public Location getLocation() {
067: return getElement().getLocation();
068: }
069:
070: public void setOverridableBy(String overridingId) {
071: getElement().setOverridableBy(overridingId);
072: }
073:
074: public void setIf(String condition) throws ElementException {
075: try {
076: getElement()
077: .setIf(LJEParser.getInstance().parse(condition));
078: } catch (SyntaxException e) {
079: throw new AttributeException("[if=\"" + condition + "\"]: "
080: + e.getMessage(), e, getName(), getLocation());
081: }
082: }
083:
084: /**
085: *
086: */
087: public void setVariables(String variableExpression)
088: throws ElementException {
089: StringTokenizer tokenizer = new StringTokenizer(
090: variableExpression, ",");
091: String[] variableNames = new String[tokenizer.countTokens()];
092: int i = 0;
093: while (tokenizer.hasMoreElements()) {
094: variableNames[i] = tokenizer.nextToken().trim();
095: i++;
096: }
097: setVariables(variableNames);
098: }
099:
100: public void setVariables(String[] variableNames)
101: throws ElementException {
102: getElement().setVariableNames(variableNames);
103: }
104:
105: public void setConstructorArguments(String argumentExpression) {
106: this .constructorArguments = argumentExpression;
107: }
108:
109: public void setConstructor(String expression)
110: throws ElementException {
111: try {
112: getElement().setConstructor(
113: LJEParser.getInstance().parse(expression));
114: } catch (SyntaxException e) {
115: throw new AttributeException("[instance=\"" + expression
116: + "\"]: " + e.getMessage(), e, getName(),
117: getLocation());
118: }
119: }
120:
121: public Operation getConstructor() {
122: return getElement().getConstructor();
123: }
124:
125: public void setInstanceClass(String className)
126: throws ElementException {
127: try {
128: getElement().setInstanceClass(
129: ClassParser.INSTANCE.toClass(className));
130: } catch (Exception e) {
131: throw new AttributeException("[class=\"" + className
132: + "\"]: " + e.getMessage(), e, getName(),
133: getLocation());
134: }
135: }
136:
137: /**
138: * <p>
139: * for setting the action as an expression.
140: * </p>
141: * <p>
142: * enhancements:
143: * </p>
144: * <ul>
145: * <li>empty string is mapped into an EmptyOperation</li>
146: * <li>
147: * expressions of type <code>setMethod(value)</code> and
148: * <code>add(component)</code> are automatically madded to expressions
149: * <code>parent.setMethod(value)</code> and
150: * <code>parent.add(component)</code>.
151: * </li>
152: * </ul>
153: */
154: public void setAction(String expression) throws ElementException {
155: if (expression.length() == 0) {
156: getElement().setAction(EmptyOperation.INSTANCE);
157: } else {
158: String preparedExpression;
159: int paramStart = expression.indexOf("(");
160: int commaIndex = -1;
161:
162: if (paramStart != -1) {
163: commaIndex = expression.substring(0, paramStart)
164: .indexOf(".");
165: }
166:
167: // the existence of '(' implies that the action is indeed a method call
168: // (what else?)
169: // the lack of '.' implies that the actor of the method call is missing.
170: // therefore we add the implicit actor parent into the expression.
171: if (paramStart != -1 && commaIndex == -1) {
172: preparedExpression = Element.VARIABLE_NAME_PARENT_INSTANCE
173: + "." + expression;
174: } else {
175: preparedExpression = expression;
176: }
177:
178: try {
179: getElement().setAction(
180: LJEParser.getInstance().parse(
181: preparedExpression));
182: } catch (SyntaxException e) {
183: throw new AttributeException("[action=\"" + expression
184: + "\"]: " + e.getMessage(), e, getName(),
185: getLocation());
186: }
187: }
188: }
189:
190: /**
191: * if this element has a constructor, the cdata is added to the element-context
192: * with the name 'cdata'. (cdata might be ignored if the constructor doesn't
193: * use cdata) if there is no constructor, cdata becomes the value of this
194: * element.
195: *
196: * @param cdata Description of the Parameter
197: * @param syntaxBasedCdataConversionsSupported
198: * @throws ElementException Description of the Exception
199: */
200: public void setCData(String cdata,
201: boolean syntaxBasedCdataConversionsSupported)
202: throws ElementException {
203: if (getElement().getConstructor() == null
204: || this .constructorDerivedFromClassInformation) {
205: // there is no real constructor yet.
206: // in this case the element instance is derived from the CDATA.
207: // obtain a constructor that converts the CDATA-string into the required
208: // object.
209:
210: if (getElement().getInstanceClass() == null) {
211: // the class needs to be specified.
212:
213: Class instanceClass;
214:
215: if (syntaxBasedCdataConversionsSupported) {
216: // we therefore resolve the class by examining the syntax of the CDATA
217: instanceClass = CdataHandler
218: .resolveInstanceClassFromCdata(cdata);
219: } else {
220: // JICE 2.0 behaviour:
221: instanceClass = String.class;
222: }
223:
224: // setting the instance class is enough:
225: // the CDATA will now be handled as a normal
226: // CDATA conversion.
227: getElement().setInstanceClass(instanceClass);
228: }
229:
230: Operation constructor;
231: try {
232: constructor = CdataHandler
233: .getClassBasedCdataConversionConstructor(
234: getElement().getInstanceClass(), cdata);
235: } catch (Exception ex) {
236: throw new ElementException(ex, getName(), getLocation());
237: }
238: getElement().deleteConstructor();
239: getElement().setConstructor(constructor);
240:
241: // note: we don't save the CDATA anywhere here. we assume that
242: // the constructor obtained from CdataHandler has stored the CDATA.
243:
244: } else {
245: // a constructor has been specified explicitly.
246:
247: if (getElement().isConstructorVariable(
248: Element.VARIABLE_NAME_CDATA)) {
249: // ok, the constructor consumes the CDATA.
250:
251: // we create a virtual element that makes it possible to handle the
252: // CDATA like any other child element..
253: VariableElement virtualElement = new StaticValueElement(
254: Element.VARIABLE_NAME_CDATA, getLocation(),
255: cdata);
256: handleChildElement(virtualElement);
257: } else {
258: // the constructor doesn't use the CDATA. It is therefore illegal!
259: throw new ElementException(
260: "Illegal CDATA: CDATA must be used in the constructor (variable 'cdata').",
261: getName(), getLocation());
262: }
263: }
264: }
265:
266: /**
267: * <p>
268: * Called by handler when the start-tag of the element has been processed -
269: * Element has been created and all attributes have been set, but no CDATA nor
270: * child-elements have been processed.
271: * </p>
272: * <p>
273: * This is a good spot for verifying that
274: * the state of the element is valid - the attributes have been used properly,
275: * attributes not set by user can be set to their default values, etc.
276: * </p>
277: *
278: * @throws ElementException Description of the Exception
279: */
280: public void elementInitialized() throws ElementException {
281: // do some validity checks concerning the use of the 'args' attribute.
282: if (this .constructorArguments != null) {
283: if (getElement().getInstanceClass() == null) {
284: throw new AttributeException(
285: "Attribute '"
286: + ATTR_NAME_CONSTRUCTOR_ARGUMENTS
287: + "' must be specified together with attribute '"
288: + ATTR_NAME_CLASS + "'", getName(),
289: getLocation());
290: }
291: if (getElement().getConstructor() != null) {
292: throw new AttributeException("Attributes '"
293: + ATTR_NAME_INSTANCE + "' and '"
294: + ATTR_NAME_CONSTRUCTOR_ARGUMENTS
295: + "' can't be used together", getName(),
296: getLocation());
297: }
298: }
299:
300: if (getElement().getConstructor() != null) {
301: // the element has a constructor
302: // -> it has an object
303: // -> the class of object must be specified!
304: if (getElement().getInstanceClass() == null) {
305: throw new AttributeException("The attribute '"
306: + ATTR_NAME_CLASS + "' must be specified.",
307: getName(), getLocation());
308: }
309: }
310:
311: deriveConstructorFromClassInformation();
312: }
313:
314: /**
315: * Used for notifying this element about a child-element
316: *
317: * @param child a child with no action, can have a value or not.
318: * @throws ElementException Description of the Exception
319: */
320: public void handleChildElement(Element child)
321: throws ElementException {
322: if (child instanceof VariableElement
323: && !getElement().isUsed((VariableElement) child)) {
324: // this value element has no use.. yet.
325: // we let the subclasses to decide the purpose of this child.
326: //
327: ActionElement actionElement = handleLooseVariableElement((VariableElement) child);
328:
329: // now the child is an action element and we can add it.
330: getElement().addChildElement(actionElement);
331: } else {
332: getElement().addChildElement(child);
333: }
334: }
335:
336: protected ElementImpl getElement() {
337: return this .element;
338: }
339:
340: /**
341: *
342: * for subclasses!
343: */
344: protected abstract ActionElement handleLooseVariableElement(
345: VariableElement child) throws ElementException;
346:
347: public String toString() {
348: return "<" + getName() + ">";
349: }
350:
351: /**
352: * <p>
353: * create the implicit constructor from 'class' and 'args' attributes,
354: * if possible. if the element already has a constructor, nothing is done.
355: * </p>
356: *
357: *
358: * @throws ElementException Description of the Exception
359: */
360: private void deriveConstructorFromClassInformation()
361: throws ElementException {
362: if (getElement().getConstructor() == null
363: && getElement().getInstanceClass() != null) {
364: // we use the instanceClass-information for constructing the implicit constructor.
365:
366: String constructorExpression;
367: if (this .constructorArguments == null) {
368: // empty constructor
369: constructorExpression = "new "
370: + getElement().getInstanceClass().getName()
371: + "()";
372: // we mark that the constructor wasn't explicitly set in the code
373: // but was an implicit constructor derived from the attributes.
374: // we need this information when handling cdata..
375: this .constructorDerivedFromClassInformation = true;
376: } else {
377: // we have some parameters.
378: constructorExpression = "new "
379: + getElement().getInstanceClass().getName()
380: + "(" + this .constructorArguments + ")";
381: this .constructorDerivedFromClassInformation = false;
382: }
383:
384: // TODO: don't create a string to be parsed - create a ready Operation-object
385:
386: try {
387: setConstructor(constructorExpression);
388:
389: } catch (ElementException e) {
390: throw new ElementException(
391: "Problems creating implicit constructor.", e,
392: getName(), getLocation());
393: }
394: }
395: }
396:
397: }
|