capture
[rrq/gorite.git] / com / intendico / gorite / Data.java
1 /*********************************************************************
2 Copyright 2012, Ralph Ronnquist.
3
4 This file is part of GORITE.
5
6 GORITE is free software: you can redistribute it and/or modify it
7 under the terms of the Lesser GNU General Public License as published
8 by the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 GORITE is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14 License for more details.
15
16 You should have received a copy of the Lesser GNU General Public
17 License along with GORITE.  If not, see <http://www.gnu.org/licenses/>.
18 **********************************************************************/
19
20 package com.intendico.gorite;
21
22 import com.intendico.data.Query;
23 import com.intendico.data.Ref;
24
25 import java.util.Enumeration;
26 import java.util.Formatter;
27 import java.util.HashSet;
28 import java.util.Hashtable;
29 import java.util.Iterator;
30 import java.util.Vector;
31 import java.util.Observable;
32 import java.util.Observer;
33
34 /**
35  * This is a container class for data elements that goals use and
36  * produce.
37  *
38  * <p> The data that a business process is instantiated for has an
39  * internal structure which allows references to individual data
40  * elements, and that a data element is either singular or a plurality
41  * of other data elements.
42  *
43  * <p> A model includes data references, primarily in connection with
44  * task goals where the data references identify the data elements
45  * that the associated tasks are to operate on, but also in connection
46  * with repeat goals, which get instantiated for the constituent
47  * elements of a plural element.
48  *
49  * <p> Any actual transformation of data is done in tasks, and the
50  * business process model is limited to data reference.
51  *
52  * <p> A task is associated with two collections of data elements,
53  * known as inputs and results. The association is defined by means of
54  * using data element names, which are symbols that refer to
55  * individual or groups of data elements.
56  *
57  * <p> A data object is a container for data references, which are
58  * names of inidividual or groups of data elements.
59  */
60 public class Data {
61
62     /**
63      * This is a base class for data elements.
64      */
65     public class Element {
66
67         /**
68          * The name of this data element.
69          */
70         public final String name;
71
72         /**
73          * The values of this data element.
74          */
75         public Vector/*<Object>*/ values = new Vector/*<Object>*/();
76
77         /**
78          * Constructor with initial value
79          */
80         public Element(String n,Object v) {
81             name = n;
82             if ( v != null )
83                 values.insertElementAt( v, 0 );
84         }
85
86         /**
87          * Constructor.
88          */
89         public Element(String n) {
90             this( n, null );
91         }
92
93         /**
94          * Tracking whether this element is ready or not.
95          */
96         public boolean ready = false;
97
98         /**
99          * Mark data as ready.
100          */
101         public synchronized void markReady() {
102             ready = true;
103         }
104
105         /**
106          * Utility method to set value and mark ready.
107          */
108         public void set(Object v)
109         {
110             set( v, true );
111         }
112
113         /**
114          * Utility method to set value and maybe mark ready.
115          */
116         public void set(Object v,boolean mark)
117         {
118             values.insertElementAt( v, 0 );
119             if ( goldfish )
120                 forget( 1 );
121             if ( mark )
122                 markReady();
123         }
124
125         /**
126          * Utility method to return top of values.
127          */
128         public Object get() {
129             return values.size() > 0? values.get( 0 ) : null ;
130         }
131
132         /**
133          * Utility method to return and remove top of values.
134          */
135         public Object pop() {
136             if ( values.size() == 0 )
137                 return null;
138             return values.remove( 0 );
139         }
140
141         /**
142          * Utility method to forget all but N values. A given N less
143          * than 1 results in silently using 1.
144          */
145         public void forget(int n) {
146             if ( n >= 0 ) {
147                 while ( values.size() > n ) {
148                     values.remove( n );
149                 }
150             } else {
151                 while ( n++ < 0 && values.size() > 0 ) {
152                     values.remove( 0 );
153                 }
154             }
155         }
156
157         /**
158          * Returns the i:th sub element, if any, or null otherwise.
159          */
160         public Object get(int i)
161         {
162             return ( i < values.size() )? values.get( i ): null;
163         }
164
165         /**
166          * Returns the number of values.
167          */
168         public int size() {
169             return values.size();
170         }
171
172         /**
173          * Text representation of this data element.
174          */
175         public String toString() {
176             /********* 1.5
177             Formatter f = new Formatter( new StringBuilder() );
178             f.format( "\"%s\" ", name );
179             if ( ready ) {
180                 f.format( "%s", values.toString() );
181             } else {
182                 f.format( "[not ready]" );
183             }
184             return f.toString();
185             **********/
186             StringBuilder s = new StringBuilder();
187             s.append( "\"" );
188             s.append( name );
189             s.append( "\" " );
190             if ( ready ) {
191                 s.append( values.toString() );
192             } else {
193                 s.append( "[not ready]" );
194             }
195             return s.toString();
196         }
197     }
198
199     /**
200      * Utility method to attach an observer to a given observable, to
201      * be used for triggering continuation of blocked intention.
202      */
203     public void setTrigger(Observable s) {
204         getBindings().setTrigger( s );
205     }
206
207     /**
208      * Utility method to attach an observer to a given query, to be
209      * used for triggering continuation of blocked intention. The
210      * query is reset and re-tested upon notification, and if true,
211      * the blocked intention is allowed to continue.
212      */
213     public void setTrigger(Query q) {
214         getBindings().setTrigger( q );
215     }
216
217     /**
218      * Utility method to remove a trigger.
219      */
220     public void clearTrigger() {
221         getBindings().clearTrigger();
222     }
223
224     /**
225      * Utility method to check whether a goal instance execution is
226      * running or not. All instance executions are considered running
227      * unless there a trigger is set via a {@link #setTrigger
228      * setTrigger} call, and still armed.
229      */
230     public boolean isRunning(String thread_name) {
231         return ! ((Bindings) bindings.get( thread_name )).monitoring ;
232     }
233
234     /**
235      * Utility method to attach an observer on the intention-local
236      * bindings. The observer will then be notified when any trigger
237      * (see {@link #setTrigger setTrigger}) fires.
238      */
239     public void addObserver(String thread_name,Observer x) {
240         ((Bindings) bindings.get( thread_name )).addObserver( x );
241     }
242
243     /**
244      * Utility method to remove a trigger observer.
245      */
246     public void deleteObserver(String thread_name,Observer x) {
247         ((Bindings) bindings.get( thread_name )).deleteObserver( x );
248     }
249     
250     /**
251      * A class for allowing transient name space split.
252      */
253     public class Bindings extends Observable {
254
255         /**
256          * The intention thread name that caused this Bindings
257          * object.
258          */
259         private String name;
260
261         /**
262          * A bindings-local flag to indicate that the associated
263          * intention is has an unblocking monitor.
264          */
265         public boolean monitoring = false;
266
267         /**
268          * Utility method to clear the monitoring flag.
269          */
270         void clearTrigger() {
271             monitoring = false;
272         }
273
274         /**
275          * Utility method to clear the monitoring flag and propagate
276          * notification to any observer.
277          */
278         void clearMonitoring() {
279             if ( monitoring ) {
280                 if ( Goal.isTracing() ) {
281                     System.err.println(
282                         "** Clearing monitoring  for " + name );
283                 }
284                 monitoring = false;
285                 setChanged();
286                 notifyObservers();
287                 unsetFlag();
288                 //setFlag(); // and wake executor....
289                 Performer p = (Performer) getValue( Goal.PERFORMER );
290                 if ( p != null ) {
291                     p.signalExecutor();
292                 }
293             }
294         }
295
296         /**
297          * Utility method to attach an Observer to the given
298          * Observable, to capture its notification as a trigger.
299          */
300         public void setTrigger(Observable s) {
301             monitoring = true;
302             setFlag();
303             s.addObserver( new Observer() {
304                     public void update(Observable x,Object y) {
305                         if ( Goal.isTracing() ) {
306                             System.err.println(
307                                 "** Observer updated by " + x +
308                                 " for " + name );
309                         }
310                         if ( monitoring )
311                             clearMonitoring();
312                         x.deleteObserver( this );
313                     }
314                 } );
315         }
316
317         /**
318          * Utility class to observe a query, to allow an intention to
319          * continue when the query becomes true.
320          */
321         private class QueryMonitor implements Observer {
322
323             /**
324              * The query being observed.
325              */
326             Query query;
327
328             /**
329              * Constructor.
330              */
331             QueryMonitor(Query q) {
332                 monitoring = true;
333                 query = q;
334                 if ( q != null )
335                     q.addObserver( this );
336                 update( null, null );
337             }
338
339             /**
340              * The Observer callback method. This will call reset()
341              * and next() on the given query, and optionally reset
342              * monitoring and call setFlag().
343              */
344             synchronized public void update(Observable x,Object y) {
345                 if ( Goal.isTracing() ) {
346                     System.err.println(
347                         "** Query " + query + " updated by " + x );
348                 }
349                 if ( ! monitoring ) {
350                     if ( query != null )
351                         query.deleteObserver( this );
352                     return;
353                 }
354                 boolean test = false;
355                 try {
356                     if ( Goal.isTracing() ) {
357                         System.err.println( "** Query reset on " + query );
358                     }
359                     if ( query != null )
360                         query.reset();
361                     if ( Goal.isTracing() ) {
362                         System.err.println( "** Query next on " + query );
363                     }
364                     test = ( query != null )? query.next() : true;
365                     if ( Goal.isTracing() ) {
366                         System.err.println(
367                             "** Query " + query + " is " + test );
368                     }
369                 } catch (Exception e) {
370                     e.printStackTrace();
371                 }
372                 if ( test ) {
373                     clearMonitoring();
374                     if ( query != null )
375                         query.deleteObserver( this );
376                 }
377                 if ( Goal.isTracing() ) {
378                     System.err.println(
379                         "** Query data: " + Data.this.toString() );
380                 }
381             }
382
383         }
384
385         /**
386          * Utility method to attach an Observer to the given Query, to
387          * capture its becoming true as a trigger.
388          */
389         public void setTrigger(Query q) {
390             new QueryMonitor( q );
391         }
392
393         /**
394          * This is the transient data store for an execution path.
395          */
396         public Hashtable/*<String,Element>*/ elements =
397             new Hashtable/*<String,Element>*/();
398
399         /**
400          * Link to a prior Bindings object; the base of this one.
401          */
402         Bindings prior = null;
403
404         /**
405          * Constructor.
406          */
407         public Bindings(String n,Bindings p)
408         {
409             name = n;
410             prior = p;
411         }
412
413         /**
414          * Special constructor to transiently bind a given name to its
415          * i:th sub element, if it exists.
416          */
417         public Bindings(String n,Bindings p,String name,int i)
418         {
419             this.name = n;
420             prior = p;
421             if ( name != null ) {
422                 Element e = Data.this.find( p, name );
423                 if ( e != null ) {
424                     Object v = e.get( i );
425                     if ( v != null ) {
426                         Element x = new Element( name, v );
427                         x.markReady();
428                         elements.put( name, x );
429                     }
430                 }
431             }
432         }
433
434         /**
435          * Returns the named data elements, if any, or null otherwise.
436          */
437         public Element find(String name)
438         {
439             return (Element) elements.get( name );
440         }
441
442         /**
443          * Utility method to create an Element instance.
444          */
445         public Element create(String name) {
446             Element e = (Element) elements.get( name );
447             if ( e == null ) {
448                 e = new Element( name );
449                 elements.put( name, e );
450             }
451             return e;
452         }
453
454         /**
455          * Utility method that pushes all data except the given one
456          * into the prior bindings.
457          */
458         public void push(String except,HashSet/*<String>*/ h) {
459             if ( prior == null )
460                 return;
461             boolean gf = goldfish;
462             goldfish = false;
463             for ( Enumeration/*<String>*/ i = elements.keys();
464                   i.hasMoreElements(); ) {
465                 String key = (String) i.nextElement();
466                 if ( key.equals( except ) )
467                     continue;
468                 h.add( key );
469                 Element e = prior.create( key );
470                 e.set( ((Element) elements.get( key )).get(), false );
471             }
472             goldfish = gf;
473         }
474
475         /**
476          * Returns a String representation of this Bindings object.
477          */
478         public String toString() {
479             /************ 1.5
480             Formatter f = new Formatter( new StringBuilder() );
481             f.format( "Data.Bindings %s", name );
482             if ( prior != null )
483                 f.format( " [prior = %s]", prior.name );
484             for (Iterator/*<String>* / i =
485                    elements.keySet().iterator(); i.hasNext(); ) {
486             f.format( "\n  %s", elements.get( i.next() ).toString() );
487             }
488             return f.toString();
489             ***************/
490
491             StringBuilder s = new StringBuilder();
492             s.append( "Data.Bindings " );
493             s.append( name );
494             if ( prior != null ) {
495                 s.append( " [prior = " );
496                 s.append( prior.name );
497                 s.append( "]" );
498             }
499             for (Iterator/*<String>*/ i =
500                      elements.keySet().iterator(); i.hasNext(); ) {
501                 s.append( "\n  " );
502                 s.append( elements.get( i.next() ).toString() );
503             }
504             return s.toString();
505         }
506     }
507
508     /**
509      * Flags this data into goldfish mode, which means for data
510      * elements to forget past values when new values are set.
511      */
512     public boolean goldfish = false;
513
514     /**
515      * Keeps the 'global' bindings object.
516      */
517     public Hashtable/*<String,Bindings>*/ bindings =
518         new Hashtable/*<String,Bindings>*/();
519
520     /**
521      * Utility method that links a thread name with the current
522      * bindings. This may be overridden later by parallel goals, which
523      * get their local bindings.
524      */
525     public void link(String thread_name) {
526         bindings.put( thread_name, getBindings() );
527     }
528
529     /**
530      * Utility method to remove a bindings entry.
531      */
532     public void dropBindings(String thread_name) {
533         bindings.remove( thread_name );
534     }
535
536     /**
537      * Utility method to locate an element from within a "thread".
538      */
539     public Element getLocalElement(String thread_name,String name) {
540         Bindings b = (Bindings) bindings.get( thread_name );
541         return find( b != null? b : getBindings(), name );
542     }
543
544     /**
545      * Utility method that creates a thread-local binding for the
546      * given name to its i:th sub element. If name is null, or the
547      * i:th sub element missing, a thread local bindings object is
548      * still created, but without initialising the name.
549      */
550     public synchronized void fork(String name,int i,String thread_name)
551     {
552         bindings.put( thread_name,
553                       new Bindings( thread_name, getBindings(), name, i ) );
554     }
555
556     /**
557      * Utility method to push data. All names of the given HashSet
558      * except the exception are pushd down to be sub elements of the
559      * equally named element of the prior bindings object.
560      */
561     public synchronized void join(
562         String except,HashSet/*<String>*/ h,String thread_name) {
563         if ( h != null )
564             getBindings().push( except, h );
565         dropBindings( thread_name );
566     }
567         
568     /**
569      * Obtains the bindings object chain for the calling thread.
570      */
571     public Bindings getBindings() {
572         Bindings b = (Bindings) bindings.get( thread_name );
573         if ( b == null ) {
574             b = new Bindings( thread_name, null );
575             bindings.put( thread_name, b );
576         }
577         return b;
578     }
579
580     //
581     // Tracking of threads and data dependencies.
582     //
583
584     /**
585      * Holds the current "thread name".
586      */
587     public String thread_name = "X";
588
589     /**
590      * Change thread name and return the prior name.
591      */
592     public String setThreadName(String n) {
593         String old = thread_name;
594         thread_name = n;
595         return old;
596     }
597
598     /**
599      * Determine the call data element name from a given head string
600      * or thread_name string.
601      */
602     public String getArgsName(String name) {
603         int i = name.indexOf( '"' );
604         if ( i < 0 )
605             i = name.length();
606         i = name.lastIndexOf( '*', i );
607         if ( i < 0 ) 
608             i = name.length();
609         return name.substring( 0, i ) + "-ARG";
610     }
611
612     /**
613      * Determine the call data element name from the thread_name, and
614      * return its value.
615      * @see BDIGoal
616      */
617     public Object getArgs(String head) {
618         return getValue( getArgsName( head ) );
619     }
620
621     /**
622      * The most recently assigned arg.
623      */
624     public Object arg;
625
626     /**
627      * Set up the call data agrument for the given instance head name
628      * as given, adding the "-ARG" suffix.
629      */
630     public void setArgs(String name,Object x) {
631         arg = x;
632         if ( x != null ) {
633             setValue( name + "-ARG", x );
634         }
635     }
636
637     //
638     // Normal mode accessors
639     //
640
641     /**
642      * Utility method to obtain data element value.
643      */
644     public Object getValue(String name) {
645         Element e = find( name );
646         return e != null? e.get() : null ;
647     }
648
649     /**
650      * Utility method to obtain data element value as seen by a given
651      * "thread".
652      */
653     public Object getValue(String name,String th_name) {
654         Bindings b = (Bindings) bindings.get( th_name );
655         if ( b == null )
656             return getValue( name );
657         Element e = find( b, name );
658         return e != null? e.get() : getValue( name ) ;
659     }
660
661     /**
662      * Utility method to set data element value.
663      */
664     public Data setValue(String name,Object v) {
665         create( name ).set( v );
666         return this;
667     }
668
669     /**
670      * Utility method to set data element value relative a given
671      * "thread".
672      */
673     public Data setValue(String th_name, String name,Object v) {
674         String old = setThreadName( th_name );
675         create( name ).set( v );
676         setThreadName( old );
677         return this;
678     }
679
680     /**
681      * Utility method to delete all elements for a name along the
682      * whole binding chain.
683      */
684     public void forget(String name) {
685         for ( Bindings b = getBindings(); b != null; b = b.prior ) {
686             b.elements.remove( name );
687         }
688     }
689
690     /**
691      * Utility method to forget all but n values for an element (i.e.,
692      * its "innermost" Binding occurrence).  If n < 0, then -n top
693      * values are forgotten. If n == 0, then everyhing is forgotten,
694      * except that the element remains.
695      */
696     public void forget(String name,int n)
697     {
698         Element e = find( name );
699         if ( e != null ) {
700             e.forget( n );
701         }
702     }
703
704     /**
705      * Utility method to replace top data element value.
706      */
707     public Data replaceValue(String name,Object v)
708     {
709         Element e = create( name );
710         e.pop();
711         e.set( v );
712         return this;
713     }
714
715     /**
716      * Utility method to restore to a prior value. 
717      */
718     public Data restoreValue(String name,Object v) {
719         Element e = create( name );
720         e.pop();
721         if ( goldfish || e.size() == 0 ) {
722             e.set( v );
723         }
724         return this;
725     }
726
727     /**
728      * Find a given data element following the bindings chain of the
729      * calling thread.
730      */
731     public synchronized Element find(String name) {
732         return find( getBindings(), name );
733     }
734
735     /**
736      * Find a given data element following the bindings chain of the
737      * calling thread.
738      */
739     public Element find(Bindings b,String name) {
740         for ( ; b != null; b = b.prior ) {
741             Element e = (Element) b.elements.get( name );
742             if ( e != null )
743                 return e;
744         }
745         return null;
746     }
747
748     /**
749      * Tells whether there is an element of a given name in the
750      * bindings object chain of the calling thread or not
751      */
752     public boolean hasElement(String name)
753     {
754         return find( name ) != null;
755     }
756
757     /**
758      * Utility to tell how many values a data element has.
759      */
760     public int size(String name) {
761         Element e = find( name );
762         return e != null? e.values.size() : 0;
763     }
764
765     /**
766      * Utility method to retrieve an Element instance on the calling
767      * thread's bindings chain, or create one at the head of the chain
768      * if missing.
769      */
770     public synchronized Element create(String name) {
771         Element e = find( name );
772         if ( e == null ) {
773             e = getBindings().create( name );
774         }
775         return e;
776     }
777
778     /**
779      * This method is used for querying about readiness of a data
780      * element. The method returns immediately, with true if the
781      * element is missing or not ready, and false otherwise.
782      */
783     public boolean pending(String name) {
784         Element e = find( name );
785         return e == null || ! e.ready ;
786     }
787
788     /**
789      * Marks a named element as ready. If no such element exists, then
790      * this method will create a new one.
791      */
792     public void ready(String name) {
793         create( name ).markReady();
794     }
795
796     /**
797      * Set data elements from the {@link Ref} objects of the given
798      * {@link Query}.
799      */
800     public void set(Query query) {
801         set( query.getRefs( new Vector/*<Ref>*/() ) );
802     }
803
804     /**
805      * Set data elements from the given {@link Ref} objects, using
806      * {@link #replaceValue}.
807      */
808     public void set(Vector/*<Ref>*/ refs) {
809         for ( Iterator/*<Ref>*/ i = refs.iterator(); i.hasNext(); ) {
810             Ref ref = (Ref) i.next();
811             //setValue( ref.getName(), ref.get() );
812             replaceValue( ref.getName(), ref.get() );
813         }
814     }
815
816     /**
817      * Set the {@link Ref} objects of a {@link Query} from data
818      * elements.
819      */
820     public void get(Query query,boolean keep) {
821         get( query.getRefs( new Vector/*<Ref>*/() ), keep );
822     }
823
824     /**
825      * Set the given {@link Ref} objects from data elements.
826      */
827     //@SuppressWarnings("unchecked")
828     public void get(Vector/*<Ref>*/ refs,boolean keep) {
829         for ( Iterator/*<Ref>*/ i = refs.iterator(); i.hasNext(); ) {
830             Ref ref = (Ref) i.next();
831             Element e = find( ref.getName() );
832             if ( e != null ) {
833                 ref.set( keep? e.get() : e.pop() );
834             }
835         }
836     }
837
838     /**
839      * Set the given {@link Ref} object from its data element, without
840      * popping the data element value.
841      */
842     //@SuppressWarnings("unchecked")
843     public void get(Ref/*...*/ [] refs) {
844         for ( int i = 0; i < refs.length; i++ ) {
845             Element e = find( refs[ i ].getName() );
846             if ( e != null )
847                 refs[ i ].set( e.get() );
848         }
849     }
850
851     //
852     // Thread control support
853     //
854
855     /**
856      * A rendezvous flag.
857      */
858     public boolean flag = true;
859
860     /**
861      * Utility method to raise the {@link #flag}.
862      */
863     public synchronized void setFlag() {
864         if ( ! flag ) {
865             flag = true;
866             notifyAll();
867         }
868     }
869     
870     public synchronized void unsetFlag() {
871         if ( flag ) {
872             flag = false;
873             notifyAll();
874         }
875     }
876
877     /**
878      * Utility method to wait for the rendezvous {@link #flag} to be
879      * set.
880      */
881     public synchronized void waitFlag() {
882         while (!flag) {
883             try {
884                 wait();
885             } catch (InterruptedException e) {
886             }
887         }
888         flag = false;
889     }
890
891     /**
892      * Returns a String representation of this Data object.
893      */
894     public String toString() {
895         /************ 1.5
896         Formatter f = new Formatter( new StringBuilder() );
897         boolean nl = false;
898         HashSet/*<String>* / done = new HashSet/*<String>* /();
899         for ( Iterator/*<String>* / i =
900                   bindings.keySet().iterator(); i.hasNext(); ) {
901             String tn = (String) i.next();
902             if ( nl )
903                 f.format( "\n" );
904             Bindings b = bindings.get( tn );
905             String h = "Data.Bindings " + b.name;
906             if ( ! done.contains( b.name ) )
907                 h = b.toString();
908             f.format( "Intention %s --> %s", tn, h );
909             done.add( b.name );
910             nl = true;
911         }
912         return f.toString();
913         **************/
914         StringBuilder s = new StringBuilder();
915         boolean nl = false;
916         HashSet/*<String>*/ done = new HashSet/*<String>*/();
917         for ( Iterator/*<String>*/ i =
918                   bindings.keySet().iterator(); i.hasNext(); ) {
919             String tn = (String) i.next();
920             if ( nl )
921                 s.append( "\n" );
922             Bindings b = (Bindings) bindings.get( tn );
923             String h = "Data.Bindings " + b.name;
924             if ( ! done.contains( b.name ) )
925                 h = b.toString();
926             s.append( "Intention " );
927             s.append( tn );
928             s.append( " --> " );
929             s.append( h );
930             done.add( b.name );
931             nl = true;
932         }
933         return s.toString();
934     }
935 }