KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jicengine > element > ElementImpl


1 package org.jicengine.element;
2
3 import java.util.ArrayList JavaDoc;
4 import java.util.List JavaDoc;
5
6 import org.jicengine.operation.Context;
7 import org.jicengine.operation.LocalContext;
8 import org.jicengine.operation.Operation;
9 import org.jicengine.operation.OperationException;
10
11 /**
12  * <p>
13  *
14  * ElementImpl encapsulates the common properties and behaviour of an element.
15  * </p> <h4> Common Element Structure </h4> <p>
16  *
17  * An Element consists of: </p>
18  * <ul>
19  * <li> max 1 constructor together with * VariableElement-children as constructor
20  * parameters. </li>
21  * <li> * ActionElement-children </li>
22  * <li> max 1 action together with * VariableElement-children as action
23  * parameters. </li>
24  * </ul>
25  * <h4> Common processing procedures </h4>
26  *
27  * <p>
28  * ElementImpl defines the common procedures for processing these
29  * sub-components. The details of the processing can be tricky - an Element
30  * might or might not have a constructor, it might or might not have an element,
31  * etc.
32  * </p>
33  *
34  * <h4> Lack of the exact runtime-behaviour </h4>
35  * <p>
36  * the runtime-behaviour of an Element is defined by interfaces VariableElement and
37  * ActionElement. Whether an Element becomes an VariableElement or an ActionElement
38  * depends on the state of the Element.
39  * </p>
40  * <p>
41  *
42  * ElementImpl can be initialized little-by-little, it is designed to be used
43  * together with ElementCompiler. because of this approach, the
44  * runtime-behaviour of the Element is known only when the Element is completely
45  * initialized.
46  * </p> <p>
47  *
48  * After an ElementImpl-object is completely initialized, its runtime-version
49  * (VariableElement or ActionElement) can be obtained with method
50  * toRuntimeElement(). </p>
51  *
52  * <h4> id binding </h4>
53  * <p>
54  * the object is added to the global-context only after it is created. this
55  * way, the child-elements that are constructor parameters are processed before
56  * the element has used its id-attribute. therefore, if a child has a same
57  * id than the parent, the duplicate-id-exceptions will blame the wrong
58  * element for the id - the parent.
59  * </p>
60  *
61  * <p>
62  * Copyright (C) 2004 Timo Laitinen
63  * </p>
64  *
65  * @author .timo
66  */

67
68 public class ElementImpl extends AbstractElement {
69
70     /**
71      * VariableElement-version of an ElementImpl.
72      *
73      * @author timo
74      */

75     public class VariableElementImpl implements VariableElement {
76         public String JavaDoc getName()
77         {
78             return ElementImpl.this.getName();
79         }
80
81         public Location getLocation()
82         {
83                 return ElementImpl.this.getLocation();
84         }
85
86         public boolean isExecuted(Context outerContext, Object JavaDoc parentInstance) throws ElementException
87         {
88             return ElementImpl.this.isExecuted(outerContext,parentInstance);
89         }
90
91
92         public Object JavaDoc getValue(Context context, Object JavaDoc parentInstance) throws ElementException
93         {
94             return ElementImpl.this.execute(context, parentInstance);
95         }
96     
97     public Class JavaDoc getInstanceClass()
98     {
99       return ElementImpl.this.getInstanceClass();
100     }
101
102         public String JavaDoc toString()
103         {
104             return "<" + getName() + ">";
105         }
106     }
107
108     /**
109      * ActionElement-version of an ElementImpl.
110      *
111      * @author timo
112      */

113     public class ActionElementImpl implements ActionElement {
114         public String JavaDoc getName()
115         {
116             return ElementImpl.this.getName();
117         }
118
119         public Location getLocation()
120         {
121             return ElementImpl.this.getLocation();
122         }
123
124         public boolean isExecuted(Context outerContext, Object JavaDoc parentInstance) throws ElementException
125         {
126             return ElementImpl.this.isExecuted(outerContext,parentInstance);
127         }
128
129         public void execute(Context context, Object JavaDoc parentInstance) throws ElementException
130         {
131             ElementImpl.this.execute(context, parentInstance);
132         }
133
134         public String JavaDoc toString()
135         {
136                 return "<" + getName() + ">";
137         }
138     }
139
140
141     protected static final int CHILD_TYPE_CONSTRUCTOR_ARG = 0;
142     protected static final int CHILD_TYPE_ACTION_ARG = 1;
143     protected static final int CHILD_TYPE_ACTION = 2;
144     protected static final int CHILD_TYPE_VARIABLE = 3;
145
146     private Operation condition;
147
148     private String JavaDoc[] variableNames;
149
150     private Operation constructor;
151     private Class JavaDoc instanceClass;
152
153     private String JavaDoc overridableBy;
154
155     private List JavaDoc childElements = new ArrayList JavaDoc();
156     private List JavaDoc childElementTypes = new ArrayList JavaDoc();
157     private int lastConstructorArgumentIndex = -1;
158
159     private Operation action;
160
161     public ElementImpl(String JavaDoc name, Location location)
162     {
163         super(name, location);
164     }
165
166     public void setConstructor(Operation operation) throws ElementException
167     {
168         if( this.constructor != null ){
169             throw new ElementException("Constructor already set to '" + this.constructor + "', new constructor '" + operation + "' not accepted.", getName(), getLocation());
170         }
171         this.constructor = operation;
172     }
173
174     /**
175      * Removes a pre-set constructor so that a new one can be set with
176      * <code>setConstructor</code>.
177      */

178     public void deleteConstructor()
179     {
180         this.constructor = null;
181     }
182
183     public void setOverridableBy(String JavaDoc overridingId)
184     {
185         this.overridableBy = overridingId;
186     }
187
188     /**
189      *
190      * @throws ElementException if variables were already set.
191      */

192     public void setVariableNames(String JavaDoc[] elementNames) throws ElementException
193     {
194         if( this.variableNames != null ){
195             throw new ElementException("Variables already set.", getName(), getLocation());
196         }
197         this.variableNames = elementNames;
198     }
199
200     public void setInstanceClass(Class JavaDoc instanceClass) throws ElementException
201     {
202         if( this.instanceClass != null ){
203             throw new ElementException("Class already set to '" + this.instanceClass + "', new class '" + instanceClass + "' not accepted.", getName(), getLocation());
204         }
205         this.instanceClass = instanceClass;
206     }
207
208     public void setAction(Operation operation) throws ElementException
209     {
210         if( this.action != null ){
211             throw new ElementException("Action already set to '" + this.action + "', new action '" + operation + "' not accepted.", getName(), getLocation());
212         }
213         this.action = operation;
214     }
215
216     public void setIf(Operation condition) throws ElementException
217     {
218         if( this.condition != null ){
219             throw new ElementException("If condition already set to '" + this.condition + "'", getName(), getLocation());
220         }
221         this.condition = condition;
222     }
223
224     public Operation getIf()
225     {
226         return this.condition;
227     }
228
229     public Operation getConstructor()
230     {
231         return this.constructor;
232     }
233
234     public Class JavaDoc getInstanceClass()
235     {
236         return this.instanceClass;
237     }
238
239     public Operation getAction()
240     {
241         return this.action;
242     }
243
244     protected boolean isOverridden(Context context)
245     {
246         //return (this.overridableBy != null && findBuildContextFrom(context).getParameters().containsKey(this.overridableBy));
247
return (this.overridableBy != null && context.hasObject(org.jicengine.expression.BuildParameterParser.PREFIX + this.overridableBy));
248     }
249
250     public boolean isElementVariable(String JavaDoc elementName)
251     {
252         if( this.variableNames != null ){
253             for (int i = 0; i < this.variableNames.length; i++) {
254                 if( this.variableNames[i] != null && this.variableNames[i].equals(elementName) ){
255                     // match
256
return true;
257                 }
258             }
259         }
260         // no variables or no match.
261
return false;
262     }
263
264     public boolean isConstructorVariable(String JavaDoc elementName)
265     {
266         return getConstructor() != null && getConstructor().needsParameter(elementName);
267     }
268
269     public boolean isActionVariable(String JavaDoc elementName)
270     {
271         return getAction() != null && getAction().needsParameter(elementName);
272     }
273
274     /**
275      * stores the information that a variable has been found and published to
276      * the context. this way we may later validate that all the necessary variables
277      * have been found.
278      *
279      * @param name String
280      */

281     protected void variablePublished(String JavaDoc name)
282     {
283         if( this.variableNames != null ){
284             for (int i = 0; i < this.variableNames.length; i++) {
285                 if( this.variableNames[i] != null && this.variableNames[i].equals(name) ){
286                     // match
287
this.variableNames[i] = null;
288                 }
289             }
290         }
291     }
292
293     protected void verifyVariablesHaveBeenFound() throws ElementException
294     {
295         if( this.variableNames != null ){
296             for (int i = 0; i < this.variableNames.length; i++) {
297                 if( this.variableNames[i] != null ){
298                     throw new ElementException("Element variable '" + this.variableNames[i] + "' not found.", getName(), "vars", getLocation());
299                 }
300      }
301         }
302     }
303
304     /**
305      * @param context the global context.
306      * @return null if this element can't be overridden at all or
307      * is not overridden currently (overriding object not found).
308      * @throws ElementException Description of the Exception
309      */

310     private Object JavaDoc getOverridingObject(Context context) throws ElementException
311     {
312         Object JavaDoc overridingObject = null;
313
314         if( isOverridden(context) ) {
315             // this element is overridden..
316
try {
317                 overridingObject = context.getObject(org.jicengine.expression.BuildParameterParser.PREFIX + this.overridableBy);
318             } catch (org.jicengine.operation.ObjectNotFoundException e){
319                 throw new RuntimeException JavaDoc("Build parameter not found although it should", e);
320             }
321             // validate it
322
validateInstance(overridingObject, true);
323         }
324         return overridingObject;
325     }
326
327     /**
328      * <p>
329      *
330      * Returns the runtime-version of this Element. Call this method after the
331      * Element is completely initialized. </p>
332      *
333      * @return either an instance of VariableElement or
334      * ActionElement.
335      * @throws ElementException Description of the Exception
336      */

337     public Element toRuntimeElement() throws ElementException
338     {
339         if(this.action != null) {
340             return new ActionElementImpl();
341         }
342         else if(this.constructor != null || this.overridableBy != null) {
343             return new VariableElementImpl();
344         }
345         else {
346             // no action and no value?
347
throw new ElementException("Element without action nor value is not allowed.", getName(), getLocation());
348         }
349     }
350
351     public String JavaDoc toString()
352     {
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     {
362         String JavaDoc name = child.getName();
363         return isConstructorVariable(name) || isActionVariable(name) || 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     {
376         if( child instanceof ActionElement ){
377             // ok.
378
addChildElement(child, CHILD_TYPE_ACTION);
379         }
380         else if( child instanceof VariableElement ){
381             VariableElement valueChild = (VariableElement) child;
382             String JavaDoc childName = valueChild.getName();
383
384             // the child may match only a single case.
385

386             if( isElementVariable(childName) ){
387                 // this is a variable
388
addChildElement(child, CHILD_TYPE_VARIABLE);
389             }
390             else if( isConstructorVariable(childName) ){
391                 // this is a constructor parameter.
392
addChildElement(child, CHILD_TYPE_CONSTRUCTOR_ARG);
393             }
394             else if( isActionVariable(childName) ){
395                 // this is a parameter of the action
396
addChildElement(child, CHILD_TYPE_ACTION_ARG);
397             }
398             else {
399                 // unused value element. we won't allow those here!
400
throw new ElementException("The element " + child + " has no use.", child.getName(), child.getLocation());
401             }
402         }
403         else {
404             throw new RuntimeException JavaDoc("unknown child-element type: '" + child.getClass().getName() + "'");
405         }
406     }
407
408     private void addChildElement(Element child, int type)
409     {
410         this.childElements.add(child);
411         this.childElementTypes.add(new Integer JavaDoc(type));
412
413         // check whether this child is needed in the constructor
414
if( type == CHILD_TYPE_CONSTRUCTOR_ARG ||
415             (type == CHILD_TYPE_VARIABLE && getConstructor() != null && getConstructor().needsParameter(child.getName()) )
416             ){
417             // this is the currently last index.
418

419             this.lastConstructorArgumentIndex = this.childElements.size()-1;
420         }
421     }
422
423     /**
424      * <p>
425      * returns the instance of this element: by creating it with the constructor or
426      * by obtaining the overriding object.</p>
427      *
428      *
429      * @param constructorContext the context where the constructor is executed.
430      * @return Description of the Return Value
431      * @throws ElementException Description of the Exception
432      */

433     private Object JavaDoc obtainElementInstance(Context constructorContext, boolean isOverridden) throws ElementException
434     {
435         Object JavaDoc instance;
436         if(isOverridden) {
437             // we can find the overriding object also from the constructor context..
438
instance = getOverridingObject(constructorContext);
439         }
440         else if(this.constructor != null) {
441             instance = executeConstructor(constructorContext);
442         }
443         else if(this.overridableBy != null) {
444             //
445
throw new ElementException("Required overriding '" + this.overridableBy + "' not found.", getName(), getLocation());
446         }
447         else {
448             throw new IllegalStateException JavaDoc("No constructor and no overriding - no instance available (" + getName() + " at " + getLocation() + ")");
449         }
450
451         return instance;
452     }
453
454     /**
455      * @param actionContext Description of the Parameter
456      * @throws ElementException Description of the Exception
457      */

458     private void executeAction(Context actionContext) throws ElementException
459     {
460         if(this.action == null) {
461             throw new IllegalStateException JavaDoc("Action is null");
462         }
463
464         try {
465             this.action.execute(actionContext);
466         } catch(OperationException e) {
467             throw new ElementException(e, getName(), 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 JavaDoc executeConstructor(Context constructorContext) throws ElementException
482     {
483         if(this.constructor == null) {
484             throw new IllegalStateException JavaDoc("executeConstructor() called although constructor is null. (" + getName() + " at " + getLocation() + ")");
485         }
486
487         // now we can execute the constructor
488
Object JavaDoc instance = null;
489         try {
490             instance = this.constructor.execute(constructorContext);
491         } catch(OperationException e) {
492             throw new ElementException(e, getName(), "instance", getLocation());
493         }
494
495         validateInstance(instance, false);
496         return instance;
497     }
498
499     /**
500      * validates the instance. an exception is thrown, if the instance is not
501      * valid.
502      *
503      * currently, only the class of the instance is validated against the
504      * 'instanceClass'-property.
505      *
506      * @param isOverridingObject for more better error messages..
507      *
508      * @throws Exception if the instance is not valid. since this method doesn't
509      * know where the instance came from, the caller must catch this exception
510      * and throw a new exception with a better error message.
511      */

512     protected void validateInstance(Object JavaDoc instance, boolean isOverridingObject) throws ElementException
513     {
514         if( instance == null ){
515             throw new ElementException("Element instance is null - null values not allowed.", getName(), getLocation());
516         }
517
518         if( this.instanceClass != null && !org.jicengine.operation.ReflectionUtils.isAssignableFrom(this.instanceClass, instance.getClass()) ){
519             if( isOverridingObject ){
520                 throw new ElementException("The overriding object '" + this.overridableBy + "' was of type '" + instance.getClass().getName() + "', expected '" + this.instanceClass.getName() + "'", getName(), getLocation());
521             }
522             else {
523                 throw new ElementException("Expected the instance to be of type '" + this.instanceClass.getName() + "', was '" + instance.getClass().getName() + "'", getName(), getLocation());
524             }
525         }
526     }
527
528     public boolean isExecuted(Context outerContext, Object JavaDoc parentInstance) throws ElementException
529     {
530         if( this.condition == null ){
531             return true;
532         }
533         else {
534             Context ifContext = new LocalContext("<" + getName() + ">//if", outerContext);
535             if( parentInstance != null ){
536                 // add the parent to the context
537
ifContext.addObject(Element.VARIABLE_NAME_PARENT_INSTANCE, parentInstance);
538             }
539
540             try {
541                 Object JavaDoc result = this.condition.execute(ifContext);
542                 return toBoolean(result);
543             } catch (OperationException e){
544                 throw new ElementException(e, getName(), "if", getLocation());
545             }
546         }
547     }
548
549     private boolean toBoolean(Object JavaDoc result)
550     {
551         if( result == null ){
552             return false;
553         }
554         else if( result instanceof Boolean JavaDoc){
555             return ((Boolean JavaDoc)result).booleanValue();
556         }
557         else {
558             // some unknown object type..
559
// interpreted as 'true' since it is not null..
560
return true;
561         }
562     }
563
564     /**
565      * <p>
566      * executes this element. this includes:
567      * </p>
568      * <ul>
569      * <li> processing of all child elements, both value and action </li>
570      * <li> executing the constructor (if there is one)</li>
571      * <li> validating the instace (if there is one) </li>
572      * <li> handling the overriding-issues: instance not created and only
573      * action-parameter child-elements are executed if this element is overridden.</li>
574      * <li> handling the id-attribute: the instance is added to the context, if
575      * necessary.
576      * <li> executing the action (if there is one) </li>
577      * </ul>
578      *
579      * @return the instance of this element, if any, or null. the caller of
580      * this method should know whether the returned value is usable or not.
581      * if this element has an action, the returned value should not be used!!
582      * and note: the returned value won't be the result of the action. it will
583      * be the result of the constructor.
584      */

585     protected Object JavaDoc execute(Context outerContext, Object JavaDoc parentInstance) throws ElementException
586     {
587         Context elementContext = new LocalContext("<" + getName() + ">", outerContext);
588
589         // the constructor and action have contexes of their own.
590
Context constructorContext = new LocalContext("<" + getName() + ">//instance", elementContext);
591         Context actionContext = new LocalContext("<" + getName() + ">//action", elementContext);
592
593         if( parentInstance != null ){
594             // make the parent instance available to both of these contexes
595
constructorContext.addObject(VARIABLE_NAME_PARENT_INSTANCE, parentInstance);
596             actionContext.addObject(VARIABLE_NAME_PARENT_INSTANCE, parentInstance);
597         }
598
599         boolean isOverridden = isOverridden(outerContext);
600
601         // -----------------------
602
// execution
603
// -----------------------
604
// 1. child elements that come BEFORE the element instance is created
605
// = action elements | variables | constructor arguments
606
//
607
// 2. create the element instance
608
//
609
// 3. execute rest of the children
610
// = action elements | variables
611
//
612
// 4. execute the action of this element
613
// -----------------------
614

615         // assertion: lastConstructorArgumentIndex <= childElements.size()!!
616

617         // first set: child-elements that are executed before the instance is created.
618
for (int i = 0; i <= this.lastConstructorArgumentIndex; i++) {
619             Element child = (Element) childElements.get(i);
620             executeChild(child, ((Integer JavaDoc)childElementTypes.get(i)).intValue(), elementContext, null, constructorContext, actionContext, isOverridden);
621         }
622
623         Object JavaDoc instance = null;
624         if( this.constructor != null || this.overridableBy != null) {
625             // we have an instance.
626
// obtain the instance - it is either created by the constructor or
627
// it is the overriding object.
628
instance = obtainElementInstance(constructorContext, isOverridden);
629         }
630
631         // execute rest of the children
632
for (int i = (this.lastConstructorArgumentIndex+1) ; i < childElements.size() ; i++) {
633             Element child = (Element) childElements.get(i);
634             // the constructor params have no importance any more
635
// should we make sure somehow that the constructorParams won't get 'abused'?
636
executeChild(child, ((Integer JavaDoc)this.childElementTypes.get(i)).intValue(), elementContext, instance, constructorContext, actionContext, isOverridden);
637         }
638
639         //
640
verifyVariablesHaveBeenFound();
641
642         if( this.action != null ){
643
644             if( instance != null ){
645                 actionContext.addObject(Element.VARIABLE_NAME_ELEMENT_INSTANCE, instance);
646             }
647             executeAction(actionContext);
648         }
649
650         // done.
651
return instance;
652     }
653
654     /**
655      *
656      * @param child Element to be executed
657      * @param type whether the child is a constructor argument, action argument, etc.
658      * @param elementContext the context of this element, holding variables.
659      * @param instance Object the instance of this element, delivered to the child elements. may be null.
660      * @param constructorContext the constructor arguments are added to this context.
661      * @param actionContext Context the action arguments are added to this context.
662      * @param isOverridden boolean whether this element (not the child!) is overridden or not
663      * @throws ElementException if the execution of a child results in an error.
664      */

665     private void executeChild(Element child, int type, Context elementContext, Object JavaDoc instance, Context constructorContext, Context actionContext, boolean isOverridden) throws ElementException
666     {
667         if( isOverridden ){
668             // only action arguments are executed when this element is overridden
669
if( type == CHILD_TYPE_ACTION_ARG ){
670                 // NOTE: we don't evaluate the if-attribute before it is absolutely
671
// necessary.
672
if( child.isExecuted(elementContext, instance) ){
673                     Object JavaDoc value = ((VariableElement) child).getValue(elementContext, instance);
674                     actionContext.addObject(child.getName(), value);
675                 }
676             }
677         }
678         else if( child.isExecuted(elementContext, instance) ){
679             // the child element is executed
680
if( child instanceof VariableElement ){
681                 // get the value
682
Object JavaDoc value = ((VariableElement)child).getValue(elementContext,instance);
683
684                 // choose the context for the value
685
if( type == CHILD_TYPE_CONSTRUCTOR_ARG ){
686                     constructorContext.addObject(child.getName(), value);
687                 }
688                 else if( type == CHILD_TYPE_VARIABLE ){
689                     elementContext.addObject(child.getName(), value);
690                     variablePublished(child.getName());
691                 }
692                 else if( type == CHILD_TYPE_ACTION_ARG ){
693                     actionContext.addObject(child.getName(), value);
694                 }
695                 else {
696                     throw new RuntimeException JavaDoc("illegal variable element type.");
697                 }
698             }
699             else {
700                 // action element
701
((ActionElement)child).execute(elementContext, instance);
702             }
703         }
704         else {
705             // child not executed.
706
}
707     }
708 }
709
Popular Tags