initial load
[DTRules.git] / DTRules / src / main / java / com / dtrules / session / DTState.java
blob71509127ab9ba8f3f1a5a2bda6021c5a78fd14c1
1 /*
2 * $Id$
3 *
4 * Copyright 2004-2007 MTBJ, Inc.
5 *
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
9 *
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.
17 */
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
42 //
43 try{
44 calendar = new GregorianCalendar();
45 }catch(Throwable t){}
48 private RDecisionTable currentTable;
49 private String currentTableSection;
50 private int numberInSection;
52 /**
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
58 * interpreter.
60 * The Entity stack is used to define the context for associating
61 * attributes with values. This is much like the PostScript Dictionary
62 * Stack.
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];
74 int ctrlstkptr = 0;
75 int datastkptr = 0;
76 int entitystkptr = 0;
78 int frames[] = new int[stklimit];
79 int framestkptr = 0;
80 int currentframe = 0;
82 final IRSession session;
84 ArrayList<String> tagstk = new ArrayList<String>();
85 int tracePoint = 0;
86 boolean newline = true;
89 public long seed = 0x711083186866559L;
90 public Random rand = new Random(seed);
91 /**
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){
122 out = debugtrace;
123 err = error;
127 * We always print the error stream. But this may not
128 * be true forever.
129 * @param s
130 * @return
132 public boolean error(String s){
133 err.print(s);
134 return true;
137 public void traceStart(){
138 setState(TRACE);
139 tracePoint = 0;
140 traceTagBegin("DTRulesTrace",null);
142 public void traceEnd() throws RulesException {
143 traceTagEnd("DTRulesTrace",null);
144 clearState(TRACE);
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.
152 * @return
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
162 * Standard out.
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+">":">"));
171 newline = false;
172 tagstk.add(tag);
176 * Prints a single tag, i.e. "<tag attribs />"
177 * @param tag
178 * @param 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+"/>":"/>"));
187 newline = true;
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.
206 * 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;
216 if(index<0){
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+">");
227 newline=true;
232 * Prints a string to the output file if DEBUG is on.
233 * If ECHO is set, then the output is also echoed to
234 * Standard out.
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){
241 System.out.print(s);
243 s = GenericXMLParser.encode(s);
244 out.print("<dbg>"+s+"</dbg>");
245 newline=false;
246 return true;
248 return false;
252 * Prints the Data Stack, Entity Stack, and Control Stack to
253 * the debugging output stream.
255 public void pstack() {
256 try{
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
281 * stack.
282 * @return data stack depth
284 public int ddepth(){
285 return datastkptr;
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{
294 if(i>=datastkptr){
295 throw new RulesException(
296 "Data Stack Overflow",
297 "getds",
298 "index out of range: "+i);
300 if(i<0){
301 throw new RulesException(
302 "Data Stack Underflow",
303 "getds",
304 "index out of range: "+i);
306 return datastk[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{
316 if(i>=entitystkptr){
317 throw new RulesException(
318 "Entity Stack Overflow", "getes",
319 "index out of range: "+i);
321 if(i<0){
322 throw new RulesException(
323 "Entity Stack Underflow", "getes",
324 "index out of range: "+i);
326 return entitystk[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.
333 * @param rs
335 DTState(IRSession rs){
336 session = rs;
340 * Get Session
341 * @return the session assocaited with this state
343 public IRSession getSession(){ return session; }
346 * Returns the index of the Entity Stack.
347 * @return
349 public int edepth () { return entitystkptr; }
351 * Pushes an IRObject onto the data stack.
352 * @param o
353 * @throws RulesException
355 public void datapush(IRObject o) throws RulesException {
356 if(datastkptr>=1000) {
357 throw new RulesException(
358 "Data Stack Overflow",
359 o.stringValue(),
360 "Data Stack overflow.");
362 datastk[datastkptr++]=o;
365 * Pops an IRObject off the data stack and returns that object.
366 * @return
367 * @throws RulesException
369 public IRObject datapop() throws RulesException {
370 if(datastkptr<=0) {
371 throw new RulesException( "Data Stack Underflow", "datapop()", "Data Stack underflow.");
373 IRObject rval = datastk[--datastkptr];
374 datastk[datastkptr]=null;
375 return(rval);
379 * Pushes an entity on the entity stack.
380 * @param o
381 * @throws RulesException
383 public void entitypush(IREntity o) throws RulesException {
384 if(entitystkptr>=1000) {
385 throw new RulesException("Entity Stack Overflow",
386 o.stringValue(),
387 "Entity Stack overflow.");
389 entitystk[entitystkptr++]=o;
392 * Pops an Entity off of the Entity stack.
393 * @return
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;
403 return(rval);
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.)
409 * @return
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];
418 return(rval);
422 * Test to see if the given flag is set.
423 * @param flag
424 * @return
426 public boolean testState(int flag){
427 return (state&flag)!=0;
431 * Clear the given flag (By and'ing the not of the flag
432 * into the state)
434 public void clearState(int flag){
435 state &= flag^0xFFFFFFFF;
439 * Set the given flag by or'ing the flag into the state
440 * @param flag
442 public void setState(int flag){
443 state |= flag;
448 * Returns the current depth of the control stack.
449 * @return
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{
464 if(framestkptr<=0){
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.
488 * @param o
490 public void cpush(IRObject o){
491 ctrlstk[ctrlstkptr++]= o;
495 * Pop the top element from the control stack and return it.
496 * @return
498 public IRObject cpop(){
499 IRObject r = ctrlstk[--ctrlstkptr];
500 ctrlstk[ctrlstkptr]= null;
501 return r;
504 public IRObject getcs(int i) throws RulesException{
505 if(i>=ctrlstkptr){
506 throw new RulesException("Control Stack Overflow","getcs",
507 "index out of range: "+i);
509 if(i<0){
510 throw new RulesException("Control Stack Underflow","getcs",
511 "index out of range: "+i);
513 return ctrlstk[i];
516 public void setcs(int i, IRObject v) throws RulesException{
517 if(i>=ctrlstkptr){
518 throw new RulesException("Control Stack Overflow","setcs",
519 "index out of range: "+i);
521 if(i<0){
522 throw new RulesException("Control Stack Underflow","getcs",
523 "index out of range: "+i);
525 ctrlstk[i] = v;
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;
552 c.execute(this);
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.
565 * @param name
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)){
579 return e;
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
595 * coin of the realm.
597 * This routine simply returns a null if an error occurs or if
598 * the name is undefined.
600 public IRObject find(String name){
601 try {
602 return find(RName.getRName(name));
603 } catch (RulesException e) {
604 return null;
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
615 * coin of the realm.
617 * This routine simply returns a null if an error occurs or if
618 * the name is undefined.
620 public IREntity findEntity(String name){
621 try {
622 return findEntity(RName.getRName(name));
623 } catch (RulesException e) {
624 return null;
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.
631 * @param name
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.
643 * @param name
644 * @param value
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());
661 e.put(name, value);
662 return true;
666 return false;
668 public RDecisionTable getCurrentTable() {
669 return currentTable;
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;