001: package org.jicengine.element;
002:
003: import java.util.ArrayList;
004: import java.util.List;
005:
006: import org.jicengine.operation.Context;
007: import org.jicengine.operation.LocalContext;
008: import org.jicengine.operation.Operation;
009: import org.jicengine.operation.OperationException;
010:
011: /**
012: * <p>
013: *
014: * ElementImpl encapsulates the common properties and behaviour of an element.
015: * </p> <h4> Common Element Structure </h4> <p>
016: *
017: * An Element consists of: </p>
018: * <ul>
019: * <li> max 1 constructor together with * VariableElement-children as constructor
020: * parameters. </li>
021: * <li> * ActionElement-children </li>
022: * <li> max 1 action together with * VariableElement-children as action
023: * parameters. </li>
024: * </ul>
025: * <h4> Common processing procedures </h4>
026: *
027: * <p>
028: * ElementImpl defines the common procedures for processing these
029: * sub-components. The details of the processing can be tricky - an Element
030: * might or might not have a constructor, it might or might not have an element,
031: * etc.
032: * </p>
033: *
034: * <h4> Lack of the exact runtime-behaviour </h4>
035: * <p>
036: * the runtime-behaviour of an Element is defined by interfaces VariableElement and
037: * ActionElement. Whether an Element becomes an VariableElement or an ActionElement
038: * depends on the state of the Element.
039: * </p>
040: * <p>
041: *
042: * ElementImpl can be initialized little-by-little, it is designed to be used
043: * together with ElementCompiler. because of this approach, the
044: * runtime-behaviour of the Element is known only when the Element is completely
045: * initialized.
046: * </p> <p>
047: *
048: * After an ElementImpl-object is completely initialized, its runtime-version
049: * (VariableElement or ActionElement) can be obtained with method
050: * toRuntimeElement(). </p>
051: *
052: * <h4> id binding </h4>
053: * <p>
054: * the object is added to the global-context only after it is created. this
055: * way, the child-elements that are constructor parameters are processed before
056: * the element has used its id-attribute. therefore, if a child has a same
057: * id than the parent, the duplicate-id-exceptions will blame the wrong
058: * element for the id - the parent.
059: * </p>
060: *
061: * <p>
062: * Copyright (C) 2004 Timo Laitinen
063: * </p>
064: *
065: * @author .timo
066: */
067:
068: public class ElementImpl extends AbstractElement {
069:
070: /**
071: * VariableElement-version of an ElementImpl.
072: *
073: * @author timo
074: */
075: public class VariableElementImpl implements VariableElement {
076: public String getName() {
077: return ElementImpl.this .getName();
078: }
079:
080: public Location getLocation() {
081: return ElementImpl.this .getLocation();
082: }
083:
084: public boolean isExecuted(Context outerContext,
085: Object parentInstance) throws ElementException {
086: return ElementImpl.this .isExecuted(outerContext,
087: parentInstance);
088: }
089:
090: public Object getValue(Context context, Object parentInstance)
091: throws ElementException {
092: return ElementImpl.this .execute(context, parentInstance);
093: }
094:
095: public Class getInstanceClass() {
096: return ElementImpl.this .getInstanceClass();
097: }
098:
099: public String toString() {
100: return "<" + getName() + ">";
101: }
102: }
103:
104: /**
105: * ActionElement-version of an ElementImpl.
106: *
107: * @author timo
108: */
109: public class ActionElementImpl implements ActionElement {
110: public String getName() {
111: return ElementImpl.this .getName();
112: }
113:
114: public Location getLocation() {
115: return ElementImpl.this .getLocation();
116: }
117:
118: public boolean isExecuted(Context outerContext,
119: Object parentInstance) throws ElementException {
120: return ElementImpl.this .isExecuted(outerContext,
121: parentInstance);
122: }
123:
124: public void execute(Context context, Object parentInstance)
125: throws ElementException {
126: ElementImpl.this .execute(context, parentInstance);
127: }
128:
129: public String toString() {
130: return "<" + getName() + ">";
131: }
132: }
133:
134: protected static final int CHILD_TYPE_CONSTRUCTOR_ARG = 0;
135: protected static final int CHILD_TYPE_ACTION_ARG = 1;
136: protected static final int CHILD_TYPE_ACTION = 2;
137: protected static final int CHILD_TYPE_VARIABLE = 3;
138:
139: private Operation condition;
140:
141: private String[] variableNames;
142:
143: private Operation constructor;
144: private Class instanceClass;
145:
146: private String overridableBy;
147:
148: private List childElements = new ArrayList();
149: private List childElementTypes = new ArrayList();
150: private int lastConstructorArgumentIndex = -1;
151:
152: private Operation action;
153:
154: public ElementImpl(String name, Location location) {
155: super (name, location);
156: }
157:
158: public void setConstructor(Operation operation)
159: throws ElementException {
160: if (this .constructor != null) {
161: throw new ElementException("Constructor already set to '"
162: + this .constructor + "', new constructor '"
163: + operation + "' not accepted.", getName(),
164: getLocation());
165: }
166: this .constructor = operation;
167: }
168:
169: /**
170: * Removes a pre-set constructor so that a new one can be set with
171: * <code>setConstructor</code>.
172: */
173: public void deleteConstructor() {
174: this .constructor = null;
175: }
176:
177: public void setOverridableBy(String overridingId) {
178: this .overridableBy = overridingId;
179: }
180:
181: /**
182: *
183: * @throws ElementException if variables were already set.
184: */
185: public void setVariableNames(String[] elementNames)
186: throws ElementException {
187: if (this .variableNames != null) {
188: throw new ElementException("Variables already set.",
189: getName(), getLocation());
190: }
191: this .variableNames = elementNames;
192: }
193:
194: public void setInstanceClass(Class instanceClass)
195: throws ElementException {
196: if (this .instanceClass != null) {
197: throw new ElementException("Class already set to '"
198: + this .instanceClass + "', new class '"
199: + instanceClass + "' not accepted.", getName(),
200: getLocation());
201: }
202: this .instanceClass = instanceClass;
203: }
204:
205: public void setAction(Operation operation) throws ElementException {
206: if (this .action != null) {
207: throw new ElementException("Action already set to '"
208: + this .action + "', new action '" + operation
209: + "' not accepted.", getName(), getLocation());
210: }
211: this .action = operation;
212: }
213:
214: public void setIf(Operation condition) throws ElementException {
215: if (this .condition != null) {
216: throw new ElementException("If condition already set to '"
217: + this .condition + "'", getName(), getLocation());
218: }
219: this .condition = condition;
220: }
221:
222: public Operation getIf() {
223: return this .condition;
224: }
225:
226: public Operation getConstructor() {
227: return this .constructor;
228: }
229:
230: public Class getInstanceClass() {
231: return this .instanceClass;
232: }
233:
234: public Operation getAction() {
235: return this .action;
236: }
237:
238: protected boolean isOverridden(Context context) {
239: //return (this.overridableBy != null && findBuildContextFrom(context).getParameters().containsKey(this.overridableBy));
240: return (this .overridableBy != null && context
241: .hasObject(org.jicengine.expression.BuildParameterParser.PREFIX
242: + this .overridableBy));
243: }
244:
245: public boolean isElementVariable(String elementName) {
246: if (this .variableNames != null) {
247: for (int i = 0; i < this .variableNames.length; i++) {
248: if (this .variableNames[i] != null
249: && this .variableNames[i].equals(elementName)) {
250: // match
251: return true;
252: }
253: }
254: }
255: // no variables or no match.
256: return false;
257: }
258:
259: public boolean isConstructorVariable(String elementName) {
260: return getConstructor() != null
261: && getConstructor().needsParameter(elementName);
262: }
263:
264: public boolean isActionVariable(String elementName) {
265: return getAction() != null
266: && getAction().needsParameter(elementName);
267: }
268:
269: /**
270: * stores the information that a variable has been found and published to
271: * the context. this way we may later validate that all the necessary variables
272: * have been found.
273: *
274: * @param name String
275: */
276: protected void variablePublished(String name) {
277: if (this .variableNames != null) {
278: for (int i = 0; i < this .variableNames.length; i++) {
279: if (this .variableNames[i] != null
280: && this .variableNames[i].equals(name)) {
281: // match
282: this .variableNames[i] = null;
283: }
284: }
285: }
286: }
287:
288: protected void verifyVariablesHaveBeenFound()
289: throws ElementException {
290: if (this .variableNames != null) {
291: for (int i = 0; i < this .variableNames.length; i++) {
292: if (this .variableNames[i] != null) {
293: throw new ElementException("Element variable '"
294: + this .variableNames[i] + "' not found.",
295: getName(), "vars", getLocation());
296: }
297: }
298: }
299: }
300:
301: /**
302: * @param context the global context.
303: * @return null if this element can't be overridden at all or
304: * is not overridden currently (overriding object not found).
305: * @throws ElementException Description of the Exception
306: */
307: private Object getOverridingObject(Context context)
308: throws ElementException {
309: Object overridingObject = null;
310:
311: if (isOverridden(context)) {
312: // this element is overridden..
313: try {
314: overridingObject = context
315: .getObject(org.jicengine.expression.BuildParameterParser.PREFIX
316: + this .overridableBy);
317: } catch (org.jicengine.operation.ObjectNotFoundException e) {
318: throw new RuntimeException(
319: "Build parameter not found although it should",
320: e);
321: }
322: // validate it
323: validateInstance(overridingObject, true);
324: }
325: return overridingObject;
326: }
327:
328: /**
329: * <p>
330: *
331: * Returns the runtime-version of this Element. Call this method after the
332: * Element is completely initialized. </p>
333: *
334: * @return either an instance of VariableElement or
335: * ActionElement.
336: * @throws ElementException Description of the Exception
337: */
338: public Element toRuntimeElement() throws ElementException {
339: if (this .action != null) {
340: return new ActionElementImpl();
341: } else if (this .constructor != null
342: || this .overridableBy != null) {
343: return new VariableElementImpl();
344: } else {
345: // no action and no value?
346: throw new ElementException(
347: "Element without action nor value is not allowed.",
348: getName(), getLocation());
349: }
350: }
351:
352: public String toString() {
353: return "<" + getName() + ">";
354: }
355:
356: /**
357: * @return true if the child element is needed i.e. used by either the action
358: * or the constructor.
359: */
360: public boolean isUsed(VariableElement child) {
361: String name = child.getName();
362: return isConstructorVariable(name) || isActionVariable(name)
363: || isElementVariable(name);
364: }
365:
366: /**
367: * adds a child element into this element.
368: *
369: * @throws ElementException if the child is a VariableElement that is not needed
370: * by the action nor the constructor. unsused child elements without a purpose
371: * (without action) are not allowed because.. their value would be created
372: * and the result would be ignored.
373: */
374: public void addChildElement(Element child) throws ElementException {
375: if (child instanceof ActionElement) {
376: // ok.
377: addChildElement(child, CHILD_TYPE_ACTION);
378: } else if (child instanceof VariableElement) {
379: VariableElement valueChild = (VariableElement) child;
380: String childName = valueChild.getName();
381:
382: // the child may match only a single case.
383:
384: if (isElementVariable(childName)) {
385: // this is a variable
386: addChildElement(child, CHILD_TYPE_VARIABLE);
387: } else if (isConstructorVariable(childName)) {
388: // this is a constructor parameter.
389: addChildElement(child, CHILD_TYPE_CONSTRUCTOR_ARG);
390: } else if (isActionVariable(childName)) {
391: // this is a parameter of the action
392: addChildElement(child, CHILD_TYPE_ACTION_ARG);
393: } else {
394: // unused value element. we won't allow those here!
395: throw new ElementException("The element " + child
396: + " has no use.", child.getName(), child
397: .getLocation());
398: }
399: } else {
400: throw new RuntimeException("unknown child-element type: '"
401: + child.getClass().getName() + "'");
402: }
403: }
404:
405: private void addChildElement(Element child, int type) {
406: this .childElements.add(child);
407: this .childElementTypes.add(new Integer(type));
408:
409: // check whether this child is needed in the constructor
410: if (type == CHILD_TYPE_CONSTRUCTOR_ARG
411: || (type == CHILD_TYPE_VARIABLE
412: && getConstructor() != null && getConstructor()
413: .needsParameter(child.getName()))) {
414: // this is the currently last index.
415:
416: this .lastConstructorArgumentIndex = this .childElements
417: .size() - 1;
418: }
419: }
420:
421: /**
422: * <p>
423: * returns the instance of this element: by creating it with the constructor or
424: * by obtaining the overriding object.</p>
425: *
426: *
427: * @param constructorContext the context where the constructor is executed.
428: * @return Description of the Return Value
429: * @throws ElementException Description of the Exception
430: */
431: private Object obtainElementInstance(Context constructorContext,
432: boolean isOverridden) throws ElementException {
433: Object instance;
434: if (isOverridden) {
435: // we can find the overriding object also from the constructor context..
436: instance = getOverridingObject(constructorContext);
437: } else if (this .constructor != null) {
438: instance = executeConstructor(constructorContext);
439: } else if (this .overridableBy != null) {
440: //
441: throw new ElementException("Required overriding '"
442: + this .overridableBy + "' not found.", getName(),
443: getLocation());
444: } else {
445: throw new IllegalStateException(
446: "No constructor and no overriding - no instance available ("
447: + getName() + " at " + getLocation() + ")");
448: }
449:
450: return instance;
451: }
452:
453: /**
454: * @param actionContext Description of the Parameter
455: * @throws ElementException Description of the Exception
456: */
457: private void executeAction(Context actionContext)
458: throws ElementException {
459: if (this .action == null) {
460: throw new IllegalStateException("Action is null");
461: }
462:
463: try {
464: this .action.execute(actionContext);
465: } catch (OperationException e) {
466: throw new ElementException(e, getName(),
467: ElementCompiler.ATTR_NAME_ACTION, getLocation());
468: }
469: }
470:
471: /**
472: * executes the constructor and returns the resulting object. the returned
473: * object has been validated (with <code>validateInstance</code>).
474: *
475: * @param constructorContext The context where the constructor is executed.
476: * @return The freshly created instance of this element.
477: * @throws ElementException Description of the Exception
478: * @throws IllegalStateException if this method was called although there is
479: * no constructor.
480: */
481: private Object executeConstructor(Context constructorContext)
482: throws ElementException {
483: if (this .constructor == null) {
484: throw new IllegalStateException(
485: "executeConstructor() called although constructor is null. ("
486: + getName() + " at " + getLocation() + ")");
487: }
488:
489: // now we can execute the constructor
490: Object instance = null;
491: try {
492: instance = this .constructor.execute(constructorContext);
493: } catch (OperationException e) {
494: throw new ElementException(e, getName(), "instance",
495: getLocation());
496: }
497:
498: validateInstance(instance, false);
499: return instance;
500: }
501:
502: /**
503: * validates the instance. an exception is thrown, if the instance is not
504: * valid.
505: *
506: * currently, only the class of the instance is validated against the
507: * 'instanceClass'-property.
508: *
509: * @param isOverridingObject for more better error messages..
510: *
511: * @throws Exception if the instance is not valid. since this method doesn't
512: * know where the instance came from, the caller must catch this exception
513: * and throw a new exception with a better error message.
514: */
515: protected void validateInstance(Object instance,
516: boolean isOverridingObject) throws ElementException {
517: if (instance == null) {
518: throw new ElementException(
519: "Element instance is null - null values not allowed.",
520: getName(), getLocation());
521: }
522:
523: if (this .instanceClass != null
524: && !org.jicengine.operation.ReflectionUtils
525: .isAssignableFrom(this .instanceClass, instance
526: .getClass())) {
527: if (isOverridingObject) {
528: throw new ElementException("The overriding object '"
529: + this .overridableBy + "' was of type '"
530: + instance.getClass().getName()
531: + "', expected '"
532: + this .instanceClass.getName() + "'",
533: getName(), getLocation());
534: } else {
535: throw new ElementException(
536: "Expected the instance to be of type '"
537: + this .instanceClass.getName()
538: + "', was '"
539: + instance.getClass().getName() + "'",
540: getName(), getLocation());
541: }
542: }
543: }
544:
545: public boolean isExecuted(Context outerContext,
546: Object parentInstance) throws ElementException {
547: if (this .condition == null) {
548: return true;
549: } else {
550: Context ifContext = new LocalContext("<" + getName()
551: + ">//if", outerContext);
552: if (parentInstance != null) {
553: // add the parent to the context
554: ifContext.addObject(
555: Element.VARIABLE_NAME_PARENT_INSTANCE,
556: parentInstance);
557: }
558:
559: try {
560: Object result = this .condition.execute(ifContext);
561: return toBoolean(result);
562: } catch (OperationException e) {
563: throw new ElementException(e, getName(), "if",
564: getLocation());
565: }
566: }
567: }
568:
569: private boolean toBoolean(Object result) {
570: if (result == null) {
571: return false;
572: } else if (result instanceof Boolean) {
573: return ((Boolean) result).booleanValue();
574: } else {
575: // some unknown object type..
576: // interpreted as 'true' since it is not null..
577: return true;
578: }
579: }
580:
581: /**
582: * <p>
583: * executes this element. this includes:
584: * </p>
585: * <ul>
586: * <li> processing of all child elements, both value and action </li>
587: * <li> executing the constructor (if there is one)</li>
588: * <li> validating the instace (if there is one) </li>
589: * <li> handling the overriding-issues: instance not created and only
590: * action-parameter child-elements are executed if this element is overridden.</li>
591: * <li> handling the id-attribute: the instance is added to the context, if
592: * necessary.
593: * <li> executing the action (if there is one) </li>
594: * </ul>
595: *
596: * @return the instance of this element, if any, or null. the caller of
597: * this method should know whether the returned value is usable or not.
598: * if this element has an action, the returned value should not be used!!
599: * and note: the returned value won't be the result of the action. it will
600: * be the result of the constructor.
601: */
602: protected Object execute(Context outerContext, Object parentInstance)
603: throws ElementException {
604: Context elementContext = new LocalContext(
605: "<" + getName() + ">", outerContext);
606:
607: // the constructor and action have contexes of their own.
608: Context constructorContext = new LocalContext("<" + getName()
609: + ">//instance", elementContext);
610: Context actionContext = new LocalContext("<" + getName()
611: + ">//action", elementContext);
612:
613: if (parentInstance != null) {
614: // make the parent instance available to both of these contexes
615: constructorContext.addObject(VARIABLE_NAME_PARENT_INSTANCE,
616: parentInstance);
617: actionContext.addObject(VARIABLE_NAME_PARENT_INSTANCE,
618: parentInstance);
619: }
620:
621: boolean isOverridden = isOverridden(outerContext);
622:
623: // -----------------------
624: // execution
625: // -----------------------
626: // 1. child elements that come BEFORE the element instance is created
627: // = action elements | variables | constructor arguments
628: //
629: // 2. create the element instance
630: //
631: // 3. execute rest of the children
632: // = action elements | variables
633: //
634: // 4. execute the action of this element
635: // -----------------------
636:
637: // assertion: lastConstructorArgumentIndex <= childElements.size()!!
638:
639: // first set: child-elements that are executed before the instance is created.
640: for (int i = 0; i <= this .lastConstructorArgumentIndex; i++) {
641: Element child = (Element) childElements.get(i);
642: executeChild(child, ((Integer) childElementTypes.get(i))
643: .intValue(), elementContext, null,
644: constructorContext, actionContext, isOverridden);
645: }
646:
647: Object instance = null;
648: if (this .constructor != null || this .overridableBy != null) {
649: // we have an instance.
650: // obtain the instance - it is either created by the constructor or
651: // it is the overriding object.
652: instance = obtainElementInstance(constructorContext,
653: isOverridden);
654: }
655:
656: // execute rest of the children
657: for (int i = (this .lastConstructorArgumentIndex + 1); i < childElements
658: .size(); i++) {
659: Element child = (Element) childElements.get(i);
660: // the constructor params have no importance any more
661: // should we make sure somehow that the constructorParams won't get 'abused'?
662: executeChild(child, ((Integer) this .childElementTypes
663: .get(i)).intValue(), elementContext, instance,
664: constructorContext, actionContext, isOverridden);
665: }
666:
667: //
668: verifyVariablesHaveBeenFound();
669:
670: if (this .action != null) {
671:
672: if (instance != null) {
673: actionContext.addObject(
674: Element.VARIABLE_NAME_ELEMENT_INSTANCE,
675: instance);
676: }
677: executeAction(actionContext);
678: }
679:
680: // done.
681: return instance;
682: }
683:
684: /**
685: *
686: * @param child Element to be executed
687: * @param type whether the child is a constructor argument, action argument, etc.
688: * @param elementContext the context of this element, holding variables.
689: * @param instance Object the instance of this element, delivered to the child elements. may be null.
690: * @param constructorContext the constructor arguments are added to this context.
691: * @param actionContext Context the action arguments are added to this context.
692: * @param isOverridden boolean whether this element (not the child!) is overridden or not
693: * @throws ElementException if the execution of a child results in an error.
694: */
695: private void executeChild(Element child, int type,
696: Context elementContext, Object instance,
697: Context constructorContext, Context actionContext,
698: boolean isOverridden) throws ElementException {
699: if (isOverridden) {
700: // only action arguments are executed when this element is overridden
701: if (type == CHILD_TYPE_ACTION_ARG) {
702: // NOTE: we don't evaluate the if-attribute before it is absolutely
703: // necessary.
704: if (child.isExecuted(elementContext, instance)) {
705: Object value = ((VariableElement) child).getValue(
706: elementContext, instance);
707: actionContext.addObject(child.getName(), value);
708: }
709: }
710: } else if (child.isExecuted(elementContext, instance)) {
711: // the child element is executed
712: if (child instanceof VariableElement) {
713: // get the value
714: Object value = ((VariableElement) child).getValue(
715: elementContext, instance);
716:
717: // choose the context for the value
718: if (type == CHILD_TYPE_CONSTRUCTOR_ARG) {
719: constructorContext
720: .addObject(child.getName(), value);
721: } else if (type == CHILD_TYPE_VARIABLE) {
722: elementContext.addObject(child.getName(), value);
723: variablePublished(child.getName());
724: } else if (type == CHILD_TYPE_ACTION_ARG) {
725: actionContext.addObject(child.getName(), value);
726: } else {
727: throw new RuntimeException(
728: "illegal variable element type.");
729: }
730: } else {
731: // action element
732: ((ActionElement) child).execute(elementContext,
733: instance);
734: }
735: } else {
736: // child not executed.
737: }
738: }
739: }
|