4 * Copyright 2004-2007 MTBJ, Inc.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package com
.dtrules
.session
;
22 import java
.io
.OutputStream
;
23 import java
.io
.PrintStream
;
24 import java
.util
.ArrayList
;
25 import java
.util
.Calendar
;
26 import java
.util
.GregorianCalendar
;
27 import java
.util
.Random
;
29 import com
.dtrules
.decisiontables
.RDecisionTable
;
30 import com
.dtrules
.entity
.IREntity
;
31 import com
.dtrules
.entity
.REntityEntry
;
32 import com
.dtrules
.infrastructure
.RulesException
;
33 import com
.dtrules
.interpreter
.IRObject
;
34 import com
.dtrules
.interpreter
.RName
;
35 import com
.dtrules
.xmlparser
.GenericXMLParser
;
37 public class DTState
{
39 public Calendar calendar
;
40 { // Work around for a Java Bug
41 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5045774
44 calendar
= new GregorianCalendar();
48 private RDecisionTable currentTable
;
49 private String currentTableSection
;
50 private int numberInSection
;
53 * The interpreter is a stack based interpreter. The implementation
54 * is much like a Postscript Interpreter.
56 * The control stack is used to implement a stack frame for
57 * decision tables. The control stack has no analog in a PostScript
60 * The Entity stack is used to define the context for associating
61 * attributes with values. This is much like the PostScript Dictionary
64 * The Data stack is used to pass data to operators and to return
65 * results from operators.
68 private final int stklimit
= 1000;
70 IRObject ctrlstk
[] = new IRObject
[stklimit
];
71 IRObject datastk
[] = new IRObject
[stklimit
];
72 IREntity entitystk
[] = new IREntity
[stklimit
];
78 int frames
[] = new int[stklimit
];
82 final IRSession session
;
84 ArrayList
<String
> tagstk
= new ArrayList
<String
>();
86 boolean newline
= true;
89 public long seed
= 0x711083186866559L
;
90 public Random rand
= new Random(seed
);
92 * The default debugging printstream is Standard Out.
93 * The default error printstream is Standard Out.
95 private PrintStream out
= System
.out
;
96 private PrintStream err
= System
.out
;
98 public PrintStream
getErrorOut() { return err
; }
99 public PrintStream
getDebugOut() { return out
; }
100 public PrintStream
getTraceOut() { return out
; }
102 public static final int DEBUG
= 0x00000001;
103 public static final int TRACE
= 0x00000002;
104 public static final int ECHO
= 0x00000004;
105 public static final int VERBOSE
= 0x00000008;
107 private int state
= 0;
111 * Set the output streams for debug and trace.
113 public void setOutput(OutputStream debugtrace
, OutputStream error
){
114 out
= new PrintStream(debugtrace
);
115 err
= new PrintStream(error
);
119 * Set the output streams for debug and trace.
121 public void setOutput(PrintStream debugtrace
, PrintStream error
){
127 * We always print the error stream. But this may not
132 public boolean error(String s
){
137 public void traceStart(){
140 traceTagBegin("DTRulesTrace",null);
142 public void traceEnd() throws RulesException
{
143 traceTagEnd("DTRulesTrace",null);
149 * Returns a trace point. The trace Point is incremented each time a
150 * point is generated. The idea is that the RulesEngine state can be
151 * reset to the state represented by one of these trace points.
154 public int tracePt(){ return tracePoint
++; }
157 * Begins an execution tag. The execution (of something) is
158 * contained between the traceStart and traceEnd tags. If any
159 * attributes should be included, pass them as a string, i.e.
160 * " dt='Compute Policy" col='5' "
161 * If ECHO is set, then the output is also echoed to
164 public void traceTagBegin(String tag
, String attribs
){
165 if(testState(TRACE
)){
166 if(!newline
)out
.println();
167 if(testState(ECHO
) && out
!= System
.out
){
168 System
.out
.print(tag
+ " "+attribs
);
170 out
.print("<"+tag
+ (attribs
!=null ?
" "+attribs
+">":">"));
176 * Prints a single tag, i.e. "<tag attribs />"
180 public void traceInfo(String tag
, String attribs
){
181 if(testState(TRACE
)){
182 if(!newline
)out
.println();
183 if(testState(ECHO
) && out
!= System
.out
){
184 System
.out
.print(tag
+ " "+attribs
);
186 out
.println("<"+tag
+ (attribs
!=null ?
" "+attribs
+"/>":"/>"));
191 * Prints a tag, attributes, body, and the end tag.
192 * @param info The tag to use in the XML
193 * @param attribs Any attibutes you want
194 * @param body The body of the tag.
196 public void traceInfo(String info
, String attribs
, String body
)throws RulesException
{
197 traceTagBegin(info
, attribs
);
198 traceTagEnd(info
,body
);
202 * Ends an execution tag. The execution (of something) is
203 * contained between the traceTagBegin and traceTagEnd calls.
204 * Tags are checked for balance (if the tag is supplied.)
205 * If ECHO is on, the body is written to standard.out.
207 * Returns true if it printed something.
209 public void traceTagEnd(String tag
, String body
) throws RulesException
{
210 if(testState(TRACE
)){
211 if(testState(ECHO
) && out
!= System
.out
&& body
!=null){
212 System
.out
.print(body
);
213 System
.out
.print("\n");
215 int index
= tagstk
.size()-1;
217 System
.out
.println("tag underflow");
219 String btag
= tagstk
.get(index
);
220 tagstk
.remove(index
);
221 if(tag
!=null && !btag
.equals(tag
)){
222 System
.out
.println("tag_error tag1='"+GenericXMLParser
.encode(btag
)+"' "+
223 "expectedtag='"+GenericXMLParser
.encode(tag
)+"'");
225 if(body
!=null) out
.print(GenericXMLParser
.encode(body
));
226 out
.println("</"+btag
+">");
232 * Prints a string to the output file if DEBUG is on.
233 * If ECHO is set, then the output is also echoed to
235 * Returns true if it printed something.
237 public boolean debug(String s
){
238 if(testState(DEBUG
)){
239 if(!newline
)out
.println();
240 if(testState(ECHO
) && out
!= System
.out
){
243 s
= GenericXMLParser
.encode(s
);
244 out
.print("<dbg>"+s
+"</dbg>");
252 * Prints the Data Stack, Entity Stack, and Control Stack to
253 * the debugging output stream.
255 public void pstack() {
257 traceTagBegin("pstack", null);
259 traceTagBegin("datastk", null);
260 for (int i
= 0; i
< ddepth(); i
++) {
261 traceTagBegin("e",null);
262 traceTagEnd ("e",getds(i
).stringValue());
264 traceTagEnd("datastk",null);
266 traceTagBegin("entitystk", null);
267 for (int i
= 0; i
< edepth(); i
++) {
268 traceTagBegin("e",null);
269 traceTagEnd ("e",getes(i
).stringValue());
271 traceTagEnd("entitystk",null);
273 traceTagEnd("pstack", null);
274 }catch(RulesException e
){
275 err
.print("ERROR printing the stacks!\n");
276 err
.print(e
.toString()+"\n");
280 * Returns the count of the number of elements on the data
282 * @return data stack depth
288 * Returns the element on the data stack at the given depth
289 * If there are 3 elements on the data stack, getds(2) will
290 * return the top element. A stack underflow or overflow will
291 * be thrown if the index is out of range.
293 public IRObject
getds(int i
) throws RulesException
{
295 throw new RulesException(
296 "Data Stack Overflow",
298 "index out of range: "+i
);
301 throw new RulesException(
302 "Data Stack Underflow",
304 "index out of range: "+i
);
310 * Returns the element on the entity stack at the given depth
311 * If there are 3 entities on the entity stack, getes(2) will
312 * return the top entity. A stack underflow or overflow will
313 * be thrown if the index is out of range.
315 public IREntity
getes(int i
) throws RulesException
{
317 throw new RulesException(
318 "Entity Stack Overflow", "getes",
319 "index out of range: "+i
);
322 throw new RulesException(
323 "Entity Stack Underflow", "getes",
324 "index out of range: "+i
);
330 * While the state holds the stacks, the Session holds changes
331 * to Entities and other Rules Engine objects. On rare occasions
332 * we need to get our session, so we save it in the DTState.
335 DTState(IRSession rs
){
341 * @return the session assocaited with this state
343 public IRSession
getSession(){ return session
; }
346 * Returns the index of the Entity Stack.
349 public int edepth () { return entitystkptr
; }
351 * Pushes an IRObject onto the data stack.
353 * @throws RulesException
355 public void datapush(IRObject o
) throws RulesException
{
356 if(datastkptr
>=1000) {
357 throw new RulesException(
358 "Data Stack Overflow",
360 "Data Stack overflow.");
362 datastk
[datastkptr
++]=o
;
365 * Pops an IRObject off the data stack and returns that object.
367 * @throws RulesException
369 public IRObject
datapop() throws RulesException
{
371 throw new RulesException( "Data Stack Underflow", "datapop()", "Data Stack underflow.");
373 IRObject rval
= datastk
[--datastkptr
];
374 datastk
[datastkptr
]=null;
379 * Pushes an entity on the entity stack.
381 * @throws RulesException
383 public void entitypush(IREntity o
) throws RulesException
{
384 if(entitystkptr
>=1000) {
385 throw new RulesException("Entity Stack Overflow",
387 "Entity Stack overflow.");
389 entitystk
[entitystkptr
++]=o
;
392 * Pops an Entity off of the Entity stack.
394 * @throws RulesException
396 public IREntity
entitypop() throws RulesException
{
397 if(entitystkptr
<=0) {
398 throw new RulesException("Entity Stack Underflow",
399 "entitypop", "Entity Stack underflow.");
401 IREntity rval
= entitystk
[--entitystkptr
];
402 entitystk
[entitystkptr
] = null;
406 * Returns the nth element from the top of the entity stack
407 * (0 returns the top element, 1 returns the 1 from the top, 2
408 * the 2 from the top, etc.)
410 * @throws RulesException
412 public IREntity
entityfetch(int i
) throws RulesException
{
413 if(entitystkptr
<=i
) {
414 throw new RulesException("Entity Stack Underflow",
415 "entityfetch", "Entity Stack underflow.");
417 IREntity rval
= entitystk
[entitystkptr
-1-i
];
422 * Test to see if the given flag is set.
426 public boolean testState(int flag
){
427 return (state
&flag
)!=0;
431 * Clear the given flag (By and'ing the not of the flag
434 public void clearState(int flag
){
435 state
&= flag^
0xFFFFFFFF;
439 * Set the given flag by or'ing the flag into the state
442 public void setState(int flag
){
448 * Returns the current depth of the control stack.
451 public int cdepth(){ return ctrlstkptr
; }
453 public void pushframe() throws RulesException
{
454 if(framestkptr
>=stklimit
){
455 throw new RulesException("Control Stack Overflow",
456 "pushframe", "Control Stack Overflow.");
459 frames
[framestkptr
++] = currentframe
;
460 currentframe
= ctrlstkptr
;
463 public void popframe() throws RulesException
{
465 throw new RulesException("Control Stack Underflow",
466 "popframe", "Control Stack underflow.");
468 ctrlstkptr
= currentframe
; // Pop off this frame,
469 currentframe
= frames
[--framestkptr
]; // Then set the currentframe back to its previous value.
472 public IRObject
getFrameValue(int i
) throws RulesException
{
473 if(currentframe
+i
>= ctrlstkptr
){
474 throw new RulesException("OutOfRange","getFrameValue","");
476 return getcs(currentframe
+i
);
479 public void setFrameValue(int i
, IRObject value
) throws RulesException
{
480 if(currentframe
+i
>= ctrlstkptr
){
481 throw new RulesException("OutOfRange","getFrameValue","");
483 setcs(currentframe
+i
, value
);
487 * Push an Object onto the control stack.
490 public void cpush(IRObject o
){
491 ctrlstk
[ctrlstkptr
++]= o
;
495 * Pop the top element from the control stack and return it.
498 public IRObject
cpop(){
499 IRObject r
= ctrlstk
[--ctrlstkptr
];
500 ctrlstk
[ctrlstkptr
]= null;
504 public IRObject
getcs(int i
) throws RulesException
{
506 throw new RulesException("Control Stack Overflow","getcs",
507 "index out of range: "+i
);
510 throw new RulesException("Control Stack Underflow","getcs",
511 "index out of range: "+i
);
516 public void setcs(int i
, IRObject v
) throws RulesException
{
518 throw new RulesException("Control Stack Overflow","setcs",
519 "index out of range: "+i
);
522 throw new RulesException("Control Stack Underflow","getcs",
523 "index out of range: "+i
);
529 * This method evalates a condition, or any other set of PostFix code that produces
530 * a boolean value. The code must only add one element to the data stack, and that
531 * element must have a valid boolean value.
532 * @param c -- Condition to execute
533 * @return -- Returns the boolean value of c
534 * @throws RulesException
536 public boolean evaluateCondition(IRObject c
) throws RulesException
{
537 int stackindex
= datastkptr
; // We make sure the object only produces one boolean.
538 c
.execute(this); // Execute the condition.
539 if(datastkptr
-1 != stackindex
){
540 throw new RulesException("Stack Check Exception","Evaluation of Condition","Stack not balanced");
542 return datapop().booleanValue();
545 * This method executes an action, or any other set of Postfix code. This code can
546 * have side effects, but it cannot change the depth of the data stack.
547 * @param c -- Object to execute
548 * @throws RulesException
550 public void evaluate(IRObject c
) throws RulesException
{
551 int stackindex
= datastkptr
;
553 if(datastkptr
!= stackindex
){
554 throw new RulesException("Stack Check Exception","Evaluation of Action","Stack not balanced");
559 * Looks up the entity stack for an Entity that defines an
560 * attribute that matches the name provided. When such an Entity
561 * with an attribute that matches the name is found, that Entity
562 * is returned. A null is returned if no match is found, which
563 * means no Entity on the entity Stack defines an attribute with
564 * a name that mathches the name provided.
567 public IREntity
findEntity(RName name
) throws RulesException
{
568 RName entityname
= name
.getEntityName(); // If the RName does not spec an Enttiy Name
569 if(entityname
== null){ // then we simply look for the RName in each
570 for(int i
=entitystkptr
-1;i
>=0;i
--){ // entity on the Entity Stack.
571 IREntity e
= entitystk
[i
];
572 if(e
.containsAttribute(name
)) return e
;
574 }else{ // Otherwise, we insist that the Entity name
575 for(int i
=entitystkptr
-1;i
>=0;i
--){ // match as well as insist that the Entity
576 IREntity e
= entitystk
[i
]; // have an attribute that matches this name.
577 if(e
.getName().equals(entityname
)){
578 if(e
.containsAttribute(name
)){
585 return null; // No matach found? return a null.
589 * Looks up the Entity Stack and returns the value for the
590 * given named attribute.
592 * When getting data out of the rules Engine, it is useful to
593 * take string values rather than RNames. This should never be
594 * done within the Rules Engine where RNames should be the
597 * This routine simply returns a null if an error occurs or if
598 * the name is undefined.
600 public IRObject
find(String name
){
602 return find(RName
.getRName(name
));
603 } catch (RulesException e
) {
609 * Looks up the entity stack and returns the entity which
610 * defines the value of the given attribute.
612 * When getting data out of the rules Engine, it is useful to
613 * take string values rather than RNames. This should never be
614 * done within the Rules Engine where RNames should be the
617 * This routine simply returns a null if an error occurs or if
618 * the name is undefined.
620 public IREntity
findEntity(String name
){
622 return findEntity(RName
.getRName(name
));
623 } catch (RulesException e
) {
629 * Looks up the entity stack for a match for the RName. When a match is found, the
630 * value is returned. A null is returned if no match is found.
633 public IRObject
find(RName name
) throws RulesException
{
634 IREntity entity
= findEntity(name
);
635 if(entity
== null) return null;
636 return entity
.get(name
);
640 * Looks up the entity stack for a match for the RName. When a match is found, the
641 * value is placed there and a true is returned.
642 * If no match is found, def returns false.
646 public boolean def(RName name
, IRObject value
, boolean protect
) throws RulesException
{
648 RName entityname
= name
.getEntityName();
651 for(int i
=entitystkptr
-1;i
>=0;i
--){
652 IREntity e
= entitystk
[i
];
653 if(!e
.isReadOnly() && (entityname
== null || e
.getName().equals(entityname
) )){
654 REntityEntry entry
= e
.getEntry(name
);
655 if(entry
!=null &&(!protect
|| entry
.writable
)){
656 if(testState(TRACE
)){
657 traceTagBegin("def", "entity='"+e
.postFix()+"' name='"+name
.stringValue()+"'");
658 traceTagEnd("def",value
.postFix());
668 public RDecisionTable
getCurrentTable() {
671 public void setCurrentTable(RDecisionTable currentTable
) {
672 this.currentTable
= currentTable
;
675 * Condition, Action, Context, etc.
676 * @return the currentTableSection
678 public String
getCurrentTableSection() {
679 return currentTableSection
;
682 * Condition, Action, Context, etc.
683 * @param currentTableSection the currentTableSection to set
685 public void setCurrentTableSection(String currentTableSection
,int number
) {
686 this.currentTableSection
= currentTableSection
;
687 this.numberInSection
= number
;
690 * Condition number, context number, initial Action number, etc. -1 means not set
691 * @return the numberInSection
693 public int getNumberInSection() {
694 return numberInSection
;
697 * Condition number, context number, initial Action number, etc. -1 means not set
698 * @param numberInSection the numberInSection to set
700 public void setNumberInSection(int numberInSection
) {
701 this.numberInSection
= numberInSection
;