From 71418e927337d82a59d767d1d1362599ad731e6f Mon Sep 17 00:00:00 2001 From: Ralph Ronnquist Date: Tue, 8 Dec 2020 00:30:16 +1100 Subject: [PATCH 1/1] capture --- Makefile | 134 ++ classpath | 1 + com/intendico/data/.cvsignore | 1 + com/intendico/data/And.java | 200 ++ com/intendico/data/Condition.java | 183 ++ com/intendico/data/Distinct.java | 99 + com/intendico/data/Equals.java | 109 + com/intendico/data/Inquirable.java | 39 + com/intendico/data/Lost.java | 103 + com/intendico/data/Not.java | 162 ++ com/intendico/data/Or.java | 181 ++ com/intendico/data/Query.java | 144 ++ com/intendico/data/QueryBase.java | 139 ++ com/intendico/data/Ref.java | 259 +++ com/intendico/data/Relation.java | 1160 ++++++++++ com/intendico/data/Rule.java | 89 + com/intendico/data/RuleSet.java | 243 ++ com/intendico/data/Snapshot.java | 249 +++ com/intendico/data/Store.java | 55 + com/intendico/data/addon/Language.java | 464 ++++ .../data/addon/TextQueryCapability.java | 147 ++ com/intendico/gorite/.cvsignore | 1 + com/intendico/gorite/Action.java | 298 +++ com/intendico/gorite/BDIGoal.java | 395 ++++ com/intendico/gorite/BranchingGoal.java | 327 +++ com/intendico/gorite/Capability.java | 427 ++++ com/intendico/gorite/ConditionGoal.java | 117 + com/intendico/gorite/Context.java | 107 + com/intendico/gorite/ContextualGoal.java | 213 ++ com/intendico/gorite/ControlGoal.java | 115 + com/intendico/gorite/Data.java | 935 ++++++++ com/intendico/gorite/DataGoal.java | 83 + com/intendico/gorite/EndGoal.java | 127 ++ com/intendico/gorite/Executor.java | 440 ++++ com/intendico/gorite/FailGoal.java | 90 + com/intendico/gorite/Goal.java | 876 ++++++++ com/intendico/gorite/GoriteError.java | 35 + com/intendico/gorite/LoopEndException.java | 39 + com/intendico/gorite/LoopGoal.java | 111 + .../gorite/ParallelEndException.java | 44 + com/intendico/gorite/ParallelGoal.java | 113 + com/intendico/gorite/Performer.java | 834 +++++++ com/intendico/gorite/Plan.java | 167 ++ com/intendico/gorite/PlanChoiceGoal.java | 181 ++ com/intendico/gorite/PoolExecutor.java | 92 + com/intendico/gorite/Precedence.java | 49 + com/intendico/gorite/RepeatGoal.java | 118 + com/intendico/gorite/SequenceGoal.java | 111 + com/intendico/gorite/Team.java | 500 +++++ com/intendico/gorite/TeamGoal.java | 118 + com/intendico/gorite/TransferGoal.java | 198 ++ com/intendico/gorite/addon/.cvsignore | 1 + com/intendico/gorite/addon/BellBoy.java | 242 ++ com/intendico/gorite/addon/Handshake.java | 101 + com/intendico/gorite/addon/Perceptor.java | 199 ++ com/intendico/gorite/addon/Reflector.java | 259 +++ .../gorite/addon/RemotePerforming.java | 198 ++ com/intendico/gorite/addon/SubGoal.java | 289 +++ com/intendico/gorite/addon/TimeTrigger.java | 98 + .../gorite/addon/TodoGroupParallel.java | 44 + .../gorite/addon/TodoGroupRoundRobin.java | 75 + .../gorite/addon/TodoGroupSkipBlocked.java | 84 + .../gorite/addon/remote/Connection.java | 33 + .../gorite/addon/remote/Connector.java | 28 + .../gorite/addon/remote/RemotePerforming.java | 164 ++ com/intendico/sdp/BNFGrammar.java | 738 ++++++ com/intendico/sdp/Grammar.java | 1971 +++++++++++++++++ com/intendico/sdp/LLBNF.java | 474 ++++ examples/planchoice/Main.java | 193 ++ examples/planchoice/filter | 7 + examples/planchoice/output | 328 +++ examples/ruleset/Main.java | 43 + overview.html | 6 + 73 files changed, 16997 insertions(+) create mode 100644 Makefile create mode 100755 classpath create mode 100644 com/intendico/data/.cvsignore create mode 100644 com/intendico/data/And.java create mode 100644 com/intendico/data/Condition.java create mode 100644 com/intendico/data/Distinct.java create mode 100644 com/intendico/data/Equals.java create mode 100644 com/intendico/data/Inquirable.java create mode 100644 com/intendico/data/Lost.java create mode 100644 com/intendico/data/Not.java create mode 100644 com/intendico/data/Or.java create mode 100644 com/intendico/data/Query.java create mode 100644 com/intendico/data/QueryBase.java create mode 100644 com/intendico/data/Ref.java create mode 100644 com/intendico/data/Relation.java create mode 100644 com/intendico/data/Rule.java create mode 100644 com/intendico/data/RuleSet.java create mode 100644 com/intendico/data/Snapshot.java create mode 100644 com/intendico/data/Store.java create mode 100644 com/intendico/data/addon/Language.java create mode 100644 com/intendico/data/addon/TextQueryCapability.java create mode 100644 com/intendico/gorite/.cvsignore create mode 100644 com/intendico/gorite/Action.java create mode 100644 com/intendico/gorite/BDIGoal.java create mode 100644 com/intendico/gorite/BranchingGoal.java create mode 100644 com/intendico/gorite/Capability.java create mode 100644 com/intendico/gorite/ConditionGoal.java create mode 100644 com/intendico/gorite/Context.java create mode 100644 com/intendico/gorite/ContextualGoal.java create mode 100644 com/intendico/gorite/ControlGoal.java create mode 100644 com/intendico/gorite/Data.java create mode 100644 com/intendico/gorite/DataGoal.java create mode 100644 com/intendico/gorite/EndGoal.java create mode 100644 com/intendico/gorite/Executor.java create mode 100644 com/intendico/gorite/FailGoal.java create mode 100644 com/intendico/gorite/Goal.java create mode 100644 com/intendico/gorite/GoriteError.java create mode 100644 com/intendico/gorite/LoopEndException.java create mode 100644 com/intendico/gorite/LoopGoal.java create mode 100644 com/intendico/gorite/ParallelEndException.java create mode 100644 com/intendico/gorite/ParallelGoal.java create mode 100644 com/intendico/gorite/Performer.java create mode 100644 com/intendico/gorite/Plan.java create mode 100644 com/intendico/gorite/PlanChoiceGoal.java create mode 100644 com/intendico/gorite/PoolExecutor.java create mode 100644 com/intendico/gorite/Precedence.java create mode 100644 com/intendico/gorite/RepeatGoal.java create mode 100644 com/intendico/gorite/SequenceGoal.java create mode 100644 com/intendico/gorite/Team.java create mode 100644 com/intendico/gorite/TeamGoal.java create mode 100644 com/intendico/gorite/TransferGoal.java create mode 100644 com/intendico/gorite/addon/.cvsignore create mode 100644 com/intendico/gorite/addon/BellBoy.java create mode 100644 com/intendico/gorite/addon/Handshake.java create mode 100644 com/intendico/gorite/addon/Perceptor.java create mode 100644 com/intendico/gorite/addon/Reflector.java create mode 100644 com/intendico/gorite/addon/RemotePerforming.java create mode 100644 com/intendico/gorite/addon/SubGoal.java create mode 100644 com/intendico/gorite/addon/TimeTrigger.java create mode 100644 com/intendico/gorite/addon/TodoGroupParallel.java create mode 100644 com/intendico/gorite/addon/TodoGroupRoundRobin.java create mode 100644 com/intendico/gorite/addon/TodoGroupSkipBlocked.java create mode 100644 com/intendico/gorite/addon/remote/Connection.java create mode 100644 com/intendico/gorite/addon/remote/Connector.java create mode 100644 com/intendico/gorite/addon/remote/RemotePerforming.java create mode 100644 com/intendico/sdp/BNFGrammar.java create mode 100644 com/intendico/sdp/Grammar.java create mode 100644 com/intendico/sdp/LLBNF.java create mode 100644 examples/planchoice/Main.java create mode 100755 examples/planchoice/filter create mode 100644 examples/planchoice/output create mode 100644 examples/ruleset/Main.java create mode 100644 overview.html diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b015763 --- /dev/null +++ b/Makefile @@ -0,0 +1,134 @@ +# +# Copyright 2012, Ralph Ronnquist. +# +# Makefile for GORITE +# + +CP := $(shell ./classpath) + +DIRS = com/intendico/gorite +DIRS += com/intendico/data +DIRS += com/intendico/gorite/addon +DIRS += com/intendico/data/addon +DIRS += com/intendico/sdp + +EXGORITE = tests/com/intendico/gorite/examples +EXDATA = tests/com/intendico/data/examples + +SRC := $(foreach D,$(DIRS),$(wildcard $(D)/*.java)) +SOURCES := $(filter-out %/package-info.java,$(SRC)) +CLASSES = $(SOURCES:%.java=%.class) +CURRENT = $(foreach C,$(CLASSES),$(wildcard $(C))) +MISSING = $(filter-out $(CURRENT),$(CLASSES)) +OUTDATED = $(shell find $(DIRS) -name "*.java" \ + $(if $(CURRENT),-newer $(word 1,$(shell ls -1t $(CURRENT))))) + +jar: gorite.jar + +.PHONY: clean compile javadoc + +#JAVASRCVERSION = -source 1.4 -Xlint:unchecked +JAVASRCVERSION = -Xlint:-unchecked + +compile: $(OUTDATED) $(MISSING:%.class=%.java) + CLASSPATH=$(CP) javac $(JAVASRCVERSION) $? + +gorite.jar: $(if $(OUTDATED)$(MISSING),compile) $(SOURCES) + touch gorite.jar + jar uf gorite.jar $(addsuffix /*.class,$(DIRS)) + +install: gorite.jar + install gorite.jar ../../lib/ + +clean: + rm -f $(CLASSES:%.class=%*.class) gorite.jar + rm -rf html + +javadoc: VERSION := v11RC05 +JAVAAPI = http://download.oracle.com/javase/6/docs/api +YEAR = $(shell date +%Y) +DOCARGS = -overview overview.html -private -d html +DOCARGS += -footer "© Copyright $(YEAR), Ralph Ronnquist." +DOCARGS += -header "GORITE Version $(VERSION)" +DOCARGS += -linksource +DOCARGS += -linkoffline "$(JAVAAPI)" pkg/java +DOCARGS += -classpath $(CP) + + + +javadoc: gorite.jar + javadoc $(DOCARGS) $(subst /,.,$(DIRS)) $(TESTS) + #cat HEADER > html/Main.java + #more $(EXGORITE)/spacetravel/[A-Z]*.java >> html/Main.java + +TESTS = $(wildcard $(EXGORITE)/*/*.java) +TESTS += $(wildcard $(EXDATA)/*/*.java) + +SKIP := $(EXGORITE)/mice/ +SKIP += $(EXGORITE)/klotski/ +SKIP += $(EXGORITE)/chapter3_sensors/ + + +TCP = $(shell pwd)/tests:$(shell pwd)/gorite.jar +TDIR = $(filter-out $(SKIP),$(sort $(dir $(TESTS)))) + +.PHONY: tests $(TDIR) .force + +#PROPS = -Dgorite.goal.trace=yes +PROPS = -Xss60k -Xmx200m +#PROPS += -Dgorite.tracer=com.intendico.gorite.addon.ConsoleTracer + +ifdef REGRESSION +$(TDIR): gorite.jar .force + @echo "TEST: $@" + @CLASSPATH=$(TCP) java $(PROPS) $(@:tests/%=%)Main 2>&1 | $@filter > /tmp/tests.log + @diff -N $@output /tmp/tests.log + +$(SKIP): .force + @echo "SKIP: $@" + +tests: gorite.jar $(TESTS) + @$(MAKE) -s $(filter-out ./,$(sort $(dir $?))) 2>&1 | \ + tee regression.$$(date +%Y%m%d-%H%M) + +else + +tests/com/intendico/gorite/examples/fleet/: A = 20 100 +$(TDIR): gorite.jar + @echo "===== Test: $@" + CLASSPATH=$(TCP) javac -Xlint:unchecked $@*.java + CLASSPATH=$(TCP) java $(PROPS) $(@:tests/%=%)Main $(A) + @echo "===== End: $@" + +tests: gorite.jar $(TESTS) + echo "$(TESTS)" + @echo "****************************************************" + @$(MAKE) $(filter-out ./,$(sort $(dir $?))) 2>&1 | \ + tee log.$$(date +%Y%m%d-%H%M) + +endif + +doc/%.html: doc/%.sdf + cd doc; sdf -2html $$(basename $?) + +DOCS = CHANGES + +TARFLAG = --exclude=CVS +BINDIST = gorite.jar html $(DOCS) +SRCDIST = $(BINDIST) $(SRC) $(TESTS) overview.html Makefile classpath +SRCDIST += $(wildcard $(EXGORITE)/*/filter) +SRCDIST += $(wildcard $(EXGORITE)/*/output) + +#SRCDIST += DIFF.baseline + +srcdist: DOCARGS += -linksource +srcdist: javadoc $(DOCS) $(SRCDIST) + mkdir -p dist + tar czf dist/gorite-src-$$(date +%Y%m%d).tgz $(TARFLAG) $(SRCDIST) + +bindist: javadoc $(DOCS) $(BINDIST) + mkdir -p dist + tar czf dist/gorite-$$(date +%Y%m%d).tgz $(TARFLAG) $(BINDIST) + +regression: + $(MAKE) tests diff --git a/classpath b/classpath new file mode 100755 index 0000000..3050976 --- /dev/null +++ b/classpath @@ -0,0 +1 @@ +echo $(pwd) diff --git a/com/intendico/data/.cvsignore b/com/intendico/data/.cvsignore new file mode 100644 index 0000000..6b468b6 --- /dev/null +++ b/com/intendico/data/.cvsignore @@ -0,0 +1 @@ +*.class diff --git a/com/intendico/data/And.java b/com/intendico/data/And.java new file mode 100644 index 0000000..4cafbd9 --- /dev/null +++ b/com/intendico/data/And.java @@ -0,0 +1,200 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Observer; +import java.util.Vector; + +/** + * The And class implements a conjunction of multiple queries. It + * provides a left-to-right short-cut evaluation, which returns + * immediately if a conjunct fails. + */ +public class And implements Query { + + /** + * Holds the conjuncts being combined. + */ + public Query [] conjuncts; + + /** + * Keeps track of which query is current. + */ + public int current; + + /** + * Constructor. + */ + public And(Query/*...*/ [] q) throws Exception { + conjuncts = q; + reset(); + } + + /** + * The {@link Query#copy} method implemented by creating a new + * And object with copies of the conjuncts. + */ + public Query copy(Vector/**/ newrefs) throws Exception { + Query [] q = new Query [ conjuncts.length ]; + for ( int i = 0; i < q.length; i++ ) { + q[i] = conjuncts[i].copy( newrefs ); + } + return new And( q ); + } + + /** + * The {@link Query#reset} method implemented by reverting to the + * first conjunct and resetting it. + */ + public void reset() + throws Exception + { + current = 0; + if ( conjuncts != null && conjuncts.length > 0 ) + conjuncts[ 0 ].reset(); + } + + /** + * The {@link Query#next} method implemented by advancing all + * conjucts to the next match in a left-to-right fashion. Thus, + * the right-most conjunct is advanced to its next match. If the + * conjunct is exhausted, it gets reset, but after that the + * preceding conjunct is advanced to its next match. If that is + * exhausted, then that gets reset, and so on, up to advancing the + * first conjunct until it gets exhausted. + */ + public boolean next() + throws Exception + { + if ( conjuncts == null || conjuncts.length == 0 ) + return true; + while ( current >= 0 ) { + if ( conjuncts[ current ].next() ) { + current += 1; + if ( current < conjuncts.length ) { + conjuncts[ current ].reset(); + continue; + } + current -= 1; + return true; + } + current -= 1; + } + return false; + } + + /** + * The {@link Query#getRefs} method implemented by combining the + * Ref objects of all conjuncts. + */ + public Vector/**/ getRefs(Vector/**/ v) { + if ( conjuncts != null ) { + for ( int i=0; i < conjuncts.length; i++ ) { + conjuncts[ i ].getRefs( v ); + } + } + return v; + } + + /** + * The {@link Query#addObserver} method implemented by adding + * the observer to all the conjuncts. + */ + public void addObserver(Observer x) { + if ( conjuncts != null ) + for ( int i=0; i < conjuncts.length; i++ ) + conjuncts[ i ].addObserver( x ); + } + + /** + * The {@link Query#deleteObserver} method implemented by + * removing the observer from all the conjuncts. + */ + public void deleteObserver(Observer x) { + if ( conjuncts != null ) + for ( int i=0; i < conjuncts.length; i++ ) + conjuncts[ i ].deleteObserver( x ); + } + + /** + * The {@link Query#addable} method implemented by forwarding to all + * conjuncts. + */ + public boolean addable() + { + for ( int i=0; i < conjuncts.length; i++ ) + if ( ! conjuncts[ i ].addable() ) + return false; + return true; + } + + /** + * The {@link Query#add} method implemented by forwarding to all + * conjuncts. + */ + public boolean add() + { + boolean added = false; + if ( conjuncts != null ) + for ( int i=0; i < conjuncts.length; i++ ) + added |= conjuncts[ i ].add(); + return added; + } + + /** + * Implements {@link Query#removable} by finding a conjunct that + * is removable. + */ + public boolean removable() + { + for ( int i=0; i < conjuncts.length; i++ ) + if ( conjuncts[ i ].removable() ) + return true; + return false; + } + + /** + * Implements {@link Query#remove} by forawrding to the first + * conjunct that is removable. + */ + public boolean remove() { + for ( int i=0; i < conjuncts.length; i++ ) + if ( conjuncts[ i ].removable() ) + return conjuncts[ i ].remove(); + return false; + } + + /** + * Returns a String representation of the And object. + */ + public String toString() { + StringBuffer s = new StringBuffer(); + s.append( "And(" ); + if ( conjuncts != null ) + for ( int i=0; i < conjuncts.length; i++ ) { + if ( i > 0 ) + s.append( "," ); + s.append( " " ); + s.append( conjuncts[ i ].toString() ); + } + s.append( " )" ); + return s.toString(); + } +} diff --git a/com/intendico/data/Condition.java b/com/intendico/data/Condition.java new file mode 100644 index 0000000..cac0cf6 --- /dev/null +++ b/com/intendico/data/Condition.java @@ -0,0 +1,183 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Observer; +import java.util.Vector; +import java.util.Iterator; + +/** + * A utility class for including a boolean method in query + * processing. The typical use is as an inline extension that + * implements the {@link #condition} method. + */ +abstract public class Condition implements Query { + + /** + * An always true Condition query. + */ + public static Condition TRUE = new Condition() { + public boolean condition() { + return true; + } + }; + + /** + * Control flag to mark whether the condition has been tested or + * not. + */ + public boolean tested = false; + + /** + * Cache of initially unbound variables. These are captured on + * construction, just in case there is an extension that takes + * variables. In that case, the next() method on failing must + * restore the incoming unbound variables to be unbound. + */ + public Vector variables; + + /** + * The {@link Query#reset} method implemented by noting the + * condition as not tested. + */ + public void reset() throws Exception { + tested = false; + } + + /** + * The {@link Query#copy} method implemented by using this same + * object. + */ + public Query copy(Vector/**/ newrefs) throws Exception { + return this; + } + + /** + * The {@link Query#next} method implemented by returning as per + * the {@link #condition} method on its first invocation, then + * returning false without invoking the {@link #condition} method, + * until {@link #reset()}. This method also captures any unbound + * variables (an extension class may have variables), and clears + * these before returning false. + */ + public boolean next() throws Exception { + if ( ! tested ) { + variables = getRefs( new Vector() ); + if ( variables.size() > 0 ) { + for ( Iterator i = variables.iterator(); i.hasNext(); ) { + if ( ((Ref) i.next()).get() != null ) { + i.remove(); + } + } + if ( variables.size() == 0 ) + variables = null; + } else { + variables = null; + } + tested = true; + if ( condition() ) + return true; + } + if ( variables != null ) { + for ( Iterator i = variables.iterator(); i.hasNext(); ) { + ((Ref) i.next()).clear(); + } + } + return false; + } + + /** + * The {@link Query#getRefs} method implemented by adding + * nothing. An actual condition that uses {@link Ref} objects + * should override this method. + */ + public Vector/**/ getRefs(Vector/**/ v) { + return v; + } + + /** + * The {@link Query#addObserver} method implemented by doing + * nothing. An actual condition that is triggering should override + * this method as well as {@link #deleteObserver}. + */ + public void addObserver(Observer x) { + } + + /** + * The {@link Query#deleteObserver} method implemented by doing + * nothing. An actual condition that is triggering should override + * this method as well as {@link #addObserver}. + */ + public void deleteObserver(Observer x) { + } + + /** + * The {@link Query#addable} method is implemented by a reset + * followed by next. + */ + public boolean addable() { + try { + reset(); + return next(); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * The {@link Query#add} method implemented by returning false. An + * actual condition that gets updated should override this method. + */ + public boolean add() { + return false; + } + + /** + * Implements {@link Query#removable} by calling {@link #addable} + */ + public boolean removable() + { + return addable(); + } + + /** + * Implements {@link Query#remove} by returning false; + */ + public boolean remove() { + return false; + } + + /** + * The actual condition returning true or + * false. If it is appropriate to re-test the condition + * several times before resetting, the actual condition may also + * set the {@link #tested} flag. + */ + abstract public boolean condition() throws Exception; + + /** + * Returns a {@link java.lang.String} representation of the + * condition object. + */ + public String toString() { + return "Condition#" + System.identityHashCode( this ); + } +} diff --git a/com/intendico/data/Distinct.java b/com/intendico/data/Distinct.java new file mode 100644 index 0000000..7304ed3 --- /dev/null +++ b/com/intendico/data/Distinct.java @@ -0,0 +1,99 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Vector; +import java.util.HashSet; + +/** + * The Distinct class is a query to ensure that a collection of given + * {@link Ref} objects have distinct bindings. + */ +public class Distinct extends Condition { + + /** + * The Ref objects concerned. + */ + public Object [] refs; + + /** + * Constructor; + */ + public Distinct(Object/*...*/ [] r) { + refs = r; + } + + /** + * The {@link Query#copy} method implemented by creating a new + * Distinct object with the copies of the refs. + */ + //@SuppressWarnings("unchecked") + public Query copy(Vector/**/ newrefs) throws Exception { + Object [] nrefs = new Object [ refs.length ]; + for ( int i = 0; i < refs.length; i++ ) { + if ( refs[ i ] instanceof Ref ) { + nrefs[ i ] = ((Ref) refs[ i ]).find( newrefs ); + } else { + nrefs[ i ] = refs[ i ]; + } + } + return new Distinct( nrefs ); + } + + /** + * The distinction condition + * @return false if any {@link Ref} object is + * null, or equal to any other {@link Ref} object, + * otherwise true. + */ + public boolean condition() { + HashSet/**/ v = new HashSet/**/(); + for ( int i = 0; i < refs.length; i++ ) { + Object r = refs[ i ]; + Object x = Ref.deref( r ); + if ( x == null || v.contains( x ) ) + return false; + v.add( x ); + } + return true; + } + + /** + * The {@link Query#getRefs} method implemented by adding any Ref + * object not already in the vector. + */ + public Vector/**/ getRefs(Vector/**/ v) { + for ( int i = 0; i < refs.length; i++ ) { + Object r = refs[ i ]; + if ( r instanceof Ref ) { + if ( ! v.contains( (Ref) r ) ) + v.add( (Ref) r ); + } + } + return v; + } + + /** + * Returns the textual representation of the query. + */ + public String toString() { + return "Distinct(" + Ref.toString( refs ) + ")"; + } +} diff --git a/com/intendico/data/Equals.java b/com/intendico/data/Equals.java new file mode 100644 index 0000000..dcc4ca9 --- /dev/null +++ b/com/intendico/data/Equals.java @@ -0,0 +1,109 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Vector; + +/** + * The Equals class is a query to ensure that two or more given + * objects are equal, where any or both of the objects may be {@link + * Ref} objects. If a {@link Ref} object is null, it is set to the + * other value. + */ +public class Equals extends Condition { + + /** + * The distinct values concerned. + */ + public Object [] values; + + /** + * Constructor. + */ + public Equals(Object/*...*/ [] v) { + values = v; + } + + /** + * The {@link Query#copy} method implemented by creating a new + * Except object with a copy of the ref, but the same values + */ + //@SuppressWarnings("unchecked") + public Query copy(Vector/**/ newrefs) throws Exception { + Object [] v = new Object [ values.length ]; + for ( int i = 0; i < values.length; i++ ) { + if ( values[ i ] instanceof Ref ) { + v[ i ] = ((Ref)values[ i ]).find( newrefs ); + } else { + v[ i ] = values[ i ]; + } + } + return new Equals( v ); + } + + /** + * The exception condition. + * @return false if the {@link Ref} object is equal to + * any value, otherwise true. Note that any Ref object as + * value is also dereferenced. + */ + public boolean condition() { + Object x = null; + for ( int i = 0; i < values.length; i++ ) { + Object value = values[ i ]; + Object y = Ref.deref( value ); + if ( x != null && ! x.equals( y ) ) + return false; + x = y; + } + return true; + } + + /** + * The {@link Query#getRefs} method implemented by adding and Ref + * not already contained in the vector. + */ + public Vector/**/ getRefs(Vector/**/ v) { + for ( int i = 0; i < values.length; i++ ) { + Object x = values[ i ]; + if ( x instanceof Ref && ! v.contains( x ) ) + v.add( (Ref) x ); + } + return v; + } + + /** + * Returns the textual representation of the query. + */ + public String toString() { + StringBuilder s = new StringBuilder( "equals(" ); + for ( int i = 0; i < values.length; i++ ) { + Object x = values[ i ]; + s.append( "," ); + if ( x instanceof Ref ) { + s.append( ((Ref) x).getName() ); + s.append( "=" ); + } + s.append( x.toString() ); + } + s.append( ")" ); + return s.toString(); + } +} diff --git a/com/intendico/data/Inquirable.java b/com/intendico/data/Inquirable.java new file mode 100644 index 0000000..84a7302 --- /dev/null +++ b/com/intendico/data/Inquirable.java @@ -0,0 +1,39 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +/** + * This interface defines the requirements of an inquirable data + * structure, which basically is a named {@link Query} factory. + */ +public interface Inquirable { + + /** + * Interface method to obtain the name of this Inquirable. + */ + public String getName(); + + /** + * Interface method for providing an access {@link Query} given a + * collection of values and {@link Ref} objects. + */ + public Query get(Object/*...*/ [] arguments) throws Exception ; + +} diff --git a/com/intendico/data/Lost.java b/com/intendico/data/Lost.java new file mode 100644 index 0000000..c7616a8 --- /dev/null +++ b/com/intendico/data/Lost.java @@ -0,0 +1,103 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Observer; +import java.util.Vector; + +/** + * The Lost class is a {@link Snapshot} that enumerates the lost + * tuples rather than the added tuples, and it also treats adding and + * removing like {@link Not}. + */ +public class Lost extends Snapshot { + + /** + * Constructor; + */ + public Lost(Query q) { + super( q ); + } + + /** + * Alternative constructor with synchronization object. + */ + public Lost(Object s,Query q) { + super( s, q ); + } + + /** + * The {@link Query#copy} method implemented by creating a new + * Lost with a copy of the monitored {@link #query}, and sharing + * any {@link #sync} object. + */ + public Query copy(Vector/**/ newrefs) throws Exception { + return new Lost( sync, query.copy( newrefs ) ); + } + + /** + * The {@link Query#reset} method implemented by forwarding to the + * wrapped query. + */ + public void reset() throws Exception { + super.reset(); + bindings = lost.iterator(); + } + + /** + * Implements {@link Query#addable} by calling {@link + * Query#removable} on the wrapped query. + */ + public boolean addable() { + return query.removable(); + } + + /** + * Implements {@link Query#add} by calling {@link Query#remove} on + * the wrapped query. + */ + public boolean add() { + return query.remove(); + } + + /** + * Implements {@link Query#removable} by calling {@link + * Query#addable} on the wrapped query. + */ + public boolean removable() { + return query.addable(); + } + + /** + * Implements {@link Query#remove} by calling {@link Query#add} on + * the wrapped query. + */ + public boolean remove() { + return query.add(); + } + + /** + * Returns the textual representation of the wrapped query, within + * parentheses. + */ + public String toString() { + return "Lost(" + query + ")"; + } +} diff --git a/com/intendico/data/Not.java b/com/intendico/data/Not.java new file mode 100644 index 0000000..380b736 --- /dev/null +++ b/com/intendico/data/Not.java @@ -0,0 +1,162 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Vector; +import java.util.Iterator; +import java.util.Observer; + +/** + * The Not class implements a negation-by-failure query, which is true + * only if the wrapped {@link Query} does not provide any valid + * bindings. The Not class also manages input bindings to ensure that + * nothing unbound gets bound by the wrapped query. + */ +public class Not implements Query { + + /** + * Flag to mark whether the query has been tested. + */ + public boolean tested; + + /** + * Holds unbound input variables. + */ + public Vector/**/ unbound; + + /** + * Holds wrapped query. + */ + public Query query; + + /** + * Constructor. + */ + public Not(Query q) throws Exception { + query = q; + reset(); + } + + /** + * Implements {@link Query#copy} by creating a new Not object + * around a copy of the wrapped query. + */ + public Query copy(Vector/**/ newrefs) throws Exception { + return new Not( query.copy( newrefs ) ); + } + + /** + * Implements {@link Query#reset} by capturing unbound Ref + * objects, then forwarding the reset call to the wrapped query. + */ + public void reset() throws Exception { + unbound = new Vector/**/(); + for ( Iterator/**/ i = + query.getRefs( new Vector/**/() ).iterator(); + i.hasNext(); ) { + Ref r = (Ref) i.next(); + if ( r.get() == null ) + unbound.add( r ); + } + tested = false; + query.reset(); + } + + /** + * Implements {@link Query#next} by calling on the wrapped query, + * then remove any bindings. + */ + //@SuppressWarnings("unchecked") + public boolean next() throws Exception { + if ( tested ) + return false; + tested = true; + boolean b = query.next(); + for ( Iterator/**/ i = unbound.iterator(); i.hasNext(); ) { + Ref r = (Ref) i.next(); + r.set( null ); + } + return ! b; + } + + /** + * Implements {@link Query#getRefs} by forwarding to the wrapped + * query. + */ + public Vector/**/ getRefs(Vector/**/ v) { + return query.getRefs( v ); + } + + /** + * Implements {@link Query#addObserver} by forwarding to the + * wrapped query. + */ + public void addObserver(Observer x) { + query.addObserver( x ); + } + + /** + * Implements {@link Query#deleteObserver} by forwarding to the + * wrapped query. + */ + public void deleteObserver(Observer x) { + query.deleteObserver( x ); + } + + /** + * Implements {@link Query#addable} by calling {@link + * Query#removable} on the wrapped query. + */ + public boolean addable() { + return query.removable(); + } + + /** + * Implements {@link Query#add} by calling {@link Query#remove} on + * the wrapped query. + */ + public boolean add() { + return query.remove(); + } + + /** + * Implements {@link Query#removable} by calling {@link + * Query#addable} on the wrapped query. + */ + public boolean removable() { + return query.addable(); + } + + /** + * Implements {@link Query#remove} by calling {@link Query#add} on + * the wrapped query. + */ + public boolean remove() { + return query.add(); + } + + /** + * Returns the textual representation of the wrapped query, within + * parentheses. + */ + public String toString() { + return "(" + query + ")"; + } +} diff --git a/com/intendico/data/Or.java b/com/intendico/data/Or.java new file mode 100644 index 0000000..d18adf5 --- /dev/null +++ b/com/intendico/data/Or.java @@ -0,0 +1,181 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Observer; +import java.util.Vector; + +/** + * The Or class implements a disjunction of multiple queries. + */ +public class Or implements Query { + + /** + * Holds the disjuncts being combined. + */ + public Query [] disjuncts; + + /** + * Keeps track of which query is current. + */ + public int current; + + /** + * Constructor. + */ + public Or(Query/*...*/ [] q) + throws Exception { + disjuncts = q; + reset(); + } + + /** + * The {@link Query#copy} method implemented by creating a new + * Or object with copies of the disjuncts. + */ + public Query copy(Vector/**/ newrefs) throws Exception { + Query [] q = new Query [ disjuncts.length ]; + for ( int i = 0; i < q.length; i++ ) { + q[i] = disjuncts[i].copy( newrefs ); + } + return new Or( q ); + } + + /** + * The {@link Query#reset} method implemented by reverting to the + * first disjunct and resetting it. + */ + public void reset() + throws Exception + { + current = 0; + disjuncts[ 0 ].reset(); + } + + /** + * The {@link Query#next} method implemented by considering all + * disjucts one by one to the next match in a left-to-right fashion. + */ + public boolean next() + throws Exception + { + while ( current < disjuncts.length ) { + if ( disjuncts[ current ].next() ) + return true; + current += 1; + if ( current >= disjuncts.length ) + break; + disjuncts[ current ].reset(); + } + return false; + } + + /** + * The {@link Query#getRefs} method implemented by combining the + * Ref objects of all disjuncts. + */ + public Vector/**/ getRefs(Vector/**/ v) { + for ( int i=0; i < disjuncts.length; i++ ) + disjuncts[ i ].getRefs( v ); + return v; + } + + /** + * The {@link Query#addObserver} method implemented by adding + * the observer to all the disjuncts. + */ + public void addObserver(Observer x) { + for ( int i=0; i < disjuncts.length; i++ ) + disjuncts[ i ].addObserver( x ); + } + + /** + * The {@link Query#deleteObserver} method implemented by + * removing the observer from all the disjuncts. + */ + public void deleteObserver(Observer x) { + for ( int i=0; i < disjuncts.length; i++ ) + disjuncts[ i ].deleteObserver( x ); + } + + /** + * The {@link Query#addable} method implemented by forwarding to all + * disjuncts. + */ + public boolean addable() + { + for ( int i=0; i < disjuncts.length; i++ ) + if ( disjuncts[ i ].addable() ) + return true; + return false; + } + + /** + * The {@link Query#add} method implemented by forwarding to first + * addable disjunct. + */ + public boolean add() + { + for ( int i=0; i < disjuncts.length; i++ ) { + if ( disjuncts[ i ].addable() ) + return disjuncts[ i ].add(); + } + return false; + } + + /** + * Implements {@link Query#removable} by checking that all + * disjuncts are removable. + */ + public boolean removable() + { + for ( int i=0; i < disjuncts.length; i++ ) + if ( ! disjuncts[ i ].removable() ) + return false; + return true; + } + + /** + * Implements {@link Query#remove} by forwarding to all disjuncts. + */ + public boolean remove() { + boolean removed = false; + for ( int i=0; i < disjuncts.length; i++ ) + if ( disjuncts[ i ].removable() ) + removed |= disjuncts[ i ].remove(); + return removed; + } + + /** + * Returns a {@link String} representation of the Or object. + */ + public String toString() { + StringBuffer s = new StringBuffer(); + s.append( "Or(" ); + for ( int i=0; i < disjuncts.length; i++ ) { + if ( i > 0 ) + s.append( "," ); + s.append( " " ); + s.append( disjuncts[ i ].toString() ); + } + s.append( " )" ); + return s.toString(); + } +} diff --git a/com/intendico/data/Query.java b/com/intendico/data/Query.java new file mode 100644 index 0000000..ae0e817 --- /dev/null +++ b/com/intendico/data/Query.java @@ -0,0 +1,144 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Observer; +import java.util.Vector; + +/** + * The Query interface defines methods for query processing, to + * support left-to-right short-cut evaluation with multiple bindings. + * + *

The sequence for query processing would typically be as follows: + *

    + * + *
  1. create the {@link Ref} objects to carry query outputs and + * intermediate values between query conjuncts; + * + *
  2. create the query object structure; + * + *
  3. (optionally) add an observer on the query, in order to + * capture triggerring from the source elements. This is only needed + * when the following query processing is deferred or temporal in some + * fashion; + * + *
  4. invoke the {@link #reset} method. Most {@link Query} + * implementations include an automatic intital {@link #reset}, and + * therefore this call would only be needed at a second or subsequent + * use of the query object structure; + * + *
  5. invoke the {@link #next} method. The first invokation + * establishes the first valid combination of bindings for the {@link + * Ref} objects involved, and subsequent calls establish subsequent + * valid bindings. The {@link #next} method returns false when + * it exhausts the vlid combinations of bindings. + * + *
  6. (optionally) remove the observer previosuly added. + * + *
+ * + *

The following snippet is an illustration: + *

+ * Relation r = new Relation( "parent", String.class, String.class );
+ * Ref p = new Ref( "$parent" );
+ * Ref pc = new Ref( "$child_parent" );
+ * Ref c = new Ref( "$child" );
+ *
+ * Query grandparent = new And( r.get( p, ch ), r.get( ch, c ) );
+ * while ( grandparent.next() ) {
+ *     // Here r, pc and c have a valid combination of bindings
+ *     System.out.println( p + " is grand parent of " + c );
+ * }
+ * 
+ * 
+ */ +public interface Query { + + /** + * The reset() method is invoked in order for the Query to renew + * itself, and provide its binding sequence from the top. The + * Query should renew itself with regard to any changes in the + * input. + */ + public void reset() throws Exception; + + /** + * The next() method is invoked in order for the Query to + * establish the next binding in its output. + */ + public boolean next() throws Exception; + + /** + * The getRefs() method is invoked in order for the Query to + * collect and report all its Ref objects. + */ + public Vector/**/ getRefs(Vector/**/ v); + + /** + * The copy method is invoked for the purpose of obtaining a deep + * copy of a query, and in particular using new {@link Ref} + * objects by the original names. + */ + public Query copy(Vector/**/ newrefs) throws Exception; + + /** + * The addObserver method is invoked for the purpose of adding a + * Query processor to the observable source(s) of the query. + */ + public void addObserver(Observer x); + + /** + * The deleteObserver method is invoked by the Query processor in + * order to "detach" from the observable Query source(s). + */ + public void deleteObserver(Observer x); + + /** + * The addable method is invoked with bound {@link Ref} objects to + * ask whether these bindings could be added. + */ + public boolean addable(); + + /** + * The add method is invoked with bound {@link Ref} objects in + * order to add the current combination to all query sources, if + * possible. + * + * @return true if any source element was updated, and + * false otherwise. + */ + public boolean add(); + + /** + * The removable method is invoked with bound {@link Ref} objects + * to ask whether these bindings could be removed. + */ + public boolean removable(); + + /** + * The remove method is invoked with bound {@link Ref} objects in + * order to remove the current combination to query sources. + * + * @return true if any source element was updated, and + * false otherwise. + */ + public boolean remove(); + +} diff --git a/com/intendico/data/QueryBase.java b/com/intendico/data/QueryBase.java new file mode 100644 index 0000000..f6654e3 --- /dev/null +++ b/com/intendico/data/QueryBase.java @@ -0,0 +1,139 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Observer; +import java.util.Vector; + +/** + * The QueryBase class is a wrapper class that provides transparent + * implementations for the {@link Query} methods. It is intended to be + * base class for simple query implementations that only need to + * override one or a few of the implementations, typically the + * constructor. + */ +public class QueryBase implements Query { + + /** + * Holds a/the sub query wrapped by this QueryBase. + */ + public Query query; + + /** + * Constructor; + */ + public QueryBase() { + } + + /** + * Constructor; + */ + public QueryBase(Query q) { + query = q; + } + + /** + * The {@link Query#copy} method implemented by returning this + * same query. + */ + public Query copy(Vector/**/ newrefs) throws Exception { + return this; + } + + /** + * The {@link Query#reset} method implemented by forwarding to the + * wrapped query. + */ + public void reset() throws Exception { + query.reset(); + } + + /** + * The {@link Query#next} method implemented by forwarding to the + * wrapped query. + */ + public boolean next() throws Exception { + return query.next(); + } + + /** + * The {@link Query#getRefs} method implemented by forwarding to the + * wrapped query. + */ + public Vector/**/ getRefs(Vector/**/ v) { + return query.getRefs( v ); + } + + /** + * The {@link Query#addObserver} method implemented by forwarding + * to the wrapped query. + */ + public void addObserver(Observer x) { + query.addObserver( x ); + } + + /** + * The {@link Query#deleteObserver} method implemented by + * forwarding to the wrapped query. + */ + public void deleteObserver(Observer x) { + query.deleteObserver( x ); + } + + /** + * The {@link Query#addable} method implemented by forwarding to + * the wrapped query. + */ + public boolean addable() { + return query.addable(); + } + + /** + * The {@link Query#add} method implemented by forwarding to the + * wrapped query. + */ + public boolean add() { + return query.add(); + } + + /** + * The {@link Query#removable} method implemented by forwarding to + * the wrapped query. + */ + public boolean removable() { + return query.removable(); + } + + /** + * The {@link Query#remove} method implemented by forwarding to + * the wrapped query. + */ + public boolean remove() { + return query.remove(); + } + + /** + * Returns the textual representation of the wrapped query, within + * parentheses. + */ + public String toString() { + return "(" + query + ")"; + } +} diff --git a/com/intendico/data/Ref.java b/com/intendico/data/Ref.java new file mode 100644 index 0000000..adc497e --- /dev/null +++ b/com/intendico/data/Ref.java @@ -0,0 +1,259 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Vector; +import java.util.Iterator; +import java.io.Serializable; + +/** + * This class is a carrier of values. The Relation class knows of Ref + * objects, so as to allow passing of values between queries. + */ +public class Ref/**/ implements Serializable { + + /** + * Utility method to obtain an actual value of a possible Ref + * object. + */ + public static Object deref(Object x) { + return x instanceof Ref? ((Ref) x).get() : x; + } + + /** + * Utility method that creates an array of Object values from an + * array of Ref + */ + public static Object [] getValues(Ref [] rs) { + Object [] v = new Object [ rs.length ]; + for ( int i = 0; i < rs.length; i++ ) { + v[ i ] = rs[ i ].get(); + } + return v; + } + + /** + * Utility method to set an array of Ref from an array of Object + * values. + */ + //@SuppressWarnings("unchecked") + public static void setValues(Ref [] rs,Object [] v) { + for ( int i = 0; i < rs.length; i++ ) { + rs[ i ].set( Ref.deref( v[ i ] ) ); + } + } + + /** + * Utility method that collects the bindings of given {@link Ref} + * objects into a {@link java.util.Vector}. + */ + public static Vector getBinding(Vector/**/ refs) { + Vector/**/ v = new Vector/**/(); + for ( Iterator/**/ i = refs.iterator(); i.hasNext(); ) + v.add( ((Ref) i.next()).get() ); + return v; + } + + /** + * Utility method that binds the given {@link Ref} objects with + * corresponding values from a {@link java.util.Vector}. + */ + //@SuppressWarnings("unchecked") + public static void bind(Vector/**/ refs,Vector v) { + int c = 0; + for ( Iterator/**/ i = refs.iterator(); i.hasNext(); ) + ((Ref) i.next()).set( deref( v.get( c++ ) ) ); + } + + /** + * Utility method to create a vector of ref objects from a string + * array of names. + */ + public static Vector/**/ create(String [] names) { + Vector/**/ v = new Vector/**/(); + if ( names == null ) + return v; + for ( int i = 0; i < names.length; i++ ) { + v.add( new Ref( names[i] ) ); + } + return v; + } + + /** + * Utility method that invokes {@link #clear} for the given {@link + * Ref} objects with. + */ + public static void clear(Vector/**/ refs) { + for ( Iterator/**/ i = refs.iterator(); i.hasNext(); ) + ((Ref) i.next()).clear(); + } + + /** + * Creates copies of Ref objects. + */ + //@SuppressWarnings("unchecked") + public static Vector/**/ copy(Vector/**/ src) { + Vector/**/ refs = new Vector/**/(); + for ( Iterator/**/ i = src.iterator(); i.hasNext(); ) + refs.add( new Ref( (Ref) i.next() ) ); + return refs; + } + + /** + * Returns a textual representation of a vector of {@link Ref} + * objects. + */ + public static String toString(Vector/**/ refs) { + return toString( (Ref[]) refs.toArray( new Ref [ refs.size() ] ) ); + } + + /** + * Returns a textual representation of an expected array of Ref + * objects. Each Ref object is presented by name and value, using + * {@link #toString()}, but the value is clipped to at most 40 + * characters. The whole things is put within a matching pair of + * curly braces. + */ + public static String toString(Object [] refs) { + StringBuffer s = new StringBuffer(); + s.append( "{" ); + for ( int i = 0; i < refs.length; i++ ) { + if ( i > 0 ) + s.append( "," ); + if ( refs[ i ] instanceof Ref ) { + Ref r = (Ref) refs[ i ]; + s.append( r.getName() ); + s.append( "=" ); + String v = r.toString(); + if ( v.length() > 40 ) + v = v.substring( 0, 37 ) + "..."; + s.append( v ); + } else { + s.append( refs[ i ] ); + } + } + s.append( "}" ); + return s.toString(); + } + + /** + * Two Ref vectors are equals if they are of same size, and + * dereference to the same values in the same order. + */ + public static boolean equals(Vector/**/ a,Vector/**/ b) { + if ( a.size() != b.size() ) + return false; + for ( int i = 0; i < a.size(); i++ ) { + Object ra = deref( a.get( i ) ); + Object rb = deref( b.get( i ) ); + if ( ra == null || rb == null ) + if ( ra != rb ) + return false; + if ( ! ra.equals( rb ) ) + return false; + } + return true; + } + + /** + * The name given to the {@link Ref} object at construction. + */ + public String name; + + /** + * The value of the {@link Ref} object, or null if not + * bound. + */ + private /*T*/ Object value; + + /** + * Constructor. Each Ref object is named. + */ + public Ref(String n) { + name = n; + } + + /** + * Constructor with both name and value. + */ + public Ref(String n,/*T*/ Object v) { + name = n; + value = v; + } + + /** + * Constructor that copies another Ref object. + */ + public Ref(Ref/**/ src) { + name = src.getName(); + value = src.get(); + } + + /** + * Finds a same named Ref object in a {@link Vector}. + */ + public Ref find(Vector/**/ refs) { + for ( Iterator/**/ i = refs.iterator(); i.hasNext(); ) { + Ref ref = (Ref) i.next(); + if ( name.equals( ref.name ) ) + return ref; + } + return null; + } + + /** + * Returns the value. This is null unless bound to + * something. + */ + public /*T*/ Object get() { + return value; + } + + /** + * Accessor method for the name. + */ + public String getName() { + return name == null? "Ref#" + System.identityHashCode( this ) : name; + } + + /** + * Assigns the value. + */ + public void set(/*T*/ Object v) { + value = v; + } + + /** + * Resets its value to null. + */ + public void clear() { + value = null; + } + + /** + * Returns a String representation of this {@link Ref} object. The + * object is transparent, and it presents itself only via its + * value. Use {@link #getName} to present the {@link Ref} object. + */ + public String toString() { + return "" + value; + } + +} diff --git a/com/intendico/data/Relation.java b/com/intendico/data/Relation.java new file mode 100644 index 0000000..18922f4 --- /dev/null +++ b/com/intendico/data/Relation.java @@ -0,0 +1,1160 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Observable; +import java.util.Observer; +import java.util.Vector; + +/** + * A Relation is a set of tuples of the same size. It is created by + * enumerating the types of its columns. + * + *

A relation may have one or more key constraints. A key + * constraint combines one or more fields into a key, which + * the relation may hold only one tuple for any combination of + * values. The key constraints are used by the {@link #add} method, + * such that addition of a new tuple may cause the removal of prior + * tuples that are equal in some key combination of fields. + * + *

A relation is modified via the {@link #add} and {@link #remove} + * methods. Both of these notify observers if the relation is changed. + * + *

A relation can be queried via the {@link #query} method, which + * returns an {@link java.util.Iterator} for the matching tuples, or + * by the {@link #get} method, which instead returns a {@link + * Relation.Cursor} that implements the {@link Query} interface. + */ +public class Relation extends Observable implements Store, Inquirable { + + /** + * Utility method to create a copy of an object array. Only the + * array is copied, not its values. + */ + public static Object [] copyArray(Object[] in) + { + Object [] out = new Object [ in.length ]; + System.arraycopy( in, 0, out, 0, in.length); + return out; + } + + /** + * An instance name for this relation. + */ + public final String name; + + /** + * The required field types. + */ + public final Class [] column_types; + + /** + * The special key pattern for all fields being key. + */ + public final KeyPattern main_key; + + /** + * Holds the relation data in a layered structure. First hash + * index is the key pattern, and second hash index is the key (of + * a pattern). Actual data is a HashSet of Tuple objects, which + * are compared by using {@link java.lang.Object#equals} of their + * fields. + */ + public Hashtable/*>>*/ indexes = + new Hashtable/*>>*/(); + + /** + * Holds the collection of constraining key patterns. When a tuple + * is added, any already existing tuple of the same key (according + * to any constraining key pattern) is seen as an opposing tuple, + * which is removed (without observer notification). + */ + public HashSet/**/ constraints; + + /** + * The ArityException is thrown when a relation is accessed with a + * wrong number of fields. + */ + public class ArityException extends Exception { + + /** + * Version identity required for serialization. + */ + public static final long serialVersionUID = 1L; + + /** + * Telling the required number of fields. + */ + public final int requires; + + /** + * Telling the given number of fields. + */ + public final int attempted; + + /** + * Constructor. + */ + public ArityException(int r,int a) { + requires = r; + attempted = a; + } + } + + /** + * The ColumnTypeException exception is thrown when a realtion is + * accessed by means of wrong field types. + */ + public class ColumnTypeException extends Exception { + + /** + * Version identity required for serialization. + */ + public static final long serialVersionUID = 1L; + + /** + * The required type of the first erroneous field. + */ + public final Class requires; + + /** + * The given type of the first erroneous field. + */ + public final Class attempted; + + /** + * The field index. + */ + public final int index; + + /** + * Constructor. + */ + public ColumnTypeException(int i,Class r,Class a) { + requires = r; + attempted = a; + index = i; + } + } + + /** + * Constructor. This defines both the relation arity and the + * individual types of the fields. + */ + public Relation(String n,Class/*...*/ [] types) { + name = n; + column_types = types; + main_key = new KeyPattern(); + } + + /** + * Alternative constructor with name and arity only. + */ + public Relation(String n,int arity) { + name = n; + column_types = new Class [ arity ]; + for ( int i = 0; i < arity; i++ ) { + column_types[ i ] = Object.class; + } + main_key = new KeyPattern(); + } + + /** + * Clears the relation, i.e. removes all its tuples. Its key + * constraints are unchanged. + */ + public void clear() { + indexes = + new Hashtable/*>>*/(); + setChanged(); + notifyObservers(); + } + + /** + * Returning an Observable that notifies about subsequent changes + * to this Relation. + */ + public Observable getMonitor() { + return this; + } + + /** + * Adds a constraining key pattern. The relation is managed to + * allow at most one tuple of any key of the constraining key + * patterns. This is enforced when new tuples are added, which + * thus results in that a prior tuple of the same key is removed + * (without observer notification). + * + *

Note that a relation that violates the key constraint when + * the constraint is added will not be corrected until an opposing + * tuple is added. It does make good sense to add all key + * constraints before any tuples are added. + */ + public Relation addConstraint(boolean/*...*/ [] pattern) + throws ArityException + { + if ( constraints == null ) + constraints = new HashSet/**/(); + constraints.add( new KeyPattern( pattern ) ); + return this; + } + + /** + * Removes a constraining key pattern. + * + *

Note that the general advice is aginst removing + * key constraints, but rather creating a new relation with the + * reduced set of constraints and then populate that. + */ + public void removeConstraint(boolean/*...*/ [] pattern) + throws ArityException + { + if ( constraints == null ) + return; + constraints.remove( new KeyPattern( pattern ) ); + if ( constraints.size() == 0 ) + constraints = null; + } + + /** + * Utility method to remove all tuples opposed to a given tuple + * according to a given key pattern. Tuples are removed without + * observer notification. + */ + public void applyConstraint(KeyPattern constraint,Tuple adding) + throws ArityException, ColumnTypeException + { + Tuple key = constraint.getKey( adding ); + for ( Iterator/**/ i = query( key ); i.hasNext(); ) { + addPending( (Tuple) i.next() ); + } + processPending( false ); + } + + /** + * Utility method to remove all tuples that are opposite to the + * given tuple with respect to the constraining key patterns. + * Tuples are removed without observer notification. + */ + public void applyConstraints(Tuple adding) + throws ArityException, ColumnTypeException + { + if ( constraints == null ) + return; + for ( Iterator/**/ i = + constraints.iterator(); i.hasNext(); ) { + applyConstraint( (KeyPattern) i.next(), adding ); + } + } + + /** + * Utility method that investigates that fields are appropriate + * for the relation. Any violations are reported by throwing the + * appropriate exception. + * @throws ArityException if there is a wrong number of fields. + * @throws ColumnTypeException if any field is of wrong type. + */ + public void runtimeTyping(Object/*...*/ [] fields) + throws ArityException, ColumnTypeException + { + if ( column_types == null ) { + if ( fields == null ) + return; + throw new ArityException( 0, fields.length ); + } + + if ( fields == null ) + throw new ArityException( column_types.length, 0 ); + + if ( fields.length != column_types.length ) + throw new ArityException( column_types.length, fields.length ); + + for ( int i = 0; i < fields.length; i++ ) { + Object x = Ref.deref( fields[ i ] ); + if ( x == null ) + continue; + if ( column_types[ i ].isInstance( x ) ) + continue; + throw new ColumnTypeException( + i, column_types[ i ], x.getClass() ); + } + } + + + /** + * Represents an I/O key pattern for the relation. This is used + * for identifying keyed access tables. + */ + public class KeyPattern { + + /** + * The I/O pattern expressed as a boolean array. A true means + * input, and a false means output. + */ + boolean [] pattern; + + /** + * Constructor by boolean indicators. True indicates an input + * field, and false indicates an output field. + */ + public KeyPattern(boolean/*...*/ [] io) + throws ArityException + { + if ( io.length != column_types.length ) + throw new ArityException( column_types.length, io.length ); + pattern = io; + } + + /** + * Default constructor, with all fields key fields. + */ + public KeyPattern() { + pattern = new boolean[ column_types.length ]; + for ( int i = 0; i < pattern.length; i++ ) + pattern[ i ] = false; + } + + /** + * Constructor by field presence. The constructor is called + * with the right number of fields, leaving output fields as + * null and input fields non-null. + */ + public KeyPattern(Object/*...*/ [] io) + throws ArityException + { + if ( io.length != column_types.length ) + throw new ArityException( column_types.length, io.length ); + // check types as well? + pattern = new boolean [ io.length ]; + for ( int i = 0; i < io.length; i++ ) + pattern[ i ] = Ref.deref( io[ i ] ) != null; + } + + /** + * Equality of KeyPattern objects is determined by comparing + * their boolean patterns. + */ + public boolean equals(Object p) { + return p instanceof KeyPattern ? equals( (KeyPattern) p ) : false ; + } + + /** + * Equality of KeyPattern objects is determined by comparing + * their boolean patterns. + */ + public boolean equals(KeyPattern p) { + if ( pattern == null || p.pattern == null ) + return pattern == null && p.pattern == null; + if ( pattern.length != p.pattern.length ) + return false; + for ( int i = 0; i < pattern.length; i++ ) + if ( pattern[ i ] != p.pattern[ i ] ) + return false; + return true; + } + + /** + * The hashCode of a KeyPattern is derived by treating its + * boolean pattern as a bit pattern. + */ + public int hashCode() { + int code = 0; + for ( int i = 0; i < pattern.length; i++ ) + code = ( code << 1 ) + ( pattern[ i ]? 1 : 0 ); + return code; + } + + /** + * Returns a tuple by projecting the given tuple through this + * key pattern. + */ + public Tuple getKey(Tuple t) { + return new Tuple( t, pattern ); + } + + /** + * Returns a String representation of the KeyPattern object. + */ + public String toString() { + StringBuffer s = new StringBuffer(); + String sep = ""; + for ( int i = 0; i < pattern.length; i++ ) { + s.append( sep ); + s.append( pattern[ i ] ); + sep = ","; + } + return "KeyPattern(" + s.toString() +")"; + } + } + + /** + * Representation of relation row. In a contents row, all fields + * of a tuple should be non-null. Tuples that represent + * access patterns typically have null-valued {@link Ref} + * objects, or are null directly, for the output fields, + * whereas the input fields provide values to match with. + */ + public class Tuple { + + /** + * Holds the field values. + */ + public Object [] fields; + + /** + * Constructor. + */ + public Tuple() { + fields = new Object[ column_types.length ]; + } + + /** + * Constructor. + */ + public Tuple(Object/*...*/ [] tuple) { + fields = copyArray( tuple ); + } + + /** + * Returns the number of null fields of this tuple. + */ + public int nullCount() { + int x = 0; + for (int i = 0; i < fields.length; i++) { + if ( Ref.deref( fields[ i ] ) == null ) + x += 1; + } + return x; + } + + /** + * Key constructor. Creates a copy of the given tuple, but + * clears the output fields, as given by the key pattern. + */ + public Tuple(Tuple t,boolean [] pattern) { + this(); + for (int i = 0; i < fields.length; i++) { + if ( pattern[ i ] ) + fields[ i ] = Ref.deref( t.fields[ i ] ); + else if ( fields[ i ] instanceof Ref ) + ((Ref) fields[ i ]).clear(); + } + } + + /** + * Utility method to clear this tuple according to the key + * pattern. + */ + public void clear(boolean [] pattern) { + for ( int i = 0; i < pattern.length; i++ ) { + if ( pattern[ i ] ) + continue; + if ( fields[ i ] instanceof Ref ) + ((Ref) fields[ i ] ).clear(); + else + fields[ i ] = null; + } + } + + /** + * Determine whether a given query matches this tuple, by + * comparing all non-null query fields with the tuple fields + * using the equals method. + * + * @return true if this tuple matches the given query + * tuple when ignoring its null fields, and + * false otherwise. + */ + public boolean match(Tuple query) { + for ( int i = 0; i < fields.length; i++ ) { + Object x = Ref.deref( query.fields[ i ] ); + if ( x == null ) + continue; + if ( ! x.equals( Ref.deref( fields[ i ] ) ) ) + return false; + } + return true; + } + + /** + * Fill in the fields of a query from this tuple using the + * given key pattern. + */ + //@SuppressWarnings("unchecked") + public void getFields(KeyPattern kp, Object [] output) { + for ( int i = 0; i < fields.length; i++ ) { + if ( ! kp.pattern[ i ] ) { + if ( output[ i ] instanceof Ref ) + ((Ref) output[ i ]).set( fields[ i ] ); + else + output[ i ] = fields[ i ]; + } + } + } + + /** + * Returns a given field value. + */ + public Object getField(int index) { + return fields[ index ]; + } + + /** + * Determine whether this tuple is equal to a given object. + */ + public boolean equals(Object x) { + return x instanceof Tuple? equals( (Tuple) x ) : false; + } + + /** + * Determine whether this tuple is equal to another. The + * method dereferences any {@link Ref} objects of both this + * tuple and the other tuple. + * + * @return true if all field values (after + * dereference) are equal, and false otherwise. + */ + public boolean equals(Tuple x) { + for ( int i = 0; i < fields.length; i++ ) { + if ( ! equalFields( + Ref.deref( fields[ i ] ), + Ref.deref( x.fields[ i ] ) ) ) + return false; + } + return true; + } + + /** + * Utility method to compare field values that may be + * null. + * + * @return true if the field values are equal, and + * false otherwise. + */ + public boolean equalFields(Object a,Object b) { + return ( a == null || b == null )? a == b : a.equals( b ); + } + + /** + * Compute a tuple hash code, by combining the dereferenced + * field values in a deterministic way. + * + *

Note that tuples may have the same hash code without + * being equal. + * + * @return the hash code. + */ + public int hashCode() { + int x = 0; + for ( int i = 0; i < fields.length; i++ ) { + x <<= 2 ; + x += ( Ref.deref( fields[ i ] ) == null? + 0 : + Ref.deref( fields[ i ] ).hashCode() ); + } + return x; + } + + /** + * Support method to queue up this tuple for removal from its + * relation. Use method {@link #processPending} to process + * delayed removals. + * + *

This method is intended to simplify the combination of + * iterating over several tuples of the relation while + * removing some of them, without this causing problems due to + * {@link java.util.ConcurrentModificationException} being + * thrown. (Please refer to notes about {@link + * java.util.HashSet}). + */ + public void delayedRemove() { + addPending( this ); + } + + /** + * Returns a String representation of this tuple. + */ + public String toString() { + StringBuffer s = new StringBuffer("<"); + for ( int i = 0; i < fields.length; i++ ) { + if ( i > 0 ) + s.append( "," ); + s.append( fields[ i ] == null? "null" : fields[ i ].toString() ); + } + s.append( ">" ); + return s.toString(); + } + + /** + * Utility method to collect Ref objects in the fields. + */ + public Vector/**/ getRefs(Vector/**/ v) { + for ( int i = 0; i < fields.length; i++ ) { + if ( fields[ i ] instanceof Ref ) { + Ref r = (Ref) fields[ i ]; + if ( ! v.contains( r ) ) + v.add( r ); + } + } + return v; + } + + } + + /** + * Utility method for accessing the data table of a key pattern + * and keying tuple. + */ + public HashSet/**/ getData(KeyPattern kp,Tuple tuple,boolean add) { + Hashtable/*>*/ table = + (Hashtable/*>*/) indexes.get( kp ); + if ( table == null ) { + table = new Hashtable/*>*/(); + indexes.put( kp, table ); + } + Tuple key = kp.getKey( tuple ); + HashSet/**/ data = (HashSet/**/) table.get( key ); + if ( add && data == null ) { + data = new HashSet/**/(); + table.put( key, data ); + } + return data; + } + + /** + * Utility method to obtain an iterator over existing key + * patterns. + */ + public Iterator/**/ keyPatterns() { + return indexes.keySet().iterator(); + } + + /** + * Access method to add a tuple to the relation. Returns false if + * the relation already contains the tuple. Otherwise all tables + * are updated, and the method returns true. + * + *

Note that all values a dereferenced before being added to + * the relation. + * + * @return true if the relation is changed, and + * false otherwise. + * + * @see #runtimeTyping + * @see #remove + */ + synchronized public boolean add(Object/*...*/ [] values) + throws ArityException, ColumnTypeException + { + runtimeTyping( values ); + + for ( int i=0; i < values.length; i++ ) + values[ i ] = Ref.deref( values[ i ] ); + + Tuple tuple = new Tuple( values ); + + HashSet/**/ data = getData( main_key, tuple, true ); + if ( data.contains( tuple ) ) { + return false; + } + + applyConstraints( tuple ); + + for (Iterator/**/ kpi = keyPatterns(); kpi.hasNext(); ) { + getData( (KeyPattern) kpi.next(), tuple, true ).add( tuple ); + } + + setChanged(); + notifyObservers(); + return true; + } + + /** + * Access method to remove a tuple from the relation. Returns + * false if the tuple is missing from the relation. Otherwise all + * tables are updated, and the method returns true. + * + * @see #runtimeTyping + * @see #add + */ + synchronized public boolean remove(Object/*...*/ [] values) + throws ArityException, ColumnTypeException + { + if ( removeQuietly( values ) ) { + setChanged(); + notifyObservers(); + return true; + } + return false; + } + + /** + * Internal working method to remove tuples without notifying any + * observer. The remove function is split up in this way in order + * to allow the {@link #add} method to apply the key constraints + * without notifications of key constraint knock-outs. + */ + public boolean removeQuietly(Object/*...*/ [] values) + throws ArityException, ColumnTypeException + { + runtimeTyping( values ); + Tuple tuple = new Tuple( values ); + + HashSet/**/ data = getData( main_key, tuple, true ); + if ( !data.contains( tuple ) ) + return false; + + for (Iterator/**/ kpi = keyPatterns(); kpi.hasNext(); ) { + getData( (KeyPattern) kpi.next(), tuple, true ).remove( tuple ); + } + return true; + } + + /** + * Returns the size of the relation, i.e., the number of key + * tuples in the {@link #main_key} collection. + */ + synchronized public int size() { + Hashtable/*>*/ table = + (Hashtable/*>*/) indexes.get( main_key ); + return table == null? 0 : table.size(); + } + + /** + * Returns the projected size of the relation, i.e., the number of + * tuples matching to the tuple of the given values, with null + * values (or unbound Ref objects) indicating non-key columns. + */ + public int size(Object/*...*/ [] values) { + return size( new Tuple( values ) ); + } + + /** + * Returns the projected size of the relation, i.e., the number of + * tuples matching to the given key tuple. + */ + synchronized public int size(Tuple key) { + try { + KeyPattern kp = new KeyPattern( key.fields ); + if ( indexes.get( kp ) == null ) { + return 0; + } + HashSet/**/ data = getData( kp, key, false ); + return data == null? 0 : data.size(); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * Utility method to populate a HashSet matching to a key + */ + public void populate(KeyPattern kp) + { + Hashtable/*>*/ table = + new Hashtable/*>*/(); + indexes.put( kp, table ); + HashSet/**/ db = getData( main_key, new Tuple(), true ); + for ( Iterator/**/ i = db.iterator(); i.hasNext(); ) { + Tuple t = (Tuple) i.next(); + HashSet/**/ data = getData( kp, kp.getKey( t ), true ); + data.add( t ); + } + } + + /** + * Utility method to obtain an Iterator for the tuple set that + * matches the given key values. + */ + public Iterator/**/ query(Object/*...*/ [] values) + throws ArityException, ColumnTypeException + { + runtimeTyping( values ); + Tuple key = new Tuple( values ); + return query( key ); + } + + /** + * Utility method to obtain an Iterator for the tuple set that + * matches the given key tuple. + */ + synchronized public Iterator/**/ query(Tuple key) + throws ArityException, ColumnTypeException + { + KeyPattern kp = new KeyPattern( key.fields ); + if ( indexes.get( kp ) == null ) { + populate( kp ); + } + HashSet/**/ data = getData( kp, key, true ); + return data.iterator(); + } + + /** + * The Cursor class provides Query processing support for a + * relation. + */ + public class Cursor implements Query { + + /** + * A {@link Relation.Tuple} object that is updated for new + * mathing values. + */ + public Tuple output; + + /** + * The querying key pattern. + */ + public KeyPattern key_pattern; + + /** + * The result iterator. + */ + public Iterator/**/ current; + + /** + * Constructor for key values. Output fields are marked by + * {@link Ref} objects of null values, and ignored + * fields are marked by null. + */ + public Cursor(Object/*...*/ [] values) + throws ArityException, ColumnTypeException + { + output = new Tuple( values ); + key_pattern = new KeyPattern( output.fields ); + reset(); + } + + /** + * The {@link Query#copy} method implemented by creating a new + * Cursor with new {@link Ref} objects. + */ + //@SuppressWarnings("unchecked") + public Query copy(Vector/**/ newrefs) throws Exception { + Object [] fields = copyArray( output.fields ); + for ( int i = 0; i < fields.length; i++ ) { + if ( fields[ i ] instanceof Ref ) { + fields[ i ] = ((Ref) fields[ i ]).find( newrefs ); + } + } + return new Cursor( fields ); + } + + /** + * The {@link Query#reset} method implemented by recomputing + * the key pattern and the matching tuple set. + */ + public void reset() + throws ArityException, ColumnTypeException + { + key_pattern = new KeyPattern( output.fields ); + current = query( key_pattern.getKey( output ) ); + } + + /** + * The {@link Query#next} method implemented by binding the + * output {@link Ref} objects for the next match. + */ + public boolean next() { + if ( ! current.hasNext() ) { + output.clear( key_pattern.pattern ); + return false; + } + ((Tuple) current.next()).getFields( key_pattern, output.fields ); + return true; + } + + /** + * The {@link Query#getRefs} method implemented by returning the + * {@link Ref} objects given to this Cursor object. + */ + public Vector/**/ getRefs(Vector/**/ v) { + return output.getRefs( v ); + } + + /** + * The {@link Query#addObserver} method implemented by adding + * the observer to the relation. + */ + public void addObserver(Observer x) { + Relation.this.addObserver( x ); + } + + /** + * The {@link Query#deleteObserver} method implemented by + * removing the observer from the relation. + */ + public void deleteObserver(Observer x) { + Relation.this.deleteObserver( x ); + } + + /** + * Implements the {@link Query#addable} method by verifying + * that all {@link Ref} objects are non-null. + */ + public boolean addable() + { + return output.nullCount() == 0; + } + + /** + * The {@link Query#add} method implemented by adding to + * current output tuple to the relation, provided that all its + * {@link Ref} objects are non-null. + */ + public boolean add() + { + try { + if ( output.nullCount() == 0 ) { + Object [] fields = new Object [ output.fields.length ]; + System.arraycopy( + output.fields, 0, fields, 0, fields.length ); + return Relation.this.add( fields ); + } + } catch (Exception e) { + e.printStackTrace(); + System.err.println( "** ignored **" ); + } + return false; + } + + /** + * Implements {@link Query#removable} by checking that the + * indicated tuple is stored. + */ + public boolean removable() { + HashSet/**/ data = getData( main_key, output, true ); + return data.contains( output ); + } + + /** + * Implements {@link Query#remove} by removing the indicated + * tuple. + * + * @return true if tuple was removed, and + * false otherwise. + */ + public boolean remove() { + try { + return Relation.this.remove( output.fields ); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * Returns a {link java.lang.String} representation of this + * Cursor. This uses the {@link Relation#name} field as well + * as the {@link Ref#name} field of any {@link Ref} object + * involved (both input and output). + */ + public String toString() { + StringBuffer s = new StringBuffer(); + s.append( Relation.this.name ); + s.append( "(" ); + for ( int i = 0; i < output.fields.length; i++ ) { + if ( i > 0 ) + s.append( "," ); + s.append( " " ); + if ( output.fields[ i ] instanceof Ref ) { + s.append( ((Ref) output.fields[ i ] ).getName() ); + s.append( "=" ); + } + s.append( output.fields[ i ] ); + } + s.append( " )" ); + return s.toString(); + } + + } + + /** + * An interface method to obtain a {@link Query} implementation + * object, {@link Relation.Cursor} for given key. The output + * fields would be marked by null valued {@link Ref} + * objects, which get successively assigned for the matching + * tuples. + */ + public Query get(Object/*...*/ [] values) throws Exception { + return new Cursor( values ); + } + + /** + * Interface method to obtain the Inquirable name. + */ + public String getName() { + return name; + } + + // + // Pending removal support + // + + /** + * Transient support for delayed removal of tuples. + */ + public HashSet/**/ pending; + + /** + * Adds a tuple to the pending table. + * + * @see Relation.Tuple#delayedRemove + */ + synchronized public void addPending(Tuple t) { + if ( pending == null ) + pending = new HashSet/**/(); + pending.add( t ); + } + + /** + * Utility method to remove all pending tuples. Invokes {@link + * #processPending(boolean)} with argument true. + * @return true if the relation is changed, and + *false otherwise. + * + * @return true if the relation is changed, and + * false otherwise. + * + * @see #processPending(boolean) + */ + synchronized public boolean processPending() + { + return processPending( true ); + } + + /** + * Utility method to remove all pending tuples. The noise argument + * marks whether (true) or not (false) to notify + * observers if the relation is changed. + * + * @return true if the relation is changed, and + *false otherwise. + * + * @see #removeQuietly + */ + synchronized public boolean processPending(boolean noise) + { + if ( pending == null ) + return false; + boolean removed = false; + for ( Iterator/**/ i = pending.iterator(); i.hasNext(); ) { + try { + removed |= removeQuietly( ((Tuple) i.next()).fields ); + } catch (Exception e) { + System.err.println( "processPending: " + e.toString() ); + } + } + pending = null; + if ( noise && removed ) { + setChanged(); + notifyObservers(); + } + return removed; + } + + /// + /// Convenience methods + /// + + /** + * Convenience method for unary relation key constraint setting. + */ + public void addConstraint(boolean x) + throws ArityException { + addConstraint( new boolean [] { x } ); + } + + /** + * Convenience method for unary relation assertion. + */ + public boolean add(Object x) + throws ArityException, ColumnTypeException { + return add( new Object [] { x } ); + } + + /** + * Convenience method for unary relation retraction. + */ + public boolean remove(Object x) + throws ArityException, ColumnTypeException { + return remove( new Object [] { x } ); + } + + + /** + * Convenience method for binary relation key constraint setting. + */ + public void addConstraint(boolean x1,boolean x2) + throws ArityException { + addConstraint( new boolean [] { x1, x2 } ); + } + + /** + * Convenience method for binary relation assertion. + */ + public boolean add(Object x1,Object x2) + throws ArityException, ColumnTypeException { + return add( new Object [] { x1, x2 } ); + } + + /** + * Convenience method for binary relation retraction. + */ + public boolean remove(Object x1,Object x2) + throws ArityException, ColumnTypeException { + return remove( new Object [] { x1, x2 } ); + } + + + /** + * Convenience method for tertiary relation key constraint setting. + */ + public void addConstraint(boolean x1,boolean x2,boolean x3) + throws ArityException { + addConstraint( new boolean [] { x1, x2, x3 } ); + } + + /** + * Convenience method for tertiary relation assertion. + */ + public boolean add(Object x1,Object x2,Object x3) + throws ArityException, ColumnTypeException { + return add( new Object [] { x1, x2, x3 } ); + } + + /** + * Convenience method for tertiary relation retraction. + */ + public boolean remove(Object x1,Object x2,Object x3) + throws ArityException, ColumnTypeException { + return remove( new Object [] { x1, x2, x3 } ); + } + + /** + * Returns a String representation of this relation. + */ + public String toString() { + return indexes.toString(); + } +} diff --git a/com/intendico/data/Rule.java b/com/intendico/data/Rule.java new file mode 100644 index 0000000..b503e43 --- /dev/null +++ b/com/intendico/data/Rule.java @@ -0,0 +1,89 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.ConcurrentModificationException; + +/** + * The Rule class defines a belief propagation relationship + * between a pair of {@link Query} objects that share {@link Ref} + * objects. The Rule provides a forward directed implication such + * that when the antecedent comes true for a binding, then the + * consequent is made true for that binding. + * + *

A Rule object is operated by first calling {@link #reset}, + * which results in a {link Snapshot#reset} that collects all new + * bindings for the antecedent {@link Query}. The second step is a + * subsequent call to {@link #propagate}, which results in calls to + * {@link Query#add} on the consequent {@link Query} with bindings as + * given by the antecedent snapshot. + */ +public class Rule { + + /** + * The rule antecedent. + */ + public Snapshot antecedent; + + /** + * The rule consequent. + */ + public Query consequent; + + /** + * Constructor. + */ + public Rule(Query a,Query c) { + if ( a != null ) + antecedent = new Snapshot( a ); + consequent = c; + } + + /** + * Invoked to restart the Rule from the current belief state. + */ + public void reset() throws Exception { + try { + antecedent.reset(); + } catch (ConcurrentModificationException e) { + System.err.println( "Rule: " + this ); + throw e; + } + } + + /** + * Invoked to propagate antecedent bindings into consequent + * additions. + */ + public void propagate() throws Exception { + while ( antecedent.next() ) { + consequent.reset(); + if ( ! consequent.next() ) + consequent.add(); + } + } + + /** + * Returns a textual representation of a Rule. + */ + public String toString() { + return antecedent.query + " => " + consequent; + } +} diff --git a/com/intendico/data/RuleSet.java b/com/intendico/data/RuleSet.java new file mode 100644 index 0000000..799690f --- /dev/null +++ b/com/intendico/data/RuleSet.java @@ -0,0 +1,243 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Observable; +import java.util.Observer; +import java.util.Vector; +import java.util.HashSet; +import java.util.Iterator; + +/** + * A RuleSet is a reason maintenance unit, which attempts to uphold + * given rules. In normal use, the RuleSet is populated with some + * number of Rule objects, via either {@link #add(Rule)} or {@link + * #add(Query,Query)}, which tie the {@link Rule} objects to the + * RuleSet. Each {@link Rule} object, qua {@link Observer}, gets + * notified about possible changes by their antecedent source , which + * result in them invoking the {@link #activate} method. The RuleSet + * {@link #run} method then pops and processes all activated rules to + * achieve their belief propagation effects. + */ +public class RuleSet extends Observable implements Runnable { + + /** + * Utility class to observe source update + */ + class RuleObserver implements Observer { + + /** + * The {@link Rule} concerned. + */ + Rule rule; + + /** + * Constructor. + */ + RuleObserver(Rule r) { + rule = r ; + } + + /** + * Implements {@link Observer#update} by activating the rule + * concerned. However, if the {@link Rule} is not contained in + * {@link #all_rules} then this observer just deletes itself + * from the calling {@link Observable}. + */ + public void update(Observable x,Object y) { + if ( ! all_rules.contains( rule ) ) { + x.deleteObserver( this ); + } else { + activate( rule ); + } + } + + /** + * Returns a String representation of this object. + */ + public String toString() { + return "RuleObserver: " + rule; + } + } + + /** + * The collection of {@link Rule} objects explicitly added to this + * RuleSet. + */ + public HashSet/**/ all_rules = new HashSet/**/(); + + /** + * The current set of activated rules. + */ + public HashSet/**/ active = new HashSet/**/(); + + /** + * Utility method to activate a {@link Rule} in this RuleSet. This + * includes adding the {@link Rule} to the {@link #active} + * collection, then both {@link Object#notify} on that object, and + * invoke {@link Observable#notifyObservers} to signal that this + * RuleSet has activated rules. + * @see #run + */ + public void activate(Rule r) { + synchronized ( active ) { + if ( ! active.contains( r ) ) { + active.add( r ); + active.notifyAll(); + setChanged(); + notifyObservers(); + } + } + } + + /** + * Returns true if there are any activated rules. + */ + public boolean hasActive() { + return active.size() > 0; + } + + /** + * This is a flag to signal that the run method should keep + * waiting for next update rather than returning. + */ + public boolean wait = false; + + /** + * The reset method calls {@link Rule#reset} on all given rules so + * as to reset their antecedent snapshots. + */ + public void reset(Vector/**/ rules) throws Exception { + for ( Iterator/**/ i = rules.iterator(); i.hasNext(); ) { + Rule r = (Rule) i.next(); + r.reset(); + } + } + + /** + * The propagate method calls {@link Rule#propagate} on all rules + * so as to apply the rules for all their new bindings, which + * previosuly have been snapshot by {@link Rule#reset} calls. + */ + public void propagate(Vector/**/ rules) throws Exception { + for ( Iterator/**/ i = rules.iterator(); i.hasNext(); ) { + Rule r = (Rule) i.next(); + r.propagate(); + } + } + + /** + * Returns a String representation of this RuleSet. + */ + public String toString() { + StringBuilder s = new StringBuilder(); + s.append( "{" ); + boolean first = true; + for ( Iterator/**/ i = all_rules.iterator(); i.hasNext(); ) { + Rule r = (Rule) i.next(); + if ( ! first ) + s.append( "; " ); + else + first = false; + s.append( r.toString() ); + } + s.append( "}" ); + return s.toString(); + } + + /** + * Utility method to add a {@link Rule} object and tie it to this + * RuleSet. + */ + public void add(Rule r) { + if ( all_rules.contains( r ) ) { + return; + } + all_rules.add( r ); + r.antecedent.addObserver( new RuleObserver( r ) ); + } + + /** + * Utility method to create a new {@link Rule} object with given + * antecedent and consequent {@link Query} objects, and add it to + * this RuleSet. + */ + public void add(Query a,Query c) { + add( new Rule( a, c ) ); + } + + /** + * Deactivates this rule. + */ + public void cancel(Rule r) { + all_rules.remove( r ); + } + + /** + * Utility method that adds all currently active rules to a + * {@link Vector}, and clears the active set. + */ + public boolean popActivated(Vector/**/ v,boolean w) { + synchronized ( active ) { + while ( active.size() == 0 ) { + if ( w ) { + try { + active.wait(); + } catch (InterruptedException e) { + } + } else { + return false; + } + } + v.addAll( active ); + active.clear(); + } + return true; + } + + /// + /// Runnable implementation + /// + + /** + * Utility method to keep refreshing the rules if and while there + * are source updates. The {@link #wait} flag controls whether + * this method should block and wait for a new update (true) or + * return (false) when a propagation session ends. Note that if + * there are rules to propagate competing assertions in a cyclic + * manner, then the propagation will not end and the overall + * application will suffer badly. + */ + public void run() { + try { + Vector/**/ rules = new Vector/**/(); + for ( ;; ) { + if ( ! popActivated( rules, wait ) ) + return; + reset( rules ); + propagate( rules ); + rules.clear(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/com/intendico/data/Snapshot.java b/com/intendico/data/Snapshot.java new file mode 100644 index 0000000..1508f03 --- /dev/null +++ b/com/intendico/data/Snapshot.java @@ -0,0 +1,249 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Vector; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Observer; + +/** + * The Snapshot class implements a query overlay for capturing changes + * to a source {@link Query} in respect of the added and lost valid + * binding combinations for the {@link Ref} objects + * involved. Temporally a snapshot is taken by the {@link #reset} + * method, as well as at construction. Thereafter successive {@link + * #next} invocations provide the added valid binding combinations + * since the prior snapshot. + * + *

Following a reset, the Snapshot also holds the lost bindings, + * which can be inspected separately. + * + *

A Snapshot may hold a non-null {@link #sync} object, which is + * used for synchronizing on during {@link #reset}. Any concurrent + * thread that updates queried components should synchronize on the + * same object, and in that way avoid multi-threading conflict. + */ +public class Snapshot implements Query { + + /** + * Holds all the bindings found at the most recent snapshot. + */ + public HashSet/**/ snapshot; + + /** + * Holds the bindings that were valid before the most recent + * snapshot, but were no longer valid at that snapshot. + */ + public HashSet/**/ lost = new HashSet/**/(); + + /** + * Holds the Ref objects in a Vector. + */ + public Vector/**/ refs; + + /** + * The iterator for recalling successive new bindings by the + * {@link #next} method. + */ + public Iterator/**/ bindings; + + /** + * The query being monitored. + */ + public Query query; + + /** + * The object, if any, to synchronize on during {@link + * #reset}. This is to support multi-thread access to queried + * components; any concurrent thread needs to synchronize on the + * same object. + */ + public Object sync = null; + + /** + * Constructor. + */ + public Snapshot(Query q) { + query = q; + refs = query.getRefs( new Vector/**/() ); + try { + reset(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Alternative constructor with a synchronization object. + */ + public Snapshot(Object lock,Query q) { + this( q ); + sync = lock; + } + + /** + * The {@link Query#copy} method implemented by creating a new + * Snapshot with a copy of the monitored {@link #query}, and + * sharing any {@link #sync} object. + */ + public Query copy(Vector/**/ newrefs) throws Exception { + return new Snapshot( sync, query.copy( newrefs ) ); + } + + /** + * The {@link Query#reset} method implemented by capturing a new + * snapshot of the given query, and initialising the Iterator for + * the collection of bindings that are new since the last + * snapshot. + * + *

This method updates {@link #snapshot}, {@link #lost} and + * {@link #bindings}. + */ + public void reset() throws Exception { + if ( sync == null ) { + doReset(); + } else { + synchronized( sync ) { + doReset(); + } + } + } + + /** + * Actual reset method, which may and might not be synchronized. + */ + private void doReset() throws Exception { + lost = snapshot != null? snapshot : new HashSet/**/(); + HashSet/**/ is = new HashSet/**/(); + snapshot = new HashSet/**/(); + Ref.clear( refs ); + query.reset(); + boolean none = true; + while ( query.next() ) { + none = false; + Vector v = Ref.getBinding( refs ); + if ( snapshot.contains( v ) ) + continue; + snapshot.add( v ); + if ( lost != null && lost.contains( v ) ) { + lost.remove( v ); + } else { + is.add( v ); + } + } + bindings = none? null : is.iterator(); + } + + /** + * Produces the succession of bindings that the qurey was valid + * for at the last snapshot, but not at the one prior. + * + *

This method uses {@link #bindings}. + */ + public boolean next() { + if ( bindings == null ) + return false; + if ( bindings.hasNext() ) { + Ref.bind( refs, (Vector) bindings.next() ); + } else { + bindings = null; + return false; + } + if ( ! bindings.hasNext() ) + bindings = null; + return true; + } + + /** + * The {@link Query#getRefs} method implemented by returning the + * query's Ref object. + */ + public Vector/**/ getRefs(Vector/**/ v) { + return query.getRefs( v ); + } + + /** + * The {@link Query#addObserver} method implemented by adding + * the observer to the query. + */ + public void addObserver(Observer x) { + query.addObserver( x ); + } + + /** + * The {@link Query#deleteObserver} method implemented by + * removing the observer from the query. + */ + public void deleteObserver(Observer x) { + query.deleteObserver( x ); + } + + /** + * The {@link Query#addable} method implemented by forwarding to + * the contained query. + */ + public boolean addable() { + return query.addable(); + } + + /** + * The {@link Query#add} method implemented by forwarding to the + * contained query. + */ + public boolean add() { + return query.add(); + } + + /** + * The {@link Query#removable} method implemented by forwarding to + * the wrapped query. + */ + public boolean removable() { + return query.removable(); + } + + /** + * The {@link Query#remove} method implemented by forwarding to + * the wrapped query. + */ + public boolean remove() { + return query.remove(); + } + + /** + * Utility method to remove all remaining bindings for the + * query. This method applies to the current snapshot, and might + * make most sense for a newly created Snapshot object, where it + * will remove all matching bindings. + */ + public void removeAll() { + while ( next() ) { + remove(); + } + } + + /** + * Returns a String representation of the Snapshot object. + */ + public String toString() { + return "Snapshot( " + query.toString() + " )"; + } +} diff --git a/com/intendico/data/Store.java b/com/intendico/data/Store.java new file mode 100644 index 0000000..0cc2460 --- /dev/null +++ b/com/intendico/data/Store.java @@ -0,0 +1,55 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data; + +import java.util.Observable; + +/** + * This interface defines the requirements of a data store, which is + * an object that contains "tuples" represented as Object arrays. Note + * that stores typically should implement {@link Inquirable} as well. + */ +public interface Store { + + /** + * Interface method for adding a tuple of values to this + * Store. The method returns true if the store was modified due to + * this action, otherwise false. + */ + public boolean add(Object/*...*/ [] values) throws Exception ; + + /** + * Interface method for removing a tuple of values from this + * Store. The method returns true if the store was modified due to + * this action, otherwise false. + */ + public boolean remove(Object/*...*/ [] values) throws Exception ; + + /** + * Interface method for clearing this store of all its data. + */ + public void clear() throws Exception ; + + /** + * Interface method for returning an Observable that notifies + * about subsequent changes to the Store. + */ + public Observable getMonitor(); +} diff --git a/com/intendico/data/addon/Language.java b/com/intendico/data/addon/Language.java new file mode 100644 index 0000000..ed643bd --- /dev/null +++ b/com/intendico/data/addon/Language.java @@ -0,0 +1,464 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data.addon; + +import com.intendico.data.*; +import com.intendico.gorite.Data; +import com.intendico.sdp.BNFGrammar; +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Vector; + +/** + * This class defines the textual predicate language. The detailed + * syntax is as follows: + * + *

+ * rule ::= <predicate> '=> <predicate>
+ *
+ * predicate ::= 'And '( <predicates> ')
+ *   | 'Or '( <predicates> ')
+ *   | 'Not <predicate>
+ *   | 'Lost <predicate>
+ *   | 'Equals '( <arguments> ')
+ *   | 'Distinct '( <arguments> ')
+ *   | <name> '( <arguments> ')
+ *
+ * predicates ::= <predicate> ', <predicates>
+ *   | <predicate>
+ *
+ * arguments ::= <argument> ', <arguments>
+ *   | <argument>
+ *
+ * argument ::= <number>
+ *   | <string>         -- double-quoted
+ *   | <xstring>        -- single-quoted
+ *   | '$ <name>
+ * 
+ * + *

Note that this lanugage allows single-quotes as well as + * double-quotes for strings (though the latter are called xstring in + * the grammar). + * + *

The Language object includes three lookup tables: {@link + * #inquirables} for lookup of relations (or primitive query names), + * {@link #data} for translation time binding of variables, and {@link + * #variables} for collating and re-using same named variables. + * + *

Translation is not multi-thread safe. + */ +public class Language extends BNFGrammar { + + private static String SYNTAX = + "rule ::= '=> " + + + "predicate ::= " + + " 'And '( ') # AND " + + " | 'Or '( ') # OR " + + " | 'Lost # LOST " + + " | 'Not # NOT " + + " | 'Equals '( ') # EQUALS " + + " | 'Distinct '( ') # DISTINCT " + + " | '( ') # GET " + + + "predicates ::= ', # Enlist " + + " | # Enlist " + + + "arguments ::= ', # Enlist " + + " | # Enlist " + + + "argument ::= # NUMBER" + + " | # STRING " + + " | # STRING " + + " | '$ # VARIABLE " + + "" + ; + + /** + * Constant that describes the construction argument types for + * value queries such as {@link Equals} and {@link Distinct}. + */ + private final static Class [] QUERYARGVAL = + new Class [] { Object[].class }; + + /** + * Constant that describes the construction argument types for + * single query queries such as {@link Not} and {@link Lost}. + */ + private final static Class [] QUERYARGONE = + new Class [] { Query.class }; + + /** + * Constant that describes the construction argument types for + * multi query queries such as {@link And} and {@link Or}. + */ + private final static Class [] QUERYARGMANY = + new Class [] { Query[].class }; + + /** + * Base class for queries taking multiple value arguments. + */ + public class ArgsQuery extends Production { + /** + * Holds the target constructor for this kind of queries. + */ + private Constructor cnr; + + /** + * Constructor for a given target class. + */ + public ArgsQuery(Class c) { + try { + cnr = c.getConstructor( QUERYARGVAL ); + } catch (Exception e) { + e.printStackTrace(); + throw new Error( "BUG 1" ); + } + } + + /** + * The rule action. Create an instance of the target class for + * the given arguments. + */ + public Object action(Vector v) { + try { + v = (Vector) v.get( 0 ); + return cnr.newInstance( + new Object [] { v.toArray( new Object [ v.size() ] ) } ); + } catch (Exception e) { + e.printStackTrace(); + throw new Error( "BUG 2" ); + } + } + } + + /** + * Base class for queries taking multiple arguments. + */ + public class ManyQuery extends Production { + /** + * Holds the target constructor for this kind of queries. + */ + private Constructor cnr; + + /** + * Constructor for a given target class. + */ + public ManyQuery(Class c) { + try { + cnr = c.getConstructor( QUERYARGMANY ); + } catch (Exception e) { + e.printStackTrace(); + throw new Error( "BUG 1" ); + } + } + + /** + * The rule action. Create an instance of the target class for + * the given arguments. + */ + public Object action(Vector v) { + try { + v = (Vector) v.get( 0 ); + return cnr.newInstance( new Object [] { + (Query[]) v.toArray( new Query [ v.size() ] ) } ); + } catch (Exception e) { + e.printStackTrace(); + throw new Error( "BUG 2" ); + } + } + } + + /** + * Base class for queries taking a single argument. + */ + public class OneQuery extends Production { + /** + * Holds the target constructor for this kind of queries. + */ + private Constructor cnr; + + /** + * Constructor for a given target class. + */ + public OneQuery(Class c) { + try { + cnr = c.getConstructor( QUERYARGONE ); + } catch (Exception e) { + e.printStackTrace(); + throw new Error( "BUG 1" ); + } + } + + /** + * The rule action. Create an instance of the target class for + * the given argument. + */ + public Object action(Vector v) { + try { + return cnr.newInstance( new Object[] { (Query) v.get( 0 ) } ); + } catch (Exception e) { + e.printStackTrace(); + throw new Error( "BUG 2" ); + } + } + } + + /** + * Rule action for making an {@link And} query + */ + public class AND extends ManyQuery { + public AND() { + super( And.class ); + } + } + + /** + * Rule action for making an {@link Or} query + */ + public class OR extends ManyQuery { + public OR() { + super( Or.class ); + } + } + + /** + * Rule action for making a {@link Not} query + */ + public class NOT extends OneQuery { + public NOT() { + super( Not.class ); + } + } + + /** + * Rule action for making a {@link Lost} query + */ + public class LOST extends OneQuery { + public LOST() { + super( Lost.class ); + } + } + + /** + * Rule action for making an {@link Equals} query + */ + public class EQUALS extends ArgsQuery { + public EQUALS() { + super( Equals.class ); + } + } + + /** + * Rule action for making a {@link Distinct} query + */ + public class DISTINCT extends ArgsQuery { + public DISTINCT() { + super( Distinct.class ); + } + } + + /** + * Rule action for making an {@link Inquirable#get} query. + */ + public class GET extends Production { + public Object action(Vector v) { + String name = (String) v.get( 0 ); + try { + v = (Vector) v.get( 1 ); + Inquirable inquirable = (Inquirable) inquirables.get( name ); + return inquirable.get( v.toArray( new Object[ v.size() ] ) ); + } catch (Exception e) { + e.printStackTrace(); + throw new Error( "Unknown Inquirable: " + name ); + } + } + } + + /** + * Rule action for making a number value. + */ + public class NUMBER extends Production { + /** + * Makes an Integer object for the given number + */ + public Object action(Vector v) { + int i = 0; + try { + i = Integer.parseInt( (String) v.get( 0 ) ); + } catch (Exception e) { + e.printStackTrace(); + } + return Integer.valueOf( i ); + } + } + + /** + * Rule action for making a string value. + */ + public class STRING extends Production { + /** + * Makes a String object for the given string + */ + public Object action(Vector v) { + return v.get( 0 ); + } + } + + /** + * Rule action for making a variable. The name is first looked up + * in the {@link #variables} collection, and if not found, a new + * variable is created and added to the collection. Further, if + * the {@link #data} context is set and the variable is unbound, + * then the variable is also initialised from the equally named + * data element, if any. + */ + public class VARIABLE extends Production { + /** + * Makes a String object for the given string + */ + public Object action(Vector v) { + String name = "$" + (String) v.get( 0 ); + Ref x = (Ref) variables.get( name ); + if ( x == null ) { + x = new Ref( name ); + variables.put( name, x ); + } + if ( data != null && x.get() == null ) { + Object value = data.getValue( name ); + if ( value != null ) + x.set( value ); + } + return x; + } + } + + /** + * Data context for pre-assignment of variables. + */ + private Data data; + + /** + * Collection of variables. + */ + private Map variables = new HashMap(); + + /** + * Table of Inquirables. + */ + private Map inquirables = new HashMap(); + + /** + * Constructor, which installs the language. + */ + public Language() { + install( SYNTAX ); + addRule( new Identifier( "name" ) ); + addRule( new QuotedString( "string", '"' ) ); + addRule( new QuotedString( "xstring", '\'' ) ); + addRule( new Number( "number" ) ); + + } + + /** + * Holds the singleton grammar. + */ + private static Language grammar; + + /** + * Returns the singleton Language object, creating it on first + * call. + */ + public static Language getLanguage() { + if ( grammar == null ) + grammar = new Language(); + return grammar; + } + + /** + * Utility method that translates a {@link String} into a {@link + * Query} with given collections of inquirables and variables, and + * data context. The inquirables are used for lookup of relation + * symbols, and the {@link Data} context is used for initialising + * variables. Variables mentioned in the {@link String} are + * located from the variables collection, or created as needed. + * The inquirables collection must define all relations that are + * mentioned in the {@link String}, while the variables collection + * and the data context may be given as null. + */ + public static Query textToQuery( + String text,Collection/**/ inquirables, + Collection/**/ vars,Data settings) { + Language g = getLanguage(); + g.inquirables = new HashMap/**/(); + if ( inquirables != null ) { + for ( Iterator/**/ si = + inquirables.iterator(); si.hasNext(); ) { + Inquirable inquirable = (Inquirable) si.next(); + g.inquirables.put( inquirable.getName(), inquirable ); + } + } + g.data = settings; + g.variables = new HashMap/**/(); + if ( vars != null ) { + for ( Iterator/**/ vi = vars.iterator(); vi.hasNext(); ) { + Ref variable = (Ref) vi.next(); + g.variables.put( variable.getName(), variable ); + } + } + try { + return (Query) g.parseAndProcess( "predicate", text ); + } catch (Throwable t) { + t.printStackTrace(); + return null; + } + } + + /** + * Utility method that translates a {@link String} into a {@link + * Rule} with a given collection of inquirables. The inquirables + * collection must define all relations that are mentioned in the + * {@link String} + */ + public static Vector/**/ textToRule( + String text,Collection/**/ inquirables) { + Language g = getLanguage(); + g.inquirables = new HashMap/**/(); + if ( inquirables != null ) { + for ( Iterator/**/ si = + inquirables.iterator(); si.hasNext(); ) { + Inquirable inquirable = (Inquirable) si.next(); + g.inquirables.put( inquirable.getName(), inquirable ); + } + } + g.variables = new HashMap/**/(); + g.data = null; + try { + return (Vector/**/) g.parseAndProcess( "rule", text ); + } catch (Throwable t) { + t.printStackTrace(); + return null; + } + } + +} diff --git a/com/intendico/data/addon/TextQueryCapability.java b/com/intendico/data/addon/TextQueryCapability.java new file mode 100644 index 0000000..698b4bc --- /dev/null +++ b/com/intendico/data/addon/TextQueryCapability.java @@ -0,0 +1,147 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.data.addon; + +import com.intendico.data.*; +import com.intendico.gorite.addon.Reflector; +import com.intendico.gorite.*; +import java.util.Collection; +import java.util.Iterator; +import java.util.Observer; +import java.util.Vector; + +/** + * This is a Capability extension that offers the use of predicates in + * text form using the language defined by the {@link Language} + * grammar. + * + *

The method {@link #textQuery(Data,String)} is used for + * immediate {@link com.intendico.data.Query Query} construction, + * which is suitable for the {@link Plan#context(Data)} + * implementations. The following is an illustration of its use: + * + *

+ * addGoal( new Plan( ... ) {
+ *     public Query context(Data d) {
+ *         return textQuery( d, "And( foo( 45, $x ), bar( $x ) )" );
+ *     }
+ * } );
+ * 
+ * + *

The method {@link #addRule(String)} provides a deferred {@link + * Rule} installation from a text form rule. The following is an + * illustration of its use: + * + *

+ * addRule( "Lost primary($a,$b,$c) => secondary($a,$c)" );
+ * 
+ * + *

The method {@link #addReflector(String,String,String)} provides + * a deferred {@link Reflector} installation with a text form + * query. The following is an illustration of its use: + * + *

+ * addReflector( "secondary lost", "percept","Lost secondary($a,$c)" );
+ * 
+ * + * The deferred declarations are processed and installed by the first + * invokation of the {@link Capability#shareInquirables(Collection)} + * method, when all the inquirables have been shared. In that way the + * text form references get resolved to actual relations when + * translated into the actual {@link Query} or {@link Rule} structure. + * + *

Note that more arbitrary conditions in text form predicates + * need to be supported by appropriate {@link Inquirable} + * implementations. For example, the following could be a way to + * define a computed "greater" relation between Integer values: + * + *

+ * putInquirable( new Inquirable() {
+ *     public String getName() { return "greater"; }
+ *     public Query get(final Object [] args) {
+ *         return new Condition() {
+ *             public boolean condition() {
+ *                 int a = ((Integer) Ref.deref( args[0] )).intValue();
+ *                 int b = ((Integer) Ref.deref( args[1] )).intValue();
+ *                 return a > b;
+ *             }
+ *         };
+ *     }
+ * } );
+ * 
+ * + * @see Language + */ +public class TextQueryCapability extends Capability { + + /** + * Adds a textual rule definition. {@link Rule} creation is set up + * to be deferred until after the first {@link + * Capability#shareInquirables} invocation. + */ + public void addRule(final String rule) { + add( new Deferred() { + /** + * Installs this as a proper {@link Reflector}. + */ + public void install() { + Vector/**/ q = + Language.textToRule( rule, getInquirables().values() ); + addRule( (Query) q.get( 0 ), (Query) q.get( 1 ) ); + } + } ); + } + + /** + * Utility method to declare a {@link Reflector} for the owner + * {@link Performer} using a text form query. This is deferred to + * after the first {@link Capability#shareInquirables(Collection)} + * as needed. + */ + public void addReflector( + final String goal,final String todo,final String query) { + add( new Deferred() { + /** + * Installs this as a proper {@link Reflector}. + */ + public void install() { + Reflector r = new Reflector( getPerformer(), goal, todo ); + r.addQuery( Language.textToQuery( + query, + getInquirables().values(), null, null ), + goal ); + } + } ); + } + + /** + * Utility method that creates a {@link Query} by resolving to the + * {@link Capability#inquirables} and the given {@link Data} context. + * Note that using this method before inquirables are shared, or more + * specifically if relation names are undefined, may result in an + * {@link Error}. Further, if the method returns null if the + * predicate is not parsable. + */ + public Query textQuery(Data data,String predicate) { + return Language.textToQuery( + predicate, getInquirables().values(), null, data ); + } + +} diff --git a/com/intendico/gorite/.cvsignore b/com/intendico/gorite/.cvsignore new file mode 100644 index 0000000..6b468b6 --- /dev/null +++ b/com/intendico/gorite/.cvsignore @@ -0,0 +1 @@ +*.class diff --git a/com/intendico/gorite/Action.java b/com/intendico/gorite/Action.java new file mode 100644 index 0000000..b29f4c4 --- /dev/null +++ b/com/intendico/gorite/Action.java @@ -0,0 +1,298 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +/** + * The Action class is a utility base class for implementation of task + * goals that operate on a common entity. The created action object + * becomes a factory for goals that refer back to it, and that end up + * invoking the Action's {@link #execute} method as way of achieving + * the task goal. + * + *

Generally, an action is a named definition of a function + * performed on data. It would occur in a process model in terms of a + * task goal, which is recognised as a usage point of the action, + * linking it to particular input and output data. The same action may + * be used elsewhere, with other data names. + * + *

Task goals referring to the same action object will invoke the + * same {@link #execute} method. + * + *

Example of use: + *

+ * public class MachineControlAdapter {
+ *     Action open_lid = new Action( "open lid" ) {
+ *         public boolean execute(
+ *             boolean reentry,Data.Element [] ins,Data.Element [] * outs ) {
+ *                 .. code to perform the "open lid" action
+ *             }
+ *     }
+ *     Action status = new Action( "check status" ) {
+ *         public boolean execute(
+ *             boolean reentry,Data.Element [] ins,Data.Element [] * outs ) {
+ *                 .. code to perform the "check status" action
+ *             }
+ *     }
+ *     public Capability actions() {
+ *         return new Capability() {{
+ *             open_lid.create( new String [] { "angle" }, null );
+ *             on_off.create( null, new String [] { "status" } );
+ *         }};
+ *     }
+ * }
+ * 
+ * + * The example code illustrates a "machine adapter" with two actions: + * to "open lid" to a certain "angle", and to "check status" giving a + * "status". The "machine adapter" includes an "actions()" method that + * creates a control capability of two goals for invoking the actions. + */ +public class Action { + + /** + * The name of the action. This is also the name of the goal for + * the action created via the {@link #create} method. + */ + public String name; + + /** + * Nominates the {@link Performer.TodoGroup} to use for performing + * this action. + */ + private String group; + + /** + * Constructor without group. + */ + public Action(String n) { + name = n; + } + + /** + * Constructor. + */ + public Action(String n,String g) { + name = n; + group = g; + } + + /** + * This is a utility class to represent the appearance of the + * action within a goal hierarchy. Each such apprearance involves + * its own data connections, which are contained within the Usage + * class. When this task goal is executed, it firstly ensures that + * all input data is available, or otherwise delays the execution + * until they are available. Thereafter it ensures that some + * output data needs to be produces, before invoking the action's + * exectution. If all output data is already produced, the task + * goal execution succeeds immediately without the actual action + * execution. + */ + public class Usage extends Goal { + + /** + * The names of data elements that are inputs to the goal; the + * performance of a goal will be postponed until all inputs + * are available, although maybe not ready. + */ + public String [] inputs; + + /** + * The names of data elements that are outputs from the + * action. The task goal for an action considers these: if + * they are all ready and available when the goal is + * performed, then the action execution is skipped, and the + * goal terminates successfully. However, an action without + * outputs is always executed. + */ + public String [] outputs; + + /** + * Constructor. + */ + public Usage(String [] ins,String [] outs) { + super( Action.this.name ); + inputs = ins; + outputs = outs; + group = getGoalGroup(); + } + + /** + * Creates and returns an instance object for achieving + * a Usage. + */ + public Instance instantiate(String head,Data d) { + return new Invoke( head, d ); + } + + /** + * The Invoke class represents the intention to performa Usage + * goal. + */ + public class Invoke extends Instance { + + /** + * Constructor. + */ + public Invoke(String head,Data d) { + super( head ); + } + + /** + * Tracking of first or sub sequence perform calls. + */ + public boolean reentry = false; + + /** + * The execution of an action involves an initial step of + * establishing the data connections. This will postpone the + * actual action execution until all input data is + * available. It will also skip the action execution if all + * outputs are already marked ready. It is the responsibility + * of the actual action execution to mark outputs as ready. + */ + public States action(String head,Data d) { + Data.Element [] ins = null; + Data.Element [] outs = null; + + // Synchronise with existence and readiness of inputs. + if ( inputs != null ) { + ins = new Data.Element [ inputs.length ]; + for ( int i = 0; i < inputs.length; i++ ) { + ins[ i ] = d.find( inputs[ i ] ); + if ( ins[ i ] == null ) + return States.BLOCKED; + if ( ! ins[ i ].ready ) + return States.BLOCKED; + } + } + + if ( outputs != null ) { + outs = new Data.Element [ outputs.length ]; + for ( int i = 0; i < outputs.length; i++ ) { + outs[ i ] = d.create( outputs[ i ] ); + } + } + + // Invoke action implementation + Goal.States s = Action.this.execute( reentry, ins, outs ); + reentry = true; + + if ( s == Goal.States.PASSED ) { + // Mark all outputs as ready. + if ( outs != null ) { + for ( int i = 0; i < outs.length; i++ ) { + outs[ i ].markReady(); + } + } + } + return s; + } + } + + /** + * Makes a string representation of a string array. + */ + public String toString(String [] data) + { + StringBuffer s = new StringBuffer( "[ " ); + if ( data != null ) { + String sep = ""; + for ( int i = 0; i < data.length; i++ ) { + s.append( sep ); + s.append( nameString( data[i] ) ); + sep = ", "; + } + } + s.append( " ]" ); + return s.toString(); + } + + /** + * Makes a textual representation of this action's goal. + */ + public String toString(String counter) { + StringBuffer s = new StringBuffer( super.toString( counter ) ); + s.append( toString( inputs ) ); + s.append( " >> " ); + s.append( toString( outputs ) ); + //s.append( "\n" ); + return s.toString(); + } + } + + /** + * A factory method that makes a "usage object" for this action, + * representing its usage in a goal sentence. When performed, + * the action's execute method is invoked with the actual in and out + * Data.Element objects. + */ + public Goal create(String [] ins,String [] outs) + { + return new Usage( ins, outs ); + } + + /** + * The generic action performance method. Should be overridden by + * extension class for action implementation. + */ + public Goal.States execute( + boolean reentry,Data.Element [] ins,Data.Element [] outs) { + + StringBuffer s = new StringBuffer( "Action " ); + s.append( Goal.nameString( name ) ); + s.append( " [" ); + String sep = ""; + if ( ins != null && ins.length > 0 ) { + for ( int i=0; i < ins.length; i++ ) { + s.append( sep ); + sep = ", "; + s.append( ins[i].toString() ); + } + } + s.append( "] >> [" ); + sep = ""; + if ( outs != null && outs.length > 0 ) { + for ( int i=0; i < outs.length; i++ ) { + s.append( sep ); + sep = ", "; + s.append( outs[i].toString() ); + } + } + s.append( "]" ); + System.err.println( s.toString() ); + return Goal.States.PASSED; + } + + /** + * Returns the {@link #group} attribute. + */ + public String getTodoGroup() { + return group; + } + + /** + * Assigns the {@link #group} attribute. + */ + public void setTodoGroup(String v) { + group = v; + } + +} diff --git a/com/intendico/gorite/BDIGoal.java b/com/intendico/gorite/BDIGoal.java new file mode 100644 index 0000000..8c3ee97 --- /dev/null +++ b/com/intendico/gorite/BDIGoal.java @@ -0,0 +1,395 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; +import java.util.Random; + +/** + * A BDIGoal is achieved by means of finding and performing a goal + * hierarchy as named by the BDI goal in the {@link Capability} that + * is the value of the {@link Data.Element} named by the BDIGoal's + * {@link #control} attribute. By default, the {@link #PERFORMER} data + * name is used, which is maintained by the execution machinery to + * resolve to the current performer. The BDIGoals created by the + * {@link TeamGoal} execution uses the role name as data name, which + * then is set up to be the role filling. + * + *

A BDIGoal is executed in a sequence of steps in an attempt to + * find a goal hierarchy for the named goal that acheives it, or more + * precisely, that executes without returning FAILED. The goal + * hierarchy alternatives are found via the {@link + * Capability#lookup(String)} method of the executing {@link + * Performer}. Any such alternative that implements the {@link + * Context} interface is considered a plan whose {@link + * Context#context(Data)} method defines its applicable variants. + * + *

The BDIGoal collates all applicable plan options, then selects + * one of them as an attempt to achieve the goal. If that fails, then + * the plan option is remembered, and the BDIGoal again collates all + * applicable, non-failed plan options, picks one and tries that. This + * repeats until either one plan execution succeeds (returns PASSED), + * or the plan options are exhausted, in which case the BDIGoal fails. + * + *

The choice of which among the applicable plan options to use + * can be modified via the plan choice settings. By default, the plan + * options are queried for their {@link Precedence#precedence(Data)} + * values, and the first of highest precedence option is + * choosen. However, if the goal is associated with a {@link Random} + * object, then the option is choosen by a random draw among the + * highest precedence options. Further, if the goal is associated with + * a plan choice goal name, then that plan choice goal is performed + * for effectuating the plan choice (instead of using precedence + * values). + * + *

A BDIGoal that has a {@link #specific} object set, makes that + * object available for instance execution as a data element named by + * the BDIInstance head name, which for invoked sub goals is + * everything up to the last '*' of their heads. + * + * @see Plan + * @see PlanChoiceGoal + * @see ContextualGoal + * @see Performer#getPlanChoice(String) + * @see Performer#setPlanChoice(String,Object) + * @see TeamGoal + */ +public class BDIGoal extends Goal { + + /** + * The name of the data element whose value is the name of the + * current role for the executing performer. + */ + public static final String ROLE = "current role"; + + /** + * Constructor. + */ + public BDIGoal(String n) { + super( n ); + setGoalControl( PERFORMER ); + } + + /** + * Cache of construction object (when not a String). + */ + public Object specific; + + /** + * Constructor using an object other than String. Then the class + * name of the object is used as goal name, and the object is held + * as {@link #specific}. However, if the given object is a {@link + * String}, then its value (rather than its type) is used as goal + * name anyhow. + */ + public BDIGoal(Object x) { + this( ( x instanceof String )? (String) x : x.getClass().getName() ); + specific = x instanceof String? null : x; + } + + /** + * Creates and returns an instance object for achieving + * a BDIGoal. + */ + public Instance instantiate(String head,Data d) { + return new BDIInstance( head ); + } + + /** + * Utility method to add a Goal unless it's contained among + * failed. + */ + static public void maybeAdd( + Goal g,Vector/**/ v,Vector/**/ failed) { + if ( failed == null || ! failed.contains( g ) ) { + v.add( g ); + } + } + + /** + * Expand context sensitive alternative plans. This method gets + * invoked with the current selection of plans matching to the + * BDIGoal to achieve, the current collection of tried but failed + * plan variants, and the current {@link Data}. It processes all + * plan contexts so as to produce the currently possible + * alternative contextual plan invocations, by determining + * validating bindings for the plan's context queries. + * + *

The class {@link ContextualGoal} is used to represent a + * plan variant, which consists of the plan together with the + * query {@link Ref} object binding. This takes care of + * presenting the binding in the {@link Data} when the plan is + * invoked, to unset this binding from the {@link Data} if the + * plan execution fails, and to extract new bindings from the + * {@link Data} when the plan succeeds. + * + *

This method recognises the {@link Context#EMPTY} query + * as marker that a plan does not have any applicable variant, + * and it also catches the {@link Context.None} exception for + * the same purpose. + * + *

Plan variants are filtered against the failed set, to + * avoid the same plan variant be attempted more than once. + * + * @see Context + * @see ContextualGoal + * @see BDIInstance#action + */ + static public Vector/**/ applicable( + Vector/**/ plans, Vector/**/ failed, Data data) { + Vector/**/ v = new Vector/**/(); + + for ( Iterator/**/ i = plans.iterator(); i.hasNext(); ) { + Goal goal = (Goal) i.next(); + if ( ! ( goal instanceof Context ) ) { + maybeAdd( + new ContextualGoal( null, null, goal, data ), + v, failed ); + continue; + } + try { + Query query = ((Context) goal).context( data ); + if ( query == null ) { + maybeAdd( + new ContextualGoal( null, null, goal, data ), + v, failed ); + continue; + } + if ( query != Context.EMPTY ) { + Vector/**/ vars = + query.getRefs( new Vector/**/() ); + Vector/**/ orig = Ref.copy( vars ); + query.reset(); + while ( query.next() ) { + maybeAdd( + new ContextualGoal( orig, vars, goal, data ), + v, failed ); + } + } + } catch (Context.None e) { + // No applicable context + } catch (Throwable e) { + e.printStackTrace(); + System.err.println( "Ignored plan " + goal ); + } + } + return v; + } + + /** + * Return the plan choice for this goal, relative a given root + * capability executing the goal. This treats + */ + public Object getPlanChoice(Capability root) { + return root.getPerformer().getPlanChoice( getGoalName() ); + } + + /** + * Implements a BDI method choice. + */ + public class BDIInstance extends Instance { + + /** + * The goal alternatives. (Called "relevant set" in BDI + * terminology) + */ + public Vector/**/ relevant; + + /** + * The capability hierarchy offering goal methods. + */ + public Capability root; + + /** + * The tried and failed goals. + */ + public Vector/**/ failed = new Vector/**/(); + + /** + * The count of attempts. + */ + public int count = 0; + + /** + * The current goal. + */ + Goal goal; + + /** + * The current goal instance. + */ + Instance instance = null; + + /** + * Determine the context sensitive alternatives by invoking + * {@link #applicable} + * + *

At end, this method invokes {@link #precedenceOrder} on + * the collection of applicable plan variants, which sorts the + * plan variants by descending precedence. + * @see Context + * @see ContextualGoal + * @see #precedenceOrder + * @see BDIInstance#action + */ + public Goal contextual( + Vector/**/ set,Vector/**/ failed,Data data) { + Vector/**/ v = applicable( set, failed, data ); + + // If there is a plan choice goal for this goal, then let + // it make the choice. + if ( plan_choice instanceof String ) { + return new PlanChoiceGoal( + root.getPerformer(), (String) plan_choice, v, failed ); + } + return precedenceOrder( v, data ); + } + + /** + * Apply precedence ordering destructively to the given goal + * set. + * + * @see Precedence + */ + public Goal precedenceOrder(Vector/**/ set,Data data) { + int level = 0; + Vector/**/ best = new Vector(); + for ( Iterator/**/ i = set.iterator(); i.hasNext(); ) { + Goal g = (Goal) i.next(); + int p = g instanceof Precedence? + ((Precedence) g).precedence( data ) : + Plan.DEFAULT_PRECEDENCE ; + if ( best.size() == 0 ) { + level = p; + best.add( g ); + } else if ( p > level ) { + best.clear(); + level = p; + best.add( g ); + } else if ( p == level ) { + best.add( g ); + } + } + if ( best.size() == 0 ) + return null; + if ( best.size() == 1 || ! ( plan_choice instanceof Random ) ) + return (Goal) best.get( 0 ); + return (Goal) best.get( + ((Random) plan_choice).nextInt( best.size() ) ); + } + + /** + * Holds the plan choice goal, or null. + */ + public Object plan_choice; + + /** + * Constructor. + */ + public BDIInstance(String h) { + super( h ); + } + + /** + * Cancel the execution. + */ + public void cancel() { + if ( instance != null ) + instance.cancel(); + } + + /** + * Instantiates and performs sub goals in sequence, until the + * first one that does not fail. If a sub goal fails, then + * that is caught, and the next sub goal in sequence is + * instantiated and performed. + */ + public States action(String head,Data data) + throws LoopEndException, ParallelEndException { + if ( root == null ) { + root = (Capability) data.getValue( getGoalControl() ); + if ( root == null ) { + System.err.println( + "** Missing capability '" + getGoalControl() + + "' for " + BDIGoal.this.toString( head ) ); + return States.FAILED; + } + plan_choice = getPlanChoice( root ); + } + if ( goal == null ) { + if ( isTracing() ) { + System.err.println( + "** Lookup \"" + getGoalName() + "\"" ); + } + data.setArgs( head, specific ); + goal = contextual( + root.lookup( getGoalName() ), failed, data ); + if ( goal == null && isTracing() ) { + System.err.println( + "** Goal \"" + getGoalName() + "\" unknown " ); + } + } + + while ( goal != null ) { + if ( instance == null ) { + instance = goal.instantiate( head + "*" + count, data ); + count += 1; + } + if ( isTracing() ) { + System.err.println( + "** " + nameString( getGoalName() ) + + " attempt " + count ); + } + String was_role = (String) data.getValue( ROLE ); + States b; + data.setValue( ROLE, getGoalControl() ); + try { + b = instance.perform( data ); + } finally { + data.restoreValue( ROLE, was_role ); + } + // + // Note, the following doesn't happen if instance.perform() + // above throws an exception. + // + if ( b != States.FAILED ) + return b; + instance = null; + if ( goal instanceof PlanChoiceGoal ) { + PlanChoiceGoal pg = (PlanChoiceGoal) goal; + if ( pg.choice == null ) + break; + if ( pg.done == States.PASSED ) + failed.add( pg.choice ); + } else { + failed.add( goal ); + } + goal = contextual( + root.lookup( getGoalName() ), failed, data ); + } + return States.FAILED; + } + } +} diff --git a/com/intendico/gorite/BranchingGoal.java b/com/intendico/gorite/BranchingGoal.java new file mode 100644 index 0000000..a7b3765 --- /dev/null +++ b/com/intendico/gorite/BranchingGoal.java @@ -0,0 +1,327 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import java.util.Vector; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Observable; +import java.util.Observer; + +/** + * The BranchingGoal class is an intermediate, abstract class that + * provides common execution support for parallel branches. + */ +abstract public class BranchingGoal extends Goal { + + /** + * Constructor. + */ + public BranchingGoal(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Convenience constructor without sub goals. + */ + public BranchingGoal(String n) { + this( n, null ); + } + + /** + * Helper class that implements suport for parallel goal execution + * with interrupt control. + */ + abstract public class MultiInstance extends Instance { + + /** + * States of execution; the number of branches remaining. + */ + public int counter; + + /** + * Currently active branches. + */ + public Vector/**/ branches = null; + + /** + * Names of data elements that are output from any branch. + */ + public HashSet/**/ outs = new HashSet/**/(); + + /** + * Utility method for counting branches when they are created. + */ + public synchronized void increment() { + counter++; + } + + /** + * Constructor. + */ + public MultiInstance(String h) { + super( h ); + } + + /** + * A class to monitor the combined runnability of all the + * branches, by the logic that this intention is runnable if + * any of the branch intentions are runnable. + */ + class RunnableMonitor extends Observable implements Observer { + + private boolean some_runnable = false; + + public void update(Observable x,Object y) { + if ( ! some_runnable ) { + some_runnable = true; + setChanged(); + notifyObservers(); + } + if ( x != null ) { + x.deleteObserver( this ); + } + } + } + + /** + * Keeps the monitor for branch runnability state. + */ + private RunnableMonitor runnable_monitor = null; + + /** + * Utility class to set up runnability monitoring. + */ + private void setupMonitor(Data d) { + runnable_monitor = new RunnableMonitor(); + d.setTrigger( runnable_monitor ); + boolean some = false; + for ( Iterator/**/ i = + branches.iterator(); i.hasNext(); ) { + some |= ((Branch)i.next()).setupObserver( d ); + } + if ( some ) { + removeMonitor( d ); + d.clearTrigger(); + } + } + + /** + * Utility method to remove the runnability monitoring. + */ + private void removeMonitor(Data d) { + runnable_monitor = null; + for ( Iterator/**/ i = + branches.iterator(); i.hasNext(); ) { + ((Branch)i.next()).removeObserver( d ); + } + } + + /** + * Branch head and execution. + */ + public class Branch { + + /** + * Collecting all outputs. + */ + public HashSet/**/ outputs; + + /** + * The goal instance to execute. + */ + public Instance instance; + + /** + * Branch thread name. This needs to be distinct from the + * actual branch Instance in order to join data properly. + */ + public String thread_name; + + /** + * The Data operated on. + */ + public Data data; + + /** + * Propagate cancellation to actual branch. + */ + public void cancel() { + if ( instance != null ) { + instance.cancel(); + } + } + + /** + * Constructor, tying the branch to a goal instance. + */ + public Branch(int ix,Instance i,Data d,HashSet/**/ outs) { + instance = i; + data = d; + outputs = outs; + increment(); + thread_name = instance.thread_name + "(branch)"; + data.fork( getGoalControl(), ix, thread_name ); + } + + /** + * Branch main method: performs the associated goal + * instance, and manages current "thread name". + */ + public States action() + throws LoopEndException, ParallelEndException { + String old = data.setThreadName( thread_name ); + try { + States s = instance.perform( data ); + // TODO: collate all result data for join at end + if ( s == States.PASSED ) { + //data.join( getGoalControl(), outputs, thread_name ); + data.join( getGoalControl(), null, thread_name ); + } else if ( s == States.FAILED ) { + data.join( getGoalControl(), null, thread_name ); + } + return s; + } catch (ParallelEndException e) { + //data.join( getGoalControl(), outputs, thread_name ); + data.join( getGoalControl(), null, thread_name ); + throw e; + } finally { + data.setThreadName( old ); + } + } + + /** + * Utility method to set up the runnable_monitor as + * observer of runnability change for this branch, and + * then return the current runnability state. + */ + boolean setupObserver(Data d) { + d.addObserver( instance.thread_name, runnable_monitor ); + return d.isRunning( instance.thread_name ); + } + + /** + * Utility method to remove the runnable_monitor as + * runnability observer. + */ + void removeObserver(Data d) { + d.deleteObserver( instance.thread_name, runnable_monitor ); + } + } + + /** + * Cancels the MultiInstance (Parallel or Repeat) + */ + public void cancel() { + super.cancel(); + propagateCancel( null ); + } + + /** + * Propagates cancel to all branches + */ + public void propagateCancel(Branch skip) + { + if ( branches == null ) + return; + for ( Iterator/**/ i = + branches.iterator(); i.hasNext(); ) { + Branch b = (Branch) i.next(); + if ( b != skip ) + b.cancel(); + } + } + + /** + * Process all subgoals in parallel, and complete when they + * complete. + */ + public States action(String head,Data d) + throws LoopEndException, ParallelEndException { + if ( branches == null ) { + branches = new Vector/**/(); + for ( int i = 0; more( i, d ); i++ ) { + Instance instance = getBranch( i, head + ":" + i, d ); + Branch b = new Branch( i, instance, d, outs ); + branches.add( b ); + } + } + + if ( runnable_monitor != null ) { + removeMonitor( d ); + } + + int icount = 0; + int bcount = 0; + + try { + while ( branches.size() > 0 ) { + States s = ((Branch)branches.get( 0 )).action(); + if ( s == States.STOPPED || s == States.BLOCKED ) { + branches.add( branches.remove( 0 ) ); + icount += 1; + if ( s == States.BLOCKED ) + bcount += 1; + if ( icount < branches.size() ) + continue; + if ( bcount != icount ) + return States.STOPPED; + setupMonitor( d ); + return States.BLOCKED; + } + icount = bcount = 0; + if ( s == States.PASSED ) { + branches.remove( 0 ); + continue; + } + propagateCancel( (Branch)branches.get( 0 ) ); + return s; + } + } catch (LoopEndException e) { + propagateCancel( null ); + throw e; + } catch (ParallelEndException e) { + propagateCancel( (Branch) branches.get( 0 ) ); + // Discard data from cancelled branches + } finally { + // Signal elevated branch outputs in calling data context. + for ( Iterator/**/ i = + outs.iterator(); i.hasNext(); ) { + String key = (String) i.next(); + d.ready( key ); + } + } + // TODO join collated data + return States.PASSED; + } + + /** + * The method for determining whether there is another branch. + */ + abstract public boolean more(int i,Data d); + + /** + * Utility method to obtain the actual branch instance. + */ + abstract public Instance getBranch(int i,String head,Data d); + + } + +} diff --git a/com/intendico/gorite/Capability.java b/com/intendico/gorite/Capability.java new file mode 100644 index 0000000..1bff038 --- /dev/null +++ b/com/intendico/gorite/Capability.java @@ -0,0 +1,427 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Inquirable; +import com.intendico.data.Query; +import com.intendico.data.Rule; +import com.intendico.data.Store; +import com.intendico.gorite.addon.Reflector; +import java.util.Collection; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; + +/** + * A Capability is a container of {@link Goal} hierarchies, which are + * accessible by their names, and understood to represent alternative + * ways in which that named goal may be achieved. It provides lookup + * context for {@link BDIGoal} goals. + * + *

A Capability may also contain {@link Rule} objects, which are + * added by the {@link #addRule} method. The {@link Rule} objects are + * collated into the owning {@link Performer} {@link + * Performer#rule_set rule_set} to be applied as part of the goal + * execution. + */ +public class Capability { + + /** + * The goals of this capability, clustered by name. + */ + private Hashtable/*>*/ goals = + new Hashtable/*>*/(); + + /** + * The table of inqueriable belief structures of this capability, + * primarily understood qua the {@link Inquirable} interface. This + * table is initially built at construction time by {@link + * #putInquirable(Inquirable)} calls, and by elevating + * inqueriables of sub capabilities that are added to this + * capability. Eventually, there is also a {@link + * #shareInquirables} invocation, which accepts incoming + * inqueriables, and the distributes this {@link Inquirable} + * collection downwards to sub capabilities. In general, all + * capabilities form the same name-to-store mapping throughout a + * performer, except where name conflicts arise. + */ + private Hashtable/**/ inquirables = + new Hashtable/**/(); + + /** + * The inner capabilities of this capability. + */ + private Vector/**/ inner; + + /** + * The {@link Rule} objects of this capability. + */ + private Vector/**/ rules; + + /** + * The performer that has this capability. + */ + Performer performer; + + /** + * This interface is implemented by the deferred text form + * entities (rules or reflectors). + */ + public interface Deferred { + /** + * The method by which this entity installs itself. + */ + public void install(); + } + + /** + * The collection of deferred entities. This is set to null by the + * first {@link #shareInquirables(Collection)} call. + */ + private Vector/**/ deferred = new Vector/**/(); + + /** + * Utility method to install a Deferred object subsequent to the + * first invokation of {@link #shareInquirables(Collection)} + */ + public void add(Deferred entity) { + if ( deferred == null ) { + entity.install(); + } else { + deferred.add( entity ); + } + } + + /** + * Constructs a vector of all goals under the given name, by + * considering the local table and recursively from inner + * capabilities. + */ + public Vector/**/ lookup(String name) { + Vector/**/ g = new Vector/**/(); + if ( goals.get( name ) != null ) + g.addAll( (Vector/**/) goals.get( name ) ); + if ( inner != null ) { + for ( Iterator/**/ e = + inner.iterator(); e.hasNext(); ) { + g.addAll( ((Capability)e.next()).lookup( name ) ); + } + } + return g; + } + + /** + * Add a goal to this capability. + */ + public void addGoal(Goal g) { + Vector/**/ v = (Vector/**/) goals.get( g.getGoalName() ); + if ( v == null ) { + v = new Vector/**/(); + goals.put( g.getGoalName(), v ); + } + v.add( g ); + } + + /** + * Add a plan to this capability. + */ + public void addPlan(Plan g) { + addGoal( g ); + } + + /** + * Add an inner capability. All {@link #inquirables} of the added + * capability are added to the {@link #inquirables} of this + * capability, unless their name starts with underscore, or an + * equally named {@link Inquirable} is already defined. + * + *

Note that a capability tree built by adding children before + * grand children yield a different, less complete elevation of + * inquirables than if the grand children are added to the + * children first. + * + *

When adding a capability late (i.e., after the first + * shareInquirables invocation, or more precisely when {@link + * #deferred} is null), then {@link #shareInquirables} is invoked; + * it's only invoked on the added capability unless an inquirable + * was gained, in which case {@link #shareInquirables} is invoked + * on this capability, for distributing the gained inquirables + * over the whole capability sub tree. + * + * @see #shareInquirables(Collection) + */ + public void addCapability(Capability c) { + if ( inner == null ) + inner = new Vector/**/(); + if ( ! inner.contains( c ) ) + inner.add( c ); + if ( performer != null ) { + c.setPerformer( performer ); + } + boolean extended = false; + for ( Iterator/**/ si = c.inquirables.values().iterator(); + si.hasNext(); ) { + Inquirable inquirable = (Inquirable) si.next(); + String name = inquirable.getName(); + if ( ( ! name.startsWith( "_" ) ) && + inquirables.get( name ) == null ) { + putInquirable( inquirable ); + extended = true; + } + } + if ( deferred == null ) { + if ( extended ) { + shareInquirables( null ); + } else { + c.shareInquirables( inquirables.values() ); + } + } + } + + /** + * Creates a new {@link Rule} object with given antecedent and + * consequent {@link Query} objects. + */ + public void addRule(Query a,Query c) { + if ( rules == null ) + rules = new Vector/**/(); + Rule r = new Rule( a, c ); + rules.add( r ); + if ( performer != null ) { + if ( Goal.isTracing() ) + System.err.println( "** Installing " + r + " for " + performer ); + performer.rule_set.add( r ); + } + } + + /** + * Utility method to lookup a given goal name. + */ + public boolean hasGoal(String name) { + if ( goals.get( name ) != null ) + return true; + if ( inner == null ) + return false; + for ( Iterator/**/ ci = inner.iterator(); ci.hasNext(); ) { + if ( ((Capability) ci.next()).hasGoal( name ) ) + return true; + } + return false; + } + + /** + * Tells whether or not this capability provides some method or + * methods for all named goals. + */ + public boolean hasGoals(String [] gs) { + if ( gs == null ) + return true; + for ( int i = 0; i < gs.length; i++ ) { + if ( ! hasGoal( gs[i] ) ) + return false; + } + return true; + } + + /** + * Returns an Inquirable qua Store, or null if it's not a Store. + */ + public Store getStore(String name) { + Object x = inquirables.get( name ); + if ( x instanceof Store ) + return (Store) x; + return null; + } + + /** + * Returns the Inquirable as mapped, or null if not found. + */ + public Inquirable getInquirable(String name) { + return (Inquirable) inquirables.get( name ); + } + + /** + * Registers an {@link Inquirable} to be mapped. A Capability + * extension should register all locally created inquirables. + */ + public void putInquirable(Inquirable inquirable) { + inquirables.put( inquirable.getName(), inquirable ); + } + + /** + * Registers an {@link Inquirable} under an alias. A Capability + * extension should register all locally created inquirables. + */ + public void putInquirable(String name,Inquirable inquirable) { + inquirables.put( name, inquirable ); + } + + /** + * This method extends the inquirables collection with the given + * collection, then propagates its map downwards to sub + * capabilities. Finally all {@link Deferred} entities held in + * {@link #deferred} are installed, before setting {@link + * #deferred} to null. + * + *

Note that a {@link Performer} (which is a Capability) will + * invoke this once the first time it is added to the {@link + * Executor default executor}. A multi {@link Executor} model may + * need additional, explicit initial calls to this method in order + * to establish complete inquirables sharing. + * + * @see #addCapability(Capability) + */ + public void shareInquirables(Collection/**/ shared) { + if ( Goal.isTracing() ) { + System.err.println( "** Sharing inquirables in " + this ); + } + // Install incoming inquirables unless defined locally + if ( shared != null ) { + for ( Iterator/**/ si = + shared.iterator(); si.hasNext(); ) { + Inquirable inquirable = (Inquirable) si.next(); + if ( inquirables.get( inquirable.getName() ) == null ) { + putInquirable( inquirable ); + } + } + } + // Propagate all local inquirables to inner capabilities + if ( inner != null ) { + shared = inquirables.values(); + for ( Iterator/**/ i = inner.iterator(); + i.hasNext(); ) { + Capability c = (Capability) i.next(); + c.shareInquirables( shared ); + } + } + // Install deferred entities + if ( deferred != null ) { + for ( Iterator/**/ di = deferred.iterator(); + di.hasNext(); ) { + ((Deferred) di.next()).install(); + } + } + deferred = null; + } + + /** + * Sets the performer for this capability, and propagates it to + * all inner capabilities. + */ + public void setPerformer(Performer p) { + if ( performer != null ) { + if ( performer != p ) { + // The capability is moved + throw new Error( "Invalid GORITE model" ); + } + return; + } + performer = p; + if ( rules != null ) { + for ( Iterator/**/ i = rules.iterator(); i.hasNext(); ) { + Rule r = (Rule) i.next(); + performer.rule_set.add( r ); + } + } + if ( inner != null ) { + for ( Iterator/**/ i = + inner.iterator(); i.hasNext(); ) { + Capability c = (Capability) i.next(); + c.setPerformer( p ); + } + } + initialize(); + } + + /** + * Overridable method where to add Capability initialisation + * code. This method is invoked when the performer is set up, and + * all inner capabilites are initialized. This base implementation + * does not do anything. + */ + public void initialize() { + } + + /** + * Returns the performer that has this capability. + */ + public Performer getPerformer() { + return performer; + } + + /** + * Utility method that creates the goal of establishing a + * task team. + */ + public Goal deploy(String name) { + return new Goal( "deploy " + name ) { + public States execute(Data d) { + String ttn = getGoalName().substring( 7 ); + Team t = (Team) performer; + Team.TaskTeam tt = t.getTaskTeam( ttn ); + if ( tt == null ) + throw new Error( "Missing task team '" + ttn + + "' in team '" + t.getName() + "'" ); + if ( tt.establish( d ) ) + return States.PASSED; + return States.FAILED; + } + }; + } + + /** + * Adds a {@link Reflector} to this capability. The actual + * addition is deferred until after the inquirables are shared, at + * which time the (@link Performer} is known. + */ + public void addReflector( + final String goal,final String todo,final Query query) { + add( new Deferred() { + /** + * Installs this as a proper {@link Reflector}. + */ + public void install() { + Reflector r = new Reflector( getPerformer(), goal, todo ); + r.addQuery( query ); + } + } ); + } + + /** + * Returns the {@link #inquirables} collection. + */ + public Hashtable/**/ getInquirables() { + return inquirables; + } + + /** + * Return the goals table. + */ + public Hashtable/*>*/ getGoals() { + return goals; + } + + /** + * Return the inner capabilities, if any. + */ + public Vector/**/ getInner() { + return inner; + } +} diff --git a/com/intendico/gorite/ConditionGoal.java b/com/intendico/gorite/ConditionGoal.java new file mode 100644 index 0000000..127b136 --- /dev/null +++ b/com/intendico/gorite/ConditionGoal.java @@ -0,0 +1,117 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * A ConditionGoal is achieved by achieving any sub goal, which are + * attempted in sequence. The goal fails when and if all sub goals + * have been attempted and failed. + */ +public class ConditionGoal extends Goal { + + /** + * Constructor. + */ + public ConditionGoal(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Convenience constructor without sub goals. + */ + public ConditionGoal(String n) { + this( n, null ); + } + + /** + * Creates and returns an instance object for achieving + * a ConditionGoal. + */ + public Instance instantiate(String head,Data d) { + return new ConditionInstance( head ); + } + + /** + * Implements sequential, conditional sub goal execution + * attempt. I.e., if a sub goal fails, then try next, otherwise + * succeed. + */ + public class ConditionInstance extends Instance { + + /** + * Constructor. + */ + public ConditionInstance(String h) { + super( h ); + } + + /** + * Next sub goal index. + */ + public int index = 0; + + /** + * Current ongoing Instance. + */ + public Instance ongoing = null; + + /** + * Cancels this execution by propagating the cancel call to + * all sub goals. + */ + public void cancel() { + super.cancel(); + if ( ongoing != null ) + ongoing.cancel(); + } + + /** + * Instantiates and performs sub goals in sequence, until the + * first one that does not fail. If a sub goal fails, then + * that is caught, and the next sub goal in sequence is + * instantiated and performed. + */ + public States action(String head,Data d) + throws LoopEndException, ParallelEndException { + Goal [] subgoals = getGoalSubgoals(); + if ( subgoals == null ) + return States.FAILED; + while ( index < subgoals.length ) { + if ( ongoing == null ) { + ongoing = subgoals[ index ].instantiate( + head + "." + index, d ); + } + States s = ongoing.perform( d ); + if ( s != States.FAILED ) + return s; + index += 1; + ongoing = null; + } + return States.FAILED; + } + } +} diff --git a/com/intendico/gorite/Context.java b/com/intendico/gorite/Context.java new file mode 100644 index 0000000..40da3c9 --- /dev/null +++ b/com/intendico/gorite/Context.java @@ -0,0 +1,107 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; +import com.intendico.data.Condition; + +/** + * The Context interface is implemented by a plan (i.e. top level + * goal) that has different variants for different contexts. The + * different contexts are defined by means of a {@link Query} that + * provides multiple bindings, and each binding defines a variant + * execution of the plan. + * @see Plan + * @see BDIGoal + */ + +public interface Context { + + /** + * A constant that a {@link #context} method may return when a + * plan doesn't have any applicable variants. Returning this + * {@link Query}, which is eternally false, has the same effect as + * throwing a {@link None} exception in marking that the plan does + * not have any applicable variants. The point is that a null + * return from the {@link #context} method means that there is no + * context query, and that the goal with this context is + * applicable once. + */ + public static final Query EMPTY = new Condition() { + public boolean condition() { + return false; + } + }; + + /** + * A constant that a {@link #context} method may return when a + * plan has a single applicable variant without bindings to {@link + * Ref} objects. Returning this {@link Query}, which in fact is + * null, has the same effect as returning null in marking that the + * plan has a single applicable variant. The point is that a null + * return from the {@link #context} method means that there is no + * context query, and therefore the plan with this context is + * applicable. + */ + public static final Query TRUE = null; + + /** + * A constant that a {@link #context} method may return when a + * plan doesn't have any applicable variants. This is merely an + * alias for {@link #EMPTY} added for convenience. + */ + public static final Query FALSE = EMPTY; + + /** + * An exception that a {@link #context} method may throw in + * order to mark that there is no applicable context. + * @see #EMPTY + */ + public static class None extends Exception { + + /** + * A default constructor. + */ + public None() { + super( "no applicable context" ); + } + + /** + * Version identity required for serialization. + */ + public static final long serialVersionUID = 1L; + } + + /** + * The context method returns the {@link Query} that defines the + * multiple alternative contexts for the execution. The {@link + * #context} method is invoked for defining the {@link Query}. The + * method may return null, which means that the plan has a single + * applicable variant, or it may return {@link #EMPTY} or throw + * the {@link None} excpetion, which both indicate that the plan + * does not have any applicable variant (right now). Any other + * query is taken as defining the alternative applicable contexts. + * @see BDIGoal.BDIInstance#contextual + */ + public Query context(Data d) throws Exception; + + +} diff --git a/com/intendico/gorite/ContextualGoal.java b/com/intendico/gorite/ContextualGoal.java new file mode 100644 index 0000000..abd52bb --- /dev/null +++ b/com/intendico/gorite/ContextualGoal.java @@ -0,0 +1,213 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.*; +import java.util.Vector; + +/** + * This is a utility class used by BDIGoal execution, to associate a + * particular binding context with performing a goal. It is not + * intended for explicit use within goal hierarchies. + * + *

The {@link BDIGoal} creates individual ContextualGoal objects + * for each applicable plan option where the plans {@link + * Context#context} method returns a {@link Query} that offers + * multiple bindings (i.e., not null, {@link Context#EMPTY} or {@link + * Context#FALSE}). Plan options whose {@link Context#context} method + * returns null are collated without the wrapping of a ContextualGoal + * object, and plan options whose {@link Context#context} method + * returns {@link Context#EMPTY} or {@link Context#FALSE} are not + * applicable. + * + *

The ContextualGoal captures the entry data settings for the + * variables as well as the proposed settings that defines the plan + * option. It then manages the set up of the proposed settings into + * the data prior to executing the plan body, and the restoration of + * these settings if the plan body execution fails. (The variable + * settings are not restored if the ContextualGoal execution is + * cancelled) + * + * @see BDIGoal + */ +public class ContextualGoal extends Goal implements Precedence { + + /** + * Holds the Data object for the calling BDIInstance that created this + * ContextualGoal. + */ + public Data goal_data; + + /** + * Holds the actual {@link Ref} objects of the context + * {@link Query}. + */ + public Vector/**/ variables; + + /** + * Holds {@link Ref} objects with values representing this + * invocation context. + */ + public Vector/**/ context; + + /** + * The original assignments for the variables. + */ + public Vector/**/ original; + + /** + * Two ContextualGoal are equal if they are of the same goal and + * have the same context. + */ + public boolean equals(Object c) { + return c instanceof ContextualGoal && equals( (ContextualGoal) c ); + } + + /** + * Two ContextualGoal are equal if they are of the same goal + * and have the same context. + */ + public boolean equals(ContextualGoal c) { + if ( ! c.getGoalSubgoals()[0].equals( getGoalSubgoals()[0] ) ) + return false; + if ( context == null ) { + return c.context == null; + } + return Ref.equals( c.context, context ); + } + + /** + * Constructor. + */ + public ContextualGoal(Vector/**/ orig,Vector/**/ vars, + Goal goal,Data data) { + super( goal.getGoalName() ); + goal_data = data; + original = orig; + variables = vars; + if ( variables != null ) + context = Ref.copy( variables ); + setGoalSubgoals( new Goal[] { goal } ); + } + + /** + * A wrapping for invoking precedence of the wrapped goal. If the + * wrapped goal provides a precedence method, then it is invoked + * using the cached {@link #goal_data}, transiently establishing + * the invocation context, which is restored upon return. + */ + public int precedence(Data data) { + if ( ! ( getGoalSubgoals()[ 0 ] instanceof Precedence ) ) + return Plan.DEFAULT_PRECEDENCE; + if ( context == null ) + return ((Precedence) getGoalSubgoals()[ 0 ]) + .precedence( goal_data ); + goal_data.set( context ); + Ref.bind( variables, context ); + int x = ((Precedence) getGoalSubgoals()[ 0 ]).precedence( goal_data ); + Ref.bind( variables, original ); + goal_data.get( context, false ); + return x; + } + + /** + * Return the intention instance to be executed. + */ + public Instance instantiate(String head,Data d) { + // Assuming data == d without checking it + return new ContextualGoalInstance( head, d ); + } + + /** + * Utility class to represent the intention + */ + public class ContextualGoalInstance extends Instance { + + /** + * The target intention instance + */ + public Instance instance; + + /** + * Constructor. This established the invocation context in the + * intention {@link Data}. + */ + public ContextualGoalInstance(String h,Data d) { + super( h ); + if ( context != null ) { + d.set( context ); + Ref.bind( variables, context ); + } + String sub = context != null? Ref.toString( context ) : "{}"; + instance = getGoalSubgoals()[ 0 ].instantiate( h + sub, d ); + } + + /** + * Control callback whereby the intention gets notified that + * it is cancelled. This will forward the cancellation to the + * currently progressing instance, if any. + */ + public void cancel() { + if ( instance != null ) + instance.cancel(); + } + + /** + * Excution of this goal, which means to execute the wrapped + * goal. If successful, the invocation {@link Ref} objects are + * updated from the final {@link Data}. Execution catches + * LoopEndException, but not ParallelEndException, and makes + * it cause a failure rather then propagate up. If execution + * fails, the {@link Data} context is restored by removing the + * local context. + */ + public States action(String head,Data d) + throws LoopEndException, ParallelEndException { + States s = States.FAILED; + try { + s = instance.perform( d ); + } catch (LoopEndException e) { + s = States.FAILED; + } catch (ParallelEndException e) { + s = States.FAILED; + throw e; // Re-thrown + } finally { + if ( variables != null ) { + if ( s == States.PASSED ) { + d.get( variables, true ); + } else if ( s == States.FAILED ) { + Ref.bind( variables, original ); + d.get( context, false ); + } + } + } + return s; + } + + } // End of class ContextualGoalInstance + + /** + * Textual representation of this goal. + */ + public String toString() { + return ( context != null? context.toString() : "{}" ) + + getGoalSubgoals()[ 0 ].toString(); + } +} diff --git a/com/intendico/gorite/ControlGoal.java b/com/intendico/gorite/ControlGoal.java new file mode 100644 index 0000000..db7b344 --- /dev/null +++ b/com/intendico/gorite/ControlGoal.java @@ -0,0 +1,115 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * A ControlGoal is achieved by performing all its sub goals in + * sequence, which results in a control effect on an enclosing + * parallel execution by terminating all other branches when it + * succeeds. Thus, the execution either fails, or continues after the + * enclosing parallel goal. + * + *

The following code snippet illustrates an optional interrupt, + * i.e., how to either interrupt or succeed (rather than either + * interrupt or fail). + *

+ * new FailGoal( "not interrupting is fine", new Goal [] {
+ *     new ControlGoal( "maybe interrupt", new Goal {
+ *        // Goal sequence for causing interrupt if achieved successfully
+ *     } );
+ * }
+ * 
+ * + *

Note that the ControlGoal effect propagates up to the nearest + * parallel type goal through {@link BDIGoal} execution, and it + * therefore is not necessary for the ControlGoal to be explicitly + * within the sub goal hiearachy of the parallel type goal of + * concern. + * + *

In particular, a {@link Plan} of a {@link Team.Role} may + * include a ControlGoal to express that a {@link TeamGoal} is + * achieved by any one filler rather than requiring all of them. + * + * @see ParallelGoal + * @see RepeatGoal + * @see TeamGoal + * @see FailGoal + * @see Team.Role + */ +public class ControlGoal extends SequenceGoal { + + /** + * Constructor. + */ + public ControlGoal(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Convenience constructor without sub goals. + */ + public ControlGoal(String n) { + this( n, null ); + } + + /** + * Creates and returns an instance object for achieving + * a ControlGoal. + */ + public Instance instantiate(String head,Data d) { + return new ControlInstance( head ); + } + + /** + * Implements a control action for parallel execution. + */ + public class ControlInstance extends SequenceInstance { + + /** + * Constructor. + */ + public ControlInstance(String h) { + super( h ); + } + + /** + * Process all subgoals in sequence, then throw a + * LoopEndException if success. If a subgoals fail, then fail + * without throwing exception. + */ + public States action(String head,Data d) + throws LoopEndException, ParallelEndException { + States s = super.action( head, d ); + if ( s == States.PASSED ) { + throw new ParallelEndException( head ); + } + return s; + } + } + + +} diff --git a/com/intendico/gorite/Data.java b/com/intendico/gorite/Data.java new file mode 100644 index 0000000..877c2e3 --- /dev/null +++ b/com/intendico/gorite/Data.java @@ -0,0 +1,935 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.Enumeration; +import java.util.Formatter; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; +import java.util.Observable; +import java.util.Observer; + +/** + * This is a container class for data elements that goals use and + * produce. + * + *

The data that a business process is instantiated for has an + * internal structure which allows references to individual data + * elements, and that a data element is either singular or a plurality + * of other data elements. + * + *

A model includes data references, primarily in connection with + * task goals where the data references identify the data elements + * that the associated tasks are to operate on, but also in connection + * with repeat goals, which get instantiated for the constituent + * elements of a plural element. + * + *

Any actual transformation of data is done in tasks, and the + * business process model is limited to data reference. + * + *

A task is associated with two collections of data elements, + * known as inputs and results. The association is defined by means of + * using data element names, which are symbols that refer to + * individual or groups of data elements. + * + *

A data object is a container for data references, which are + * names of inidividual or groups of data elements. + */ +public class Data { + + /** + * This is a base class for data elements. + */ + public class Element { + + /** + * The name of this data element. + */ + public final String name; + + /** + * The values of this data element. + */ + public Vector/**/ values = new Vector/**/(); + + /** + * Constructor with initial value + */ + public Element(String n,Object v) { + name = n; + if ( v != null ) + values.insertElementAt( v, 0 ); + } + + /** + * Constructor. + */ + public Element(String n) { + this( n, null ); + } + + /** + * Tracking whether this element is ready or not. + */ + public boolean ready = false; + + /** + * Mark data as ready. + */ + public synchronized void markReady() { + ready = true; + } + + /** + * Utility method to set value and mark ready. + */ + public void set(Object v) + { + set( v, true ); + } + + /** + * Utility method to set value and maybe mark ready. + */ + public void set(Object v,boolean mark) + { + values.insertElementAt( v, 0 ); + if ( goldfish ) + forget( 1 ); + if ( mark ) + markReady(); + } + + /** + * Utility method to return top of values. + */ + public Object get() { + return values.size() > 0? values.get( 0 ) : null ; + } + + /** + * Utility method to return and remove top of values. + */ + public Object pop() { + if ( values.size() == 0 ) + return null; + return values.remove( 0 ); + } + + /** + * Utility method to forget all but N values. A given N less + * than 1 results in silently using 1. + */ + public void forget(int n) { + if ( n >= 0 ) { + while ( values.size() > n ) { + values.remove( n ); + } + } else { + while ( n++ < 0 && values.size() > 0 ) { + values.remove( 0 ); + } + } + } + + /** + * Returns the i:th sub element, if any, or null otherwise. + */ + public Object get(int i) + { + return ( i < values.size() )? values.get( i ): null; + } + + /** + * Returns the number of values. + */ + public int size() { + return values.size(); + } + + /** + * Text representation of this data element. + */ + public String toString() { + /********* 1.5 + Formatter f = new Formatter( new StringBuilder() ); + f.format( "\"%s\" ", name ); + if ( ready ) { + f.format( "%s", values.toString() ); + } else { + f.format( "[not ready]" ); + } + return f.toString(); + **********/ + StringBuilder s = new StringBuilder(); + s.append( "\"" ); + s.append( name ); + s.append( "\" " ); + if ( ready ) { + s.append( values.toString() ); + } else { + s.append( "[not ready]" ); + } + return s.toString(); + } + } + + /** + * Utility method to attach an observer to a given observable, to + * be used for triggering continuation of blocked intention. + */ + public void setTrigger(Observable s) { + getBindings().setTrigger( s ); + } + + /** + * Utility method to attach an observer to a given query, to be + * used for triggering continuation of blocked intention. The + * query is reset and re-tested upon notification, and if true, + * the blocked intention is allowed to continue. + */ + public void setTrigger(Query q) { + getBindings().setTrigger( q ); + } + + /** + * Utility method to remove a trigger. + */ + public void clearTrigger() { + getBindings().clearTrigger(); + } + + /** + * Utility method to check whether a goal instance execution is + * running or not. All instance executions are considered running + * unless there a trigger is set via a {@link #setTrigger + * setTrigger} call, and still armed. + */ + public boolean isRunning(String thread_name) { + return ! ((Bindings) bindings.get( thread_name )).monitoring ; + } + + /** + * Utility method to attach an observer on the intention-local + * bindings. The observer will then be notified when any trigger + * (see {@link #setTrigger setTrigger}) fires. + */ + public void addObserver(String thread_name,Observer x) { + ((Bindings) bindings.get( thread_name )).addObserver( x ); + } + + /** + * Utility method to remove a trigger observer. + */ + public void deleteObserver(String thread_name,Observer x) { + ((Bindings) bindings.get( thread_name )).deleteObserver( x ); + } + + /** + * A class for allowing transient name space split. + */ + public class Bindings extends Observable { + + /** + * The intention thread name that caused this Bindings + * object. + */ + private String name; + + /** + * A bindings-local flag to indicate that the associated + * intention is has an unblocking monitor. + */ + public boolean monitoring = false; + + /** + * Utility method to clear the monitoring flag. + */ + void clearTrigger() { + monitoring = false; + } + + /** + * Utility method to clear the monitoring flag and propagate + * notification to any observer. + */ + void clearMonitoring() { + if ( monitoring ) { + if ( Goal.isTracing() ) { + System.err.println( + "** Clearing monitoring for " + name ); + } + monitoring = false; + setChanged(); + notifyObservers(); + unsetFlag(); + //setFlag(); // and wake executor.... + Performer p = (Performer) getValue( Goal.PERFORMER ); + if ( p != null ) { + p.signalExecutor(); + } + } + } + + /** + * Utility method to attach an Observer to the given + * Observable, to capture its notification as a trigger. + */ + public void setTrigger(Observable s) { + monitoring = true; + setFlag(); + s.addObserver( new Observer() { + public void update(Observable x,Object y) { + if ( Goal.isTracing() ) { + System.err.println( + "** Observer updated by " + x + + " for " + name ); + } + if ( monitoring ) + clearMonitoring(); + x.deleteObserver( this ); + } + } ); + } + + /** + * Utility class to observe a query, to allow an intention to + * continue when the query becomes true. + */ + private class QueryMonitor implements Observer { + + /** + * The query being observed. + */ + Query query; + + /** + * Constructor. + */ + QueryMonitor(Query q) { + monitoring = true; + query = q; + if ( q != null ) + q.addObserver( this ); + update( null, null ); + } + + /** + * The Observer callback method. This will call reset() + * and next() on the given query, and optionally reset + * monitoring and call setFlag(). + */ + synchronized public void update(Observable x,Object y) { + if ( Goal.isTracing() ) { + System.err.println( + "** Query " + query + " updated by " + x ); + } + if ( ! monitoring ) { + if ( query != null ) + query.deleteObserver( this ); + return; + } + boolean test = false; + try { + if ( Goal.isTracing() ) { + System.err.println( "** Query reset on " + query ); + } + if ( query != null ) + query.reset(); + if ( Goal.isTracing() ) { + System.err.println( "** Query next on " + query ); + } + test = ( query != null )? query.next() : true; + if ( Goal.isTracing() ) { + System.err.println( + "** Query " + query + " is " + test ); + } + } catch (Exception e) { + e.printStackTrace(); + } + if ( test ) { + clearMonitoring(); + if ( query != null ) + query.deleteObserver( this ); + } + if ( Goal.isTracing() ) { + System.err.println( + "** Query data: " + Data.this.toString() ); + } + } + + } + + /** + * Utility method to attach an Observer to the given Query, to + * capture its becoming true as a trigger. + */ + public void setTrigger(Query q) { + new QueryMonitor( q ); + } + + /** + * This is the transient data store for an execution path. + */ + public Hashtable/**/ elements = + new Hashtable/**/(); + + /** + * Link to a prior Bindings object; the base of this one. + */ + Bindings prior = null; + + /** + * Constructor. + */ + public Bindings(String n,Bindings p) + { + name = n; + prior = p; + } + + /** + * Special constructor to transiently bind a given name to its + * i:th sub element, if it exists. + */ + public Bindings(String n,Bindings p,String name,int i) + { + this.name = n; + prior = p; + if ( name != null ) { + Element e = Data.this.find( p, name ); + if ( e != null ) { + Object v = e.get( i ); + if ( v != null ) { + Element x = new Element( name, v ); + x.markReady(); + elements.put( name, x ); + } + } + } + } + + /** + * Returns the named data elements, if any, or null otherwise. + */ + public Element find(String name) + { + return (Element) elements.get( name ); + } + + /** + * Utility method to create an Element instance. + */ + public Element create(String name) { + Element e = (Element) elements.get( name ); + if ( e == null ) { + e = new Element( name ); + elements.put( name, e ); + } + return e; + } + + /** + * Utility method that pushes all data except the given one + * into the prior bindings. + */ + public void push(String except,HashSet/**/ h) { + if ( prior == null ) + return; + boolean gf = goldfish; + goldfish = false; + for ( Enumeration/**/ i = elements.keys(); + i.hasMoreElements(); ) { + String key = (String) i.nextElement(); + if ( key.equals( except ) ) + continue; + h.add( key ); + Element e = prior.create( key ); + e.set( ((Element) elements.get( key )).get(), false ); + } + goldfish = gf; + } + + /** + * Returns a String representation of this Bindings object. + */ + public String toString() { + /************ 1.5 + Formatter f = new Formatter( new StringBuilder() ); + f.format( "Data.Bindings %s", name ); + if ( prior != null ) + f.format( " [prior = %s]", prior.name ); + for (Iterator/** / i = + elements.keySet().iterator(); i.hasNext(); ) { + f.format( "\n %s", elements.get( i.next() ).toString() ); + } + return f.toString(); + ***************/ + + StringBuilder s = new StringBuilder(); + s.append( "Data.Bindings " ); + s.append( name ); + if ( prior != null ) { + s.append( " [prior = " ); + s.append( prior.name ); + s.append( "]" ); + } + for (Iterator/**/ i = + elements.keySet().iterator(); i.hasNext(); ) { + s.append( "\n " ); + s.append( elements.get( i.next() ).toString() ); + } + return s.toString(); + } + } + + /** + * Flags this data into goldfish mode, which means for data + * elements to forget past values when new values are set. + */ + public boolean goldfish = false; + + /** + * Keeps the 'global' bindings object. + */ + public Hashtable/**/ bindings = + new Hashtable/**/(); + + /** + * Utility method that links a thread name with the current + * bindings. This may be overridden later by parallel goals, which + * get their local bindings. + */ + public void link(String thread_name) { + bindings.put( thread_name, getBindings() ); + } + + /** + * Utility method to remove a bindings entry. + */ + public void dropBindings(String thread_name) { + bindings.remove( thread_name ); + } + + /** + * Utility method to locate an element from within a "thread". + */ + public Element getLocalElement(String thread_name,String name) { + Bindings b = (Bindings) bindings.get( thread_name ); + return find( b != null? b : getBindings(), name ); + } + + /** + * Utility method that creates a thread-local binding for the + * given name to its i:th sub element. If name is null, or the + * i:th sub element missing, a thread local bindings object is + * still created, but without initialising the name. + */ + public synchronized void fork(String name,int i,String thread_name) + { + bindings.put( thread_name, + new Bindings( thread_name, getBindings(), name, i ) ); + } + + /** + * Utility method to push data. All names of the given HashSet + * except the exception are pushd down to be sub elements of the + * equally named element of the prior bindings object. + */ + public synchronized void join( + String except,HashSet/**/ h,String thread_name) { + if ( h != null ) + getBindings().push( except, h ); + dropBindings( thread_name ); + } + + /** + * Obtains the bindings object chain for the calling thread. + */ + public Bindings getBindings() { + Bindings b = (Bindings) bindings.get( thread_name ); + if ( b == null ) { + b = new Bindings( thread_name, null ); + bindings.put( thread_name, b ); + } + return b; + } + + // + // Tracking of threads and data dependencies. + // + + /** + * Holds the current "thread name". + */ + public String thread_name = "X"; + + /** + * Change thread name and return the prior name. + */ + public String setThreadName(String n) { + String old = thread_name; + thread_name = n; + return old; + } + + /** + * Determine the call data element name from a given head string + * or thread_name string. + */ + public String getArgsName(String name) { + int i = name.indexOf( '"' ); + if ( i < 0 ) + i = name.length(); + i = name.lastIndexOf( '*', i ); + if ( i < 0 ) + i = name.length(); + return name.substring( 0, i ) + "-ARG"; + } + + /** + * Determine the call data element name from the thread_name, and + * return its value. + * @see BDIGoal + */ + public Object getArgs(String head) { + return getValue( getArgsName( head ) ); + } + + /** + * The most recently assigned arg. + */ + public Object arg; + + /** + * Set up the call data agrument for the given instance head name + * as given, adding the "-ARG" suffix. + */ + public void setArgs(String name,Object x) { + arg = x; + if ( x != null ) { + setValue( name + "-ARG", x ); + } + } + + // + // Normal mode accessors + // + + /** + * Utility method to obtain data element value. + */ + public Object getValue(String name) { + Element e = find( name ); + return e != null? e.get() : null ; + } + + /** + * Utility method to obtain data element value as seen by a given + * "thread". + */ + public Object getValue(String name,String th_name) { + Bindings b = (Bindings) bindings.get( th_name ); + if ( b == null ) + return getValue( name ); + Element e = find( b, name ); + return e != null? e.get() : getValue( name ) ; + } + + /** + * Utility method to set data element value. + */ + public Data setValue(String name,Object v) { + create( name ).set( v ); + return this; + } + + /** + * Utility method to set data element value relative a given + * "thread". + */ + public Data setValue(String th_name, String name,Object v) { + String old = setThreadName( th_name ); + create( name ).set( v ); + setThreadName( old ); + return this; + } + + /** + * Utility method to delete all elements for a name along the + * whole binding chain. + */ + public void forget(String name) { + for ( Bindings b = getBindings(); b != null; b = b.prior ) { + b.elements.remove( name ); + } + } + + /** + * Utility method to forget all but n values for an element (i.e., + * its "innermost" Binding occurrence). If n < 0, then -n top + * values are forgotten. If n == 0, then everyhing is forgotten, + * except that the element remains. + */ + public void forget(String name,int n) + { + Element e = find( name ); + if ( e != null ) { + e.forget( n ); + } + } + + /** + * Utility method to replace top data element value. + */ + public Data replaceValue(String name,Object v) + { + Element e = create( name ); + e.pop(); + e.set( v ); + return this; + } + + /** + * Utility method to restore to a prior value. + */ + public Data restoreValue(String name,Object v) { + Element e = create( name ); + e.pop(); + if ( goldfish || e.size() == 0 ) { + e.set( v ); + } + return this; + } + + /** + * Find a given data element following the bindings chain of the + * calling thread. + */ + public synchronized Element find(String name) { + return find( getBindings(), name ); + } + + /** + * Find a given data element following the bindings chain of the + * calling thread. + */ + public Element find(Bindings b,String name) { + for ( ; b != null; b = b.prior ) { + Element e = (Element) b.elements.get( name ); + if ( e != null ) + return e; + } + return null; + } + + /** + * Tells whether there is an element of a given name in the + * bindings object chain of the calling thread or not + */ + public boolean hasElement(String name) + { + return find( name ) != null; + } + + /** + * Utility to tell how many values a data element has. + */ + public int size(String name) { + Element e = find( name ); + return e != null? e.values.size() : 0; + } + + /** + * Utility method to retrieve an Element instance on the calling + * thread's bindings chain, or create one at the head of the chain + * if missing. + */ + public synchronized Element create(String name) { + Element e = find( name ); + if ( e == null ) { + e = getBindings().create( name ); + } + return e; + } + + /** + * This method is used for querying about readiness of a data + * element. The method returns immediately, with true if the + * element is missing or not ready, and false otherwise. + */ + public boolean pending(String name) { + Element e = find( name ); + return e == null || ! e.ready ; + } + + /** + * Marks a named element as ready. If no such element exists, then + * this method will create a new one. + */ + public void ready(String name) { + create( name ).markReady(); + } + + /** + * Set data elements from the {@link Ref} objects of the given + * {@link Query}. + */ + public void set(Query query) { + set( query.getRefs( new Vector/**/() ) ); + } + + /** + * Set data elements from the given {@link Ref} objects, using + * {@link #replaceValue}. + */ + public void set(Vector/**/ refs) { + for ( Iterator/**/ i = refs.iterator(); i.hasNext(); ) { + Ref ref = (Ref) i.next(); + //setValue( ref.getName(), ref.get() ); + replaceValue( ref.getName(), ref.get() ); + } + } + + /** + * Set the {@link Ref} objects of a {@link Query} from data + * elements. + */ + public void get(Query query,boolean keep) { + get( query.getRefs( new Vector/**/() ), keep ); + } + + /** + * Set the given {@link Ref} objects from data elements. + */ + //@SuppressWarnings("unchecked") + public void get(Vector/**/ refs,boolean keep) { + for ( Iterator/**/ i = refs.iterator(); i.hasNext(); ) { + Ref ref = (Ref) i.next(); + Element e = find( ref.getName() ); + if ( e != null ) { + ref.set( keep? e.get() : e.pop() ); + } + } + } + + /** + * Set the given {@link Ref} object from its data element, without + * popping the data element value. + */ + //@SuppressWarnings("unchecked") + public void get(Ref/*...*/ [] refs) { + for ( int i = 0; i < refs.length; i++ ) { + Element e = find( refs[ i ].getName() ); + if ( e != null ) + refs[ i ].set( e.get() ); + } + } + + // + // Thread control support + // + + /** + * A rendezvous flag. + */ + public boolean flag = true; + + /** + * Utility method to raise the {@link #flag}. + */ + public synchronized void setFlag() { + if ( ! flag ) { + flag = true; + notifyAll(); + } + } + + public synchronized void unsetFlag() { + if ( flag ) { + flag = false; + notifyAll(); + } + } + + /** + * Utility method to wait for the rendezvous {@link #flag} to be + * set. + */ + public synchronized void waitFlag() { + while (!flag) { + try { + wait(); + } catch (InterruptedException e) { + } + } + flag = false; + } + + /** + * Returns a String representation of this Data object. + */ + public String toString() { + /************ 1.5 + Formatter f = new Formatter( new StringBuilder() ); + boolean nl = false; + HashSet/** / done = new HashSet/** /(); + for ( Iterator/** / i = + bindings.keySet().iterator(); i.hasNext(); ) { + String tn = (String) i.next(); + if ( nl ) + f.format( "\n" ); + Bindings b = bindings.get( tn ); + String h = "Data.Bindings " + b.name; + if ( ! done.contains( b.name ) ) + h = b.toString(); + f.format( "Intention %s --> %s", tn, h ); + done.add( b.name ); + nl = true; + } + return f.toString(); + **************/ + StringBuilder s = new StringBuilder(); + boolean nl = false; + HashSet/**/ done = new HashSet/**/(); + for ( Iterator/**/ i = + bindings.keySet().iterator(); i.hasNext(); ) { + String tn = (String) i.next(); + if ( nl ) + s.append( "\n" ); + Bindings b = (Bindings) bindings.get( tn ); + String h = "Data.Bindings " + b.name; + if ( ! done.contains( b.name ) ) + h = b.toString(); + s.append( "Intention " ); + s.append( tn ); + s.append( " --> " ); + s.append( h ); + done.add( b.name ); + nl = true; + } + return s.toString(); + } +} diff --git a/com/intendico/gorite/DataGoal.java b/com/intendico/gorite/DataGoal.java new file mode 100644 index 0000000..5b0e778 --- /dev/null +++ b/com/intendico/gorite/DataGoal.java @@ -0,0 +1,83 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +/** + * A DataGoal is a goal that uses the value of the data element named + * by the control attribute as the actual goal to achieve. This value + * is accessed and considered whenever the DataGoal is instantiated + * (via {@link #instantiate(String,Data)}), where it either is a + * {@link Goal}, a {@link Goal.Instance}, or some other non-null + * object. + *
    + * + *
  • If the value is null, the DataGoal will instantiate as a + * FailGoal, and thus fail when executed. + * + *
  • A Goal is instantiated and executed in lieu of the DataGoal, + * and likewise for a Goal.Instance, which is used as given and + * executed in lieu of the DataGoal. + * + * Note that a Goal.Instance execution may then a different Data + * object than for the DataGoal instantiation, and if so, there is no + * support for data transfer between the Data objects. Further, the + * Goal.Instance execution progresses from whatever execution state it + * has. + * + *
  • For any other type of object, the DataGoal creates a vacuous + * BDIGoal for the object, and that BDIGoal is instantiated and + * executed in lieu of the DataGoal. + *
+ * + *

Except for Goal.Instance values (which are already + * instantiated), the DataGoal goal adds the suffix "[xx]", where xx + * is the control data name, to the intention head, as its marker in + * an execution trace output. + */ +public class DataGoal extends Goal { + + /** + * Constructor with goal name and control data name. + */ + public DataGoal(String n,String c) { + super( n ); + setGoalControl( c ); + } + + /** + * Overrides the base class method {@link Goal#instantiate} to + * provide the instance for the data goal. + */ + public Instance instantiate(String h,Data d) { + Object g = d.getValue( getGoalControl() ); + if ( g instanceof Instance ) { + return (Instance) g; + } + String hh = h + "[" + getGoalControl() + "]"; + if ( g instanceof Goal ) { + return ((Goal) g).instantiate( hh, d ); + } + if ( g != null ) { + return new BDIGoal( g ).instantiate( hh, d ); + } + return new FailGoal( "Data getGoalControl() is null" ). + instantiate( hh, d ); + } +} diff --git a/com/intendico/gorite/EndGoal.java b/com/intendico/gorite/EndGoal.java new file mode 100644 index 0000000..891de31 --- /dev/null +++ b/com/intendico/gorite/EndGoal.java @@ -0,0 +1,127 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * An EndGoal is achieved by means of achieving its sub goals in + * sequence, and it will then cause an enclosing {@link LoopGoal} goal + * to be achieved. If any sub goal fails, the EndGoal will just + * succeed without causing the enclosing {@link LoopGoal} to + * succeed. An EndGoal never fails; either it succeeds or it causes + * the enclosing {@link LoopGoal} to succeed. + * + * The usage pattern for {@link LoopGoal} and EndGoal is as follows: + * + *

+ * new LoopGoal( "loop", new Goal [] {
+ *     .. // steps in the loop
+ *     .. new EndGoal( "break if", new Goal [] {
+ *            // optional sub goals for ending the loop if they succeed
+ *        } )
+ *     .. // more steps in the loop
+ * } )
+ * 
+ * + *

There may be any number of {@link EndGoal} to end the loop + * although of course only one will take effect. The {@link EndGoal} + * may be at any level within the LoopGoal goal hierarchy, except + * nested within another LoopGoal. + * + *

The EndLoop effect may be thought of as throwing an {@link + * Exception} that the nearest enclosing {@link LoopGoal} catches, + * interrupting the progress of any intermediate composite goal, + * including {@link ParallelGoal} and {@link RepeatGoal}. Ongoing + * branches of intermediate parallel goals will be cancelled. + * + *

The EndLoop effect does not propagate up through a {@link + * BDIGoal} execution of a {@link Plan} with non-null {@link + * Context#context(Data)} {@link Query}. I.e., the EndGoal must + * typically be explicitely within the sub goal hierarchy of the + * {@link LoopGoal} of concern. + * + *

However, the EndGoal effect does propagate up through a {@link + * BDIGoal} execution if the {@link Plan} has a null {@link + * Context#context(Data)}, or is an implementing {@link Goal} + * hierarchy without {@link Context}. + * + * @see LoopGoal + * @see SequenceGoal + * @see ParallelGoal + * @see RepeatGoal + */ +public class EndGoal extends SequenceGoal { + + /** + * Constructor. + */ + public EndGoal(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Convenience constructor without sub goals. + */ + public EndGoal(String n) { + this( n, null ); + } + + /** + * Creates and returns an instance object for achieving + * a EndGoal. + */ + public Instance instantiate(String head,Data d) { + return new EndInstance( head ); + } + + /** + * Implements a loop-end action ("break") + */ + public class EndInstance extends SequenceInstance { + + /** + * Constructor. + */ + public EndInstance(String h) { + super( h ); + } + + /** + * Process all subgoals in sequence, then throw a + * LoopEndException if success. If subgoals fail, then succeed + * without throwing exception. + */ + public States action(String head,Data d) + throws LoopEndException, ParallelEndException { + States s = super.action( head, d ); + if ( s == States.PASSED ) + throw new LoopEndException( head ); + if ( s == States.FAILED ) + return States.PASSED; + return s; + } + } +} diff --git a/com/intendico/gorite/Executor.java b/com/intendico/gorite/Executor.java new file mode 100644 index 0000000..b625089 --- /dev/null +++ b/com/intendico/gorite/Executor.java @@ -0,0 +1,440 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import java.util.Vector; +import java.util.Iterator; +import com.intendico.data.Ref; + +/** + * The Executor class implements functions for managing goal execution + * for a collection of performers. More precisely, it implements the + * dispatch thread control to the {@link Performer.TodoGroup} goal + * executions. + * + *

System execution may be controlled by invoking the {@link + * Performer#performGoal} method for the top level team. This will + * lead the execution thread into the Executor for progressing all + * {@link Performer.TodoGroup} executions. + * + *

The Executor class also includes a control loop, the {@link + * #run} method, for using a separate thread to run performers. This + * is an alternative execution principle to the use of {@link + * Performer#performGoal}. It's not a good idea to use both. + * + *

Ordinarily the use of the Executor class is transparent to an + * application developer. A single Executor object is created so as to + * provide thread control dispatch to all performers, and the top + * level {@link Performer#performGoal} utilizes this as needed. + * + *

An application may split the performers amongst several + * executors, and create background threads for {@link + * Performer.TodoGroup} executions. In that case, the developer must + * take precaution against multi-threading interferences, and in + * particular refrain from using {@link Performer#performGoal}. + * + *

The transfer of control between goal executions uses the {@link + * #steps} variable, which tells how many goal steps an execution may + * advance before being stopped to allow the thread to pursue another + * goal execution. + */ +public class Executor implements Runnable { + + /** + * This is a callback interface for monitoring the executor. + */ + public interface Listener { + /** + * This method is invoked when the Executor is going idle. + */ + public void goingIdle(Executor e); + /** + * This method is invoked when the Executor is waking up. + */ + public void wakingUp(Executor e); + } + + + /** + * Holds the current default executor that performers add + * themselves to by default. Initially null, but one is + * set up on demand if needed by {@link #getDefaultExecutor()}. + */ + public static Executor default_executor = null; + + /** + * Returns the default executor, and creates one if needed. + */ + public static Executor getDefaultExecutor() { + if ( default_executor == null ) + default_executor = new Executor( "[gorite]" ); + return default_executor; + } + + /** + * A name for the executor. This is also used as thread name for + * the executor's dedicated thread. + */ + public String name; + + /** + * Configuration value for how many {@link #changeFocus()} + * invocations to count between it returning true. + */ + public int steps = 40; + + /** + * Dynamic counter of how many {@link #changeFocus()} invocations + * remain until it will return true. + */ + public int count = steps; + + /** + * The {@link Performer} objects managed by this executor. + */ + public Vector/**/ performers = new Vector/**/(); + + /** + * A dynamic state variable. + */ + public boolean running = true; + + /** + * A dynamic state variable. Used by the {link #stop} method for + * telling the {@link #run} method to stop and exit when + * convenient. Value 1 is a "hard stop", that takes effect even + * while performers have things to do, and value 2 is a "soft + * stop" that takes effect when all performers are blocking. + */ + public int stopping = 0; + + /** + * A dynamic state variable. Index of the performer to re-run + * next. + */ + public int index = 0; + + /** + * The collection of listeners, which get notify about the + * thread going to sleep and waking up. + */ + public Vector/**/ listeners = new Vector/**/(); + + /** + * Utility method to notify all listeners + */ + public void notifyGoingIdle() { + Vector/**/ handlers = new Vector/**/(); + synchronized ( this ) { + handlers.addAll( listeners ); + } + for ( Iterator/**/ i = handlers.iterator(); + i.hasNext(); ) { + Listener x = (Listener) i.next(); + x.goingIdle( this ); + } + } + + /** + * Utility method to notify all listeners + */ + public void notifyWakingUp() { + Vector/**/ handlers = new Vector/**/(); + synchronized ( this ) { + handlers.addAll( listeners ); + } + for ( Iterator/**/ i = handlers.iterator(); + i.hasNext(); ) { + Listener x = (Listener) i.next(); + x.wakingUp( this ); + } + } + + /** + * Adds a listener to the Executor to be notified when the + * Executor goes idle and wakes up from idle + * @param listener + */ + synchronized public void addListener(Listener listener) { + listeners.add( listener ); + } + + /** + * Removes a listener from the executor. + * @param listener + */ + synchronized public void removeListener(Listener listener) { + listeners.remove( listener ); + } + + /** + * Constructor. The given name is also used as thread name for the + * thread that is started. + */ + public Executor(String n) { + name = n; + } + + /** + * Returns the executor's name. + */ + public String toString() { + return name; + } + + /** + * Tells whether it is time to change focus or not. This is used + * by the {@link Goal.Instance#perform} method to possibly stop + * the current computation in order to let the executor shift + * focus to another computation. Whenever the {@link #count} + * arrives at 0, then it it is time to shift focus, and to reset + * the count from {@link #steps}. Though, if the latter is 0 or + * negative, then a change of focus will never be signalled from + * here. + */ + public boolean changeFocus() { + if ( steps <= 0 ) + return false; + if ( count-- > 0 ) + return false; + if ( Goal.isTracing() ) + System.err.println( "CHANGE FOCUS" ); + count = steps; + return true; + } + + /** + * Utility method to "preempt" current intention by forcing {@link + * #count} to zero. This only has effect if {@link #steps} is + * non-zero, i.e., when using intention interleaving. + */ + public void preempt() { + count = 0; + } + + /** + * Utility method to add a {@link Performer} to be managed by this + * executor. If the performer already belongs to an executor, it + * is first removed from that one. + */ + synchronized public void addPerformer(Performer performer) { + if ( performer.executor != null ) { + // The performer already belongs to an executor + if ( performer.executor != this ) + performer.executor.removePerformer( performer ); + } + if ( ! performers.contains( performer ) ) + performers.add( performer ); + performer.executor = this; + } + + /** + * Utility method to remove a {@link Performer} from the + * collection of performers managed by this executor. This does + * not interrupt an ongoing computation, but means that the + * executor will not re-run the performer. + */ + synchronized public void removePerformer(Performer performer) { + if ( performer.executor == this ) { + performers.remove( performer ); + performer.executor = null; + } + } + + /** + * Utility method to localize the first {@link Performer} by its + * name. + */ + synchronized public Performer findPerformer(String name) + { + for ( Iterator/**/ i = + performers.iterator(); i.hasNext(); ) { + Performer performer = (Performer) i.next(); + if ( performer.name.equals( name ) ) + return performer; + } + return null; + } + + /** + * Utility method to "tickle" the executor with, just in case it + * is idle. + */ + synchronized public void signal() { + running = true; + notifyAll(); + } + + /** + * A utility method for obtaining the next performer to re-run, or + * null, in between having told about them all. + */ + synchronized public Performer getNext() { + if ( index < performers.size() ) + return (Performer) performers.get( index++ ); + index = 0; + return null; + } + + /** + * A utility method for going idle when there doesn't seem to be + * anything to do. + */ + public void waitRunning() { + boolean idle = false; + synchronized ( this ) { + if ( ! running && stopping == 0 ) + idle = true; + } + if ( idle ) + notifyGoingIdle(); + synchronized ( this ) { + if ( idle ) { + while ( ! running && stopping == 0 ) { + if ( Goal.isTracing() ) + System.err.println( + "** executor " + this + " idle..." ); + try { + wait(); + } catch (InterruptedException e) { + } + } + } + running = false; + } + if ( idle ) { + if ( Goal.isTracing() ) + System.err.println( "** executor " + this + " active..." ); + notifyWakingUp(); + } + } + + /** + * Utility method to run all {@link Performer Performers} once, + * and then report if they all reported blocking or not. + */ + public Goal.States runPerformersOnce() { + if ( Goal.isTracing() ) + System.err.println( "** runPerformersOnce" ); + boolean stopped = false; + Performer performer = getNext(); + while ( performer != null ) { + if ( performer.runPerformer() != Goal.States.BLOCKED ) + stopped = true; + performer = getNext(); + } + if ( Goal.isTracing() ) + System.err.println( "** runPerformersOnce stopped " + stopped ); + return stopped? Goal.States.STOPPED : Goal.States.BLOCKED ; + } + + /** + * Utility method to keep running {@link Performer Performers} + * while not all are blocking. This method invokes {@link + * #runPerformersOnce} repeatedly until it reports that all + * performers are blocked. + */ + public boolean runPerformersBlocked() { + if ( Goal.isTracing() ) + System.err.println( "** runPerformersBlocked" ); + boolean done_something = false; + while ( runPerformersOnce() != Goal.States.BLOCKED ) { + done_something = true; + if ( stopping > 0 ) + break; + } + if ( Goal.isTracing() ) + System.err.println( + "** runPerformersBlocked done something " + done_something ); + return done_something; + } + + /** + * This is the main execution loop of the executor. It keeps + * running its performers until they are all blocked, in which + * case it waits for a signal to re-run them. + */ + public void run() { + if ( Goal.isTracing() ) + System.err.println( "** starting executor " + this ); + try { + index = 0; + for (;;) { + if (runPerformersBlocked()) { + running = true; + } + if (running) { + if (stopping == 1) { + break; + } + running = false; + } else { + waitRunning(); + if (stopping > 0) { + break; + } + } + } + + } catch (Throwable t) { + t.printStackTrace(); + System.err.println( "** terminating executor " + this ); + } + stopping = 0; + } + + /** + * Tell the {@link #run} method to stop. + */ + public void stop(boolean hard) { + stopping = hard? 1 : 2; + signal(); + } + + /** + * Service interface to request a goal to be performed by a named + * performer, with thread synchronisation on this Executor. + */ + synchronized public boolean performGoal( + String performer,Goal goal,String head,Data d) { + return findPerformer( performer ).performGoal( goal, head, d ); + } + + /** + * Service interface to request a goal to be performed by a named + * performer, with thread synchronisation on this Executor. + */ + synchronized public boolean performGoal( + String performer,Goal goal,String head, + Vector/**/ ins,Vector/**/ outs) { + return findPerformer( performer ).performGoal( goal, head, ins, outs ); + } + + /** + * Service interface to request a goal to be performed by a named + * performer. + */ + public boolean performGoal( + String performer,String goal,String head, + Vector/**/ ins,Vector/**/ outs) { + return findPerformer( performer ).performGoal( goal, head, ins, outs ); + } +} + diff --git a/com/intendico/gorite/FailGoal.java b/com/intendico/gorite/FailGoal.java new file mode 100644 index 0000000..5f368bf --- /dev/null +++ b/com/intendico/gorite/FailGoal.java @@ -0,0 +1,90 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; +import com.intendico.gorite.Goal.States; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * A FailGoal is achieved by means of failing any sub goal, which are + * attempted in sequence. The goal fails when and if all sub goals + * have been attempted and succeeded. In particular, a fail goal + * without sub goals always fail immediately, and is a way to induce + * failure. Failure may also be induced by tasks, which can succeed or + * fail. + * + * @see ConditionGoal + */ +public class FailGoal extends SequenceGoal { + + /** + * Constructor. + */ + public FailGoal(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Convenience constructor without sub goals. + */ + public FailGoal(String n) { + this( n, null ); + } + + /** + * Creates and returns an instance object for achieving + * a FailGoal. + */ + public Instance instantiate(String head,Data d) { + return new FailInstance( head ); + } + + /** + * Represents the process of achieving a FailGoal + */ + public class FailInstance extends SequenceInstance { + + /** + * Constructor. + */ + public FailInstance(String h) { + super( h ); + } + + /** + * Process all sub goals in sequence, then reverse {@link + * States#PASSED} and {@link States#FAILED}. + */ + public States action(String head,Data d) + throws LoopEndException, ParallelEndException { + States s = super.action( head, d ); + if ( s == States.PASSED ) + return States.FAILED; + if ( s == States.FAILED ) + return States.PASSED; + return s; + } + } +} diff --git a/com/intendico/gorite/Goal.java b/com/intendico/gorite/Goal.java new file mode 100644 index 0000000..eaae20c --- /dev/null +++ b/com/intendico/gorite/Goal.java @@ -0,0 +1,876 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * The Goal class is base class for a goal oriented process modelling + * through a hierarchy that reflects the decomposition of a goal as an + * abstraction of sub goals. A process model is a hierarchy with a + * top level goal that is sucessively decomposed as particular + * combinations of sub goals. The top level goal is achieved by means + * of processing its sub goals according to the goal type. + * + *

A goal is processed by instantiating it via its {@link + * #instantiate} method, which creates the {@link Instance} object + * that represents an actual intention to achieve the goal. The + * intention is then pursued via the {@link Instance#perform} method, + * which eventually results in invoking one of the particular {@link + * Instance#action} methods that implement the particular execution + * semantics. + * + *

The base class is also used for task goals, where the {@link + * #execute} method is overridden to perform the intended task. In + * many cases, the task execution is a matter of processing one or + * more input data elements to proved one or more result data + * elements, as illustrated by the following example: + *

+ * public States execute(Data data) {
+ *     data.setValue( "result", process( data.getValue( "input" ) ) );
+ *     return PASSED;
+ * }
+ * 
+ * + * Of course, the data element names need to be designed and defined + * in the full application context, since all goal executions of the + * same intention use the same {@link Data} object. Typically you + * would employ some data naming scheme to ensure appropriate name + * separation. + * + *

Note that an Goal may be augmented with a reimplemented {@link + * Goal#instantiate(String,Data)} method as way of defining data + * values, a reimplemented {@link Goal#execute(Data,Goal.Instance)} + * method as way of defining post processing, and a reimplemented + * {@link Goal#cancelled(Goal.Instance)} method as way of restoring + * state if the goal execution is cancelled. + * + * In particular, if the implementation requires a dynamic lexical + * environment for each execution, then the {@link + * Goal#instantiate(String,Data)} needs to create a vacuous Goal + * extension with that environment and ultimately containing the + * associated {@link Goal#execute(Data,Goal.Instance)} method. The + * following is an illustration example of this. + *

+ * public Goal.Instance instantiate(String head,Data data) {
+ *     return new Goal( "achieving " + getGoalName() ) {
+ *         int x;
+ *         public States execute(Data data) {
+ *             // Sees a dynamically instantiated x
+ *             while ( x++ < 10 ) {
+ *                 return BLOCKED;
+ *             }
+ *             return PASSED;
+ *         }
+ *     }.instantiate( head, goal );
+ * }
+ * 
+ * + * Using that approach, a vacuous Goal object, with the "int x" + * member, is created for each instantation of the surrounding goal, + * and then that goal is instantiated for providing the actual + * Instance object. + */ +public class Goal { + + /** + * Interface for intrusive tracing of goal execution. + * @see #tracer + */ + public interface Tracer { + + /** + * Invoked before performing an instance for given data. This + * method is invoked by the gorite executor thread prior to + * entering or re-entering the goal instance execution. A + * Tracer implementation may, by hogging the thread, hold back + * the execution while inspecting the model state. The Data is + * the instance data to be; this data object becomes the + * {@link Instance#data} upon the first entry (after the call + * to this method). + */ + public void enterPerform(Instance instance,Data d); + + /** + * Invoked after performing instance returns. This method is + * invoked when the execution thread stops performing the + * instance, which is either because it is finished, or + * because it is blocked or stopped. The given {@link States} + * is the execution return value, which in many cases is also + * cached in {@link Instance#state}. + */ + public void exitPerform(Instance instance,States s); + + /** + * Invoked after performing instance throws exception. This + * method is invoked when the execution thread stops + * performing the instance due to it throwing an exception or + * ({@link Throwable}). If it happens to be a {@link + * LoopEndException} or {@link ParallelEndException}, then the + * exception is passed in on the call, otherwise null is + * passed in. Note that this invocation happens in the + * "finally block" for the pending exception propagation, + * which thus gets propagated when the method returns. + */ + public void exceptionPerform(Instance instance,Exception e); + + } + + /** + * The global intrusive goal execution tracer, if any. This may be + * set via the command line property -Dgorite.tracer=XX + * where XX is the class name for the Tracer + * implementation to instantiate. It may also be set in code. + */ + public static Tracer tracer = null; + + /** + * These are the return values of the {@link #execute(Data)} + * method, to report on the state of a goal instance execution. + */ + public /*enum*/ static class States { + + /** + * A "name" of a States object. + */ + protected String name; + + /** + * The PASSED state is returned to indicate that the goal + * execution has completed successfully. + */ + public final static States PASSED = + new States() {{ name = "PASSED"; }}; + + /** + * The FAILED state is returned to indicate that the goal + * execution has completed without success. + */ + public final static States FAILED = + new States() {{ name = "FAILED"; }}; + + /** + * The CANCEL state is not returned by goal instance + * execution, but is used to mark an {@link Instance} when the + * execution has been cancelled. + */ + public final static States CANCEL = + new States() {{ name = "CANCEL"; }}; + + /** + * The BLOCKED state is returned to indicate that the goal + * execution has not completed, and that it cannot progress + * until a blocking condition has been removed. This return + * code is further a promise that something will notify the + * execution system via {@link Data#setFlag} call when the + * blocking condition possibly has changed. Having returned + * this code, the goal instance execution may be re-entered + * whenever the execution system so wishes, regardless of the + * blocking condition, and the execution method needs to + * confirm the condition or return BLOCKED again. + * @see Data#setTrigger(Observable) + * @see Data#setTrigger(Query) + */ + public final static States BLOCKED = + new States() {{ name = "BLOCKED"; }}; + + /** + * The STOPPED state is returned to indicate that the goal + * execution has not completed, but is interrupted for some + * reason other than a blocking condition. The goal instance + * execution is thus expecting re-entry as soon as possible. + */ + public final static States STOPPED = + new States() {{ name = "STOPPED"; }}; + + /** + * Returns the name. + */ + public String toString() { + return name; + } + } + + /** + * The property name for tracing goal execution. + */ + public static final String TRACE = "gorite.goal.trace"; + + /** + * The name of the data element that identifies the executing + * performer. + */ + public static final String PERFORMER = "this performer"; + + /** + * The name of this goal. There are no restrictions on which names + * goals have, but it is the name that identifies the goal when + * resolving a {@link BDIGoal} or a {@link TeamGoal} into some + * plan to achieve it. Conceptually it is the name that *is* the + * goal, wheras a Goal object rather is a node in a goal hierarchy + * to represent the idea of achieving the named goal. + * + *

An actual attempt to achieve the named goal is also + * separate both from the goal (name) itself, and from the Goal + * object (the idea of achieving it). Such an attempt is + * understood as an "intention", which in GORITE gets represented + * by a {@link Goal.Instance}. + */ + private String name = null; + + /** + * The control data, if any. This is only used by certain kinds of + * goals, to carry some certain definition detail separate from + * the goal type. + * + *

It is for instance used by {@link ParallelGoal} and {@link + * RepeatGoal} goals to name the data element that identifies each + * branch. + * @see Data#fork(String,int,String) + * @see Data#join(String,HashSet,String) + */ + private String control = null; + + /** + * The execution group to be used for pursuing the goal. If set, + * it is the name of the {@link Performer.TodoGroup} to use for + * goal instance execution. Goals of the same group are then + * managed by the execution management associated with the {@link + * Performer.TodoGroup}. + */ + private String group = null; + + /** + * The sub goals of this goal. + */ + private Goal [] subgoals; + + /** + * Where is this created? + */ + final public StackTraceElement created; + + /** + * Constructor. + */ + public Goal(String n,Goal [] sg) { + name = n; + subgoals = sg; + StackTraceElement [] where = new Error().getStackTrace(); + for ( int i = 0; i < where.length; i++ ) { + String c = where[i].getClassName(); + if ( ! c.startsWith( "com.intendico.gorite." ) ) { + created = where[i]; + return; + } + } + created = null; + } + + /** + * Convenience constructor without sub goals. The sub goals may be + * set explicitly as value to the {@link #subgoals} member. + */ + public Goal(String n) { + this( n, null ); + } + + /** + * This flag captures whether or not the {@link #TRACE} property + * set (to any value) upon class initialisation, and it may be set + * or reset subsequently for controlling the amount of goal + * execution logging to generate. + * + *

Ordinarily one will turn on standard goal execution tracing + * by using the command line argument: + *

+     * -Dgorite.goal.trace=yes
+     * 
+ */ + public static boolean tracing = System.getProperty( TRACE, null ) != null; + + // Set up a tracer if declared + static { + String c = System.getProperty( "gorite.tracer", null ); + if ( c != null ) { + try { + tracer = (Tracer) Class.forName( c ).newInstance(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + /** + * This is a utility method to discover whether tracing has been + * requested by defining the {@link #TRACE} property, or not. + */ + public static boolean isTracing() { + return tracing; + } + + // + // Execution support + // + + /** + * This is the primary execution method for a goal. It is invoked + * from the {@link Instance} object for a goal so as to achieve + * the goal, and it is told about both the calling goal {@link + * Instance} and {@link Data}. This base implementation invokes + * {@link Instance#action(String,Data)} for actual progress of the + * intention. + * + *

This method may be overridden by a user method when + * defining a task goal that needs access to the {@link Instance} + * object. See {@link #execute(Data)} for details. + */ + public States execute(Data d,Instance i) + throws LoopEndException, ParallelEndException { + return i.action( i.head, d ); + } + + /** + * This is the simplified execution method for task goal. It is + * invoked from the {@link Instance} object for a task goal so as + * to achieve the goal by means of Java code, and it is told only + * about the intention {@link Data}. This base implementation + * merely returns {@link States#PASSED}, and it should be + * overridden by a user method when defining an actual task goal. + * + *

The method returns either {@link States#PASSED} or {@link + * States#FAILED} to indicate that the execution has terminated + * and the goal is achieved or not, or it returns {@link + * States#STOPPED} or {@link States#BLOCKED} to indicate that the + * attempt of achieving the goal is still going on, but execution + * control is relinquished temporarily (so as to allow other + * intentions to progress). By returning {@link States#BLOCKED}, + * it also indicates that this code need not be invoked unless + * some other intention has progressed, but the {@link Executor} + * may choose to go idle. This is typically combined with the + * setting of an execution trigger, which is done via {@link + * Data#setTrigger(Query)} or {@link Data#setTrigger(Observable)}, + * to ensure that the {@link Executor} wakes up when something + * interesting has happened. + */ + public States execute(Data d) + throws InterruptedException, LoopEndException, ParallelEndException { + return States.PASSED; + } + + /** + * Utility method for instantiating a goal according to its + * type. Each goal type has its own instance class extension, + * which implement the ways in which a goal is achieved through + * its sub goals. + */ + public Instance instantiate(String head,Data d) { + return new Instance( head ); + } + + /** + * Control callback whereby the goal gets notified that an (its) + * intention is cancelled. + */ + public void cancelled(Instance which) { + } + + /** + * Base class for goal instance execution procedures. + */ + public class Instance { + + /** + * The trace name of the execution point. This defines where + * in the abstract goal execution tree this instance appears. + * The head is a kind of cursor into the abstract tree, set up + * as a sequence of numbers that tell which branch to follow + * so as to eventually arrive at this node. Branches are + * numbered from 0 and up, i.e. branch #3 is the fourth + * branch; for some goal types the branch number is also an + * index into the {@link #subgoals} array for the goal the + * execution is an instantiation of. + * The numbers are separated by characters that mark which + * kind of expansion the abstract tree has at the point. + *

    + *
  • ".n" marks insantiation of subgoal n + *
  • ":n" marks repetition n or branch n, for {@link + * LoopGoal}, {@link ParallelGoal}, {@link RepeatGoal} and + * {@link TeamGoal}. + *
  • "*n" marks attempt n for a {@link BDIGoal} + *
+ */ + final public String head; + + /** + * A more verbose intention name, which consists of the head + * and the goal name. This is used as key for the {@link + * Data.Bindings} of this intention. + */ + final public String thread_name; + + /** + * The Data object for this instance. + */ + public Data data = null; + + /** + * The monitoring state. This is the state an intention + * monitor should return; it starts off as BLOCKED, and turns + * into PASSED, FAILED or STOPPED (upon exception). + */ + public States state = States.BLOCKED; + + /** + * Returns the goal of this instance. + */ + public Goal getGoal() { + return Goal.this; + } + + /** + * Returns the plan argument object, if any, for this + * instance. + */ + public Object getArgs() { + return data.getArgs( head ); + } + + /** + * Determine the plan-local variable name for the given short + * name. + */ + public String local(String name) { + int i = head.lastIndexOf( "*" ); + if ( i < 0 ) + i = head.length(); + return head.substring( 0, i ) + "-" + name; + } + + /** + * Returns the value of a data element as seen by this + * intention. + */ + public Object getValue(String name) { + if ( data == null ) + return null; + return data.getValue( name, thread_name ); + } + + /** + * Sets a data value onto the innermost lookup path as seen by + * this intention. If an element of the name is found, then + * that element gains a new value, otherwise a new element is + * created for the most local binding of this intention. + */ + public void setValue(String name,Object value) { + data.setValue( thread_name, name, value ); + } + + /** + * Utility method for updating the monitoring state, which + * also notifies any waiting thread. + */ + synchronized public void setMonitoring(States s) { + if ( isTracing() ) + trace( "-- noting", " (" + s + ")" ); + state = s; + if ( state == States.BLOCKED ) + state = s; + else + data.setFlag(); + } + + /** + * Utility method to cancel the execution of this instance. + * This method is typically overridden by extension classes in + * order to propagate cancellation. + */ + public void cancel() { + if ( isTracing() ) + System.err.println( "** cancel " + head + " " + this ); + if ( state == States.BLOCKED || state == States.STOPPED ) + state = States.CANCEL; + // Notify Goal about cancelling this intention. + cancelled( this ); + if ( data != null ) + data.dropBindings( thread_name ); + } + + /** + * Utility thread control method to wait until this instance + * is done. + */ + synchronized public States waitDone() + throws LoopEndException, ParallelEndException { + if ( state == States.BLOCKED ) { + trace( "-- waiting on ", "" ); + data.waitFlag(); + } + throwSavedException(); + return state; + } + + /** + * Utility funtion that throws the particular exception, if + * any, that has been saved for this instance, or just + * returns. + */ + public void throwSavedException() + throws LoopEndException, ParallelEndException { + if ( exception instanceof LoopEndException ) + throw (LoopEndException) exception; + if ( exception instanceof ParallelEndException ) + throw (ParallelEndException) exception; + } + + /** + * Keeps any LoopEndException or ParallelEndException thrown + * by a sub goal. + */ + public Exception exception = null; + + /** + * The instance performer. + */ + public Performer performer = null; + + /** + * Constructor, taking the execution point name as argument. + */ + public Instance(String h) { + head = h.replace( '\"', ':' ); + thread_name = head + " " + nameString( name ); + } + + /** + * Utility method to issue a standard trace message. + */ + public void trace(String prefix,String suffix) { + if ( isTracing() ) { + System.err.println( + prefix + " " + + ( performer == null? "" : ( performer.name + ":" ) ) + + thread_name + suffix ); + } + } + + /** + * This is the primary entry point for executing this goal + * instance. It is invoked repeatedly by the calling goal + * instance for the purpose of progressing this goal instance + * via its {@link Goal#execute(Data,Instance)} method. On the + * first invokation, which is detected by virtue of {@link + * #data} being unset, this method registers the instance to + * the intention data and sets up the {@link #PERFORMER} data + * element. + * + *

This method also invokes {@link + * Performer#propagateBeliefs()} before progressing the + * intention. + * + *

If the enclosing goal's {@link Goal#group} attribute is + * non-null, then it is understood to name a {@link + * Performer.TodoGroup} for coordinating instances of this + * goal. Therefore, instead of actually progressing the + * intention, this instance gets added to the named {@link + * Performer.TodoGroup} (by {@link Performer#execute}), and + * subsequently all progress will occur "in parallel" via + * calls to the {@link #action()} method, while this method + * henceforth results in merely polling the instance state + * (which happens indirectly via the subsequent calls to + * {@link Performer#execute}). + */ + public States perform(Data d) + throws LoopEndException, ParallelEndException { + Tracer t = tracer; + if ( t == null ) { + try { + return performIt( d ); + } catch(LoopEndException x) { + throw x; + } catch(ParallelEndException x) { + throw x; + } catch (Throwable x) { + throw new GoriteError( this, x ); + } + } + t.enterPerform( this, d ); + States b = States.STOPPED; + boolean returns = false; + Exception e = null; + try { + b = performIt( d ); + returns = true; + return b; + } catch (LoopEndException x) { + e = x; + throw x; + } catch (ParallelEndException x) { + e = x; + throw x; + } catch (Throwable x) { + throw new GoriteError( this, x ); + } finally { + if ( returns ) { + t.exitPerform( this, b ); + } else { + t.exceptionPerform( this, e ); + } + } + } + + /** + * The working method for performing this Instance. It used to + * be {@link #perform}, but nowadays that method only deals + * with the {@link #tracer}, while invoking this method for + * the actual work. + * @see #perform + */ + private States performIt(Data d) + throws LoopEndException, ParallelEndException { + try { + if ( state == States.CANCEL ) { + trace( "--", " (cancel)" ); + cancel(); + return States.CANCEL; + } + + boolean reentry = true; + if ( data == null ) { + trace( "=>", " (group=" + group + ")" ); + data = d; + data.link( thread_name ); + reentry = false; + performer = (Performer) data.getValue( PERFORMER ); + if ( performer == null ) { + System.err.println( + "** Missing performer for goal \"" + + name + "\" at " + head ); + System.err.println( "---------------------\n" + data ); + //System.exit( 1 ); + } + } else { + if ( state == States.BLOCKED && + ! data.isRunning( thread_name ) ) + return States.BLOCKED; + trace( "=>", " (resume)" ); + } + + if ( ! reentry && performer.changeFocus() ) { + if ( group == null ) + return States.STOPPED ; + } + + performer.propagateBeliefs(); + + States b; + if ( group == null ) { + + b = execute( data, this ); + if ( b == States.PASSED || b == States.FAILED ) { + data.dropBindings( thread_name ); + } + } else { + b = performer.execute( reentry, this, group ); + } + setMonitoring( b ); + + if ( isTracing() ) { + trace( "<=", " (" + b + ")" ); + } + + return b; + } catch (LoopEndException e) { + if ( data != null ) + data.dropBindings( thread_name ); + throw e; + } catch (ParallelEndException e) { + if ( data != null ) + data.dropBindings( thread_name ); + throw e; + } + } + + /** + * Utility method that runs this instance's + * action(String,Data). This is used for resuming the instance + * from a {@link Performer.TodoGroup}. + */ + public States action() { + if ( state == States.CANCEL ) { + trace( "--*", " (cancel)" ); + cancel(); + return States.CANCEL; + } + state = States.BLOCKED; + try { + data.setThreadName( thread_name ); + if ( performer == null ) { + throw new Error( + "** Missing performer for goal \"" + + name + "\" at " + head ); + } + data.replaceValue( Goal.PERFORMER, performer ); + trace( "=>*", " (resume)" ); + performer.propagateBeliefs(); + return execute( data, this ); + } catch (LoopEndException e) { + exception = e; + return States.STOPPED; + } catch (ParallelEndException e) { + exception = e; + return States.STOPPED; + } + } + + /** + * Utility method to access a data element relative to this + * thread. + */ + public Data.Element getDataElement(String name) { + return data.getLocalElement( thread_name, name ); + } + + /** + * Implements how a goal is achieved by means of its sub + * goals. This base implementation invokes the goal's {@link + * #execute(Data)}. + */ + public States action(String head,Data d) + throws LoopEndException, ParallelEndException { + try { + return execute( d ); + } catch (InterruptedException e) { + e.printStackTrace(); + return States.STOPPED; + } + } + } + + /** + * Utility method to quote a string. + */ + public static String nameString(String n) + { + if ( n == null ) + return "null"; + return "\"" + n + "\""; + } + + /** + * Returns a {@link java.lang.String} identifying the type of the + * goal. + */ + public String getType() + { + String s = getClass().getName(); + int i = s.lastIndexOf( "." ); + return s.substring( i + 1 ); + } + + /** + * Makes a deep-structure string representation of this goal and + * all its sub goals, using a given depth prefix. It does not + * check for cyclic goal structure. + */ + public String toString(String counter) { + StringBuffer s = new StringBuffer(); + String x = ""; + if ( counter != null ) { + s.append( counter ); + s.append( " " ); + x = counter + "."; + } + s.append( nameString( name ) ); + s.append( " (" ); + s.append( getType() ); + s.append( ")" ); + if ( subgoals != null ) { + for ( int i = 0; i < subgoals.length; i++ ) { + s.append( "\n" ); + s.append( subgoals[ i ].toString( x + (i+1) ) ); + } + //s.append( " // end " + counter + "\n" ); + } + return s.toString(); + } + + /** + * Makes a deep-structure string representation of this goal and + * all its sub goals. + */ + public String toString() { + return toString( null ); + } + + /** + * Returns the goal {@link #name} attribute. + */ + public String getGoalName() { + return name; + } + + /** + * Returns the goal {@link #control} attribute. + */ + public String getGoalControl() { + return control; + } + + /** + * Returns the {@link #subgoals} attribute. + */ + public Goal [] getGoalSubgoals() { + return subgoals; + } + + /** + * Returns the {@link #group} attribute. + */ + public String getGoalGroup() { + return group; + } + + /** + * Assigns the {@link #control} attribute. + */ + public void setGoalControl(String v) { + control = v; + } + + /** + * Assigns the {@link #subgoals} attribute. + */ + public void setGoalSubgoals(Goal [] s) { + subgoals = s; + } + + /** + * Assigns the {@link #group} attribute. + */ + public void setGoalGroup(String v) { + group = v; + } +} diff --git a/com/intendico/gorite/GoriteError.java b/com/intendico/gorite/GoriteError.java new file mode 100644 index 0000000..aa42fa5 --- /dev/null +++ b/com/intendico/gorite/GoriteError.java @@ -0,0 +1,35 @@ +package com.intendico.gorite; + +/** + * This is a specialozed {@link Error} that used for wrapping + * exceptions in a gorite intention chain, so as to give better + * guidance about where in the source execution the error occurs. + */ +public class GoriteError extends Error { + + /** + * Constructor. + */ + public GoriteError(Goal.Instance instance,Throwable cause) { + super( "Uncaught Throwable" ); + if ( cause instanceof GoriteError ) { + cause = cause.getCause(); + } + + // Reduce the stack trace for the cause in a good way... + StackTraceElement [] stack = getStackTrace(); + StackTraceElement [] inner = cause.getStackTrace(); + + int at = inner.length - stack.length; + StackTraceElement created = instance.getGoal().created; + inner[ at ] = new StackTraceElement( + created.getClassName(), + created.getMethodName() + + "|" + instance.getGoal().getGoalName() + "|" + instance.head, + created.getFileName(), + created.getLineNumber() ); + cause.setStackTrace( inner ); + initCause( cause ); + } + +} diff --git a/com/intendico/gorite/LoopEndException.java b/com/intendico/gorite/LoopEndException.java new file mode 100644 index 0000000..6a8a021 --- /dev/null +++ b/com/intendico/gorite/LoopEndException.java @@ -0,0 +1,39 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +/** + * This exception is thrown by an {@link EndGoal} goal, to be caught + * by an enclosing {@link LoopGoal} goal, which then terminates + * successfully. + */ +public class LoopEndException extends Exception { + /** + * Version identity required for serialization. + */ + public static final long serialVersionUID = 1L; + + /** + * Cosntructor. + */ + public LoopEndException(String s) { + super( s ); + } +} diff --git a/com/intendico/gorite/LoopGoal.java b/com/intendico/gorite/LoopGoal.java new file mode 100644 index 0000000..6e7e7fa --- /dev/null +++ b/com/intendico/gorite/LoopGoal.java @@ -0,0 +1,111 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * A LoopGoal is achieved by means of repeatedly achieving its sub + * goals in sequence, until eventually an embedded {@link EndGoal} is + * achieved. The usage pattern for this is as follows: + * + *

+ * new LoopGoal( "loop", new Goal [] {
+ *     .. // steps in the loop
+ *     .. new EndGoal( "break if", new Goal [] {
+ *            // optional sub goals for ending the loop if they succeed
+ *        } )
+ *     .. // more steps in the loop
+ * } )
+ * 
+ * + * There may be any number of {@link EndGoal} to end the loop although + * of course only one will take effect. The {@link EndGoal} may be at + * any level within the LoopGoal goal hierarchy, except nested within + * another LoopGoal. + * + * @see EndGoal + * @see SequenceGoal + */ +public class LoopGoal extends SequenceGoal { + + /** + * Constructor. + */ + public LoopGoal(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Convenience constructor without sub goals. + */ + public LoopGoal(String n) { + this( n, null ); + } + + /** + * Creates and returns an instance object for achieving + * a LoopGoal. + */ + public Instance instantiate(String head,Data d) { + return new LoopInstance( head ); + } + + /** + * Implements repeated, sequential exception. + */ + public class LoopInstance extends SequenceInstance { + + /** + * Constructor. + */ + public LoopInstance(String h) { + super( h ); + } + + public int count = 0; + + /** + * Repeatedly process all subgoals in sequence, until a + * thrown LoopEndException is caught. + */ + public States action(String head,Data d) + throws LoopEndException, ParallelEndException { + for ( ;; count++ ) { + States s; + try { + s = super.action( head + ":" + count, d ); + } catch (LoopEndException e) { + // End this loop + break; + } + if ( s != States.PASSED ) + return s; + index = 0; + } + return States.PASSED; + } + } +} diff --git a/com/intendico/gorite/ParallelEndException.java b/com/intendico/gorite/ParallelEndException.java new file mode 100644 index 0000000..74bd87a --- /dev/null +++ b/com/intendico/gorite/ParallelEndException.java @@ -0,0 +1,44 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +/** + * This exception is thrown by an {@link ControlGoal} goal, to be + * caught by an enclosing parallel execution, which then cancels all + * parallel branches and terminates successfully. The parallel + * executions concerned include both {@link ParallelGoal} and {@link + * RepeatGoal} goals, as well as {@link TeamGoal} goals directed to + * a multi-filled {@link Team.Role}. + */ +public class ParallelEndException extends Exception { + + /** + * Version identity required for serialization. + */ + public static final long serialVersionUID = 1L; + + /** + * Constructor. + * @param s + */ + public ParallelEndException(String s) { + super( s ); + } +} diff --git a/com/intendico/gorite/ParallelGoal.java b/com/intendico/gorite/ParallelGoal.java new file mode 100644 index 0000000..038738e --- /dev/null +++ b/com/intendico/gorite/ParallelGoal.java @@ -0,0 +1,113 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * A ParallelGoal is achieved by means of achieving all its sub goals + * in parallel. If any sub goal execution fails, then all other sub + * goal executions are interrupted by means of throwing an {@link + * java.lang.InterruptedException InterruptedException}, and + * thereafter the parallel goal execution fails (when all the sub goal + * executions have terminated). + * + *

A ParallelGoal execution can also be interrupted to end in + * success, by means of throwing a {@link ParallelEndException} within + * one of its branches. In this case all other sub goal executions are + * interrupted by means of throwing an {@link + * java.lang.InterruptedException InterruptedException}, and + * thereafter the parallel goal execution succeeds (when all the sub + * goal executions have terminated). + * + *

See also {@link ControlGoal}, which throws a {@link + * ParallelEndException} if its sub goals succeed. The following is a + * code snippet to illustrate the use of an embedded {@link + * ControlGoal} for an optional premature interupt of an indefinite + * intention. + * + *

+ * new ParallelGoal( ..., new Goal [] {
+ *     new BDIGoal( "do something indefinitely" ),
+ *     new FailGoal( "not interrupting is fine", new Goal [] {
+ *         new ControlGoal( "interrupt if desired", new Goal [] {
+ *             new BDIGoal( "decide if and when to interrupt" )
+ *         }
+ *     }
+ * } )
+ * 
+ */ +public class ParallelGoal extends BranchingGoal { + + /** + * Constructor. + */ + public ParallelGoal(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Convenience constructor without sub goals. + */ + public ParallelGoal(String n) { + this( n, null ); + } + + /** + * Creates and returns an instance object for achieving + * a ParallelGoal. + */ + public Instance instantiate(String head,Data d) { + return new ParallelInstance( head ); + } + + /** + * Implements parallel execution of sub goals. + */ + public class ParallelInstance extends MultiInstance { + + /** + * Constructor. + */ + public ParallelInstance(String h) { + super( h ); + } + + /** + * There is one branch for each sub goal. + */ + public boolean more(int i,Data d) { + return getGoalSubgoals() != null && i < getGoalSubgoals().length; + } + + /** + * The sub goal branch is obtained by instantiating the sub + * goal. + */ + public Instance getBranch(int i,String head,Data d) { + return getGoalSubgoals()[ i ].instantiate( head, d ); + } + } +} diff --git a/com/intendico/gorite/Performer.java b/com/intendico/gorite/Performer.java new file mode 100644 index 0000000..95dceec --- /dev/null +++ b/com/intendico/gorite/Performer.java @@ -0,0 +1,834 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import java.util.Vector; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Observable; +import java.util.Observer; +import com.intendico.data.Ref; +import com.intendico.data.RuleSet; +import com.intendico.data.Rule; +import com.intendico.data.Query; +import com.intendico.gorite.Goal.Instance; +import com.intendico.gorite.addon.TodoGroupSkipBlocked; +import com.intendico.gorite.addon.TodoGroupRoundRobin; +import com.intendico.gorite.addon.TodoGroupParallel; + +/** + * The Performer class is base class for {@link Team} member + * implementations. Conceptually a performer is considered to be the + * agent for {@link Goal} executions, and it contains the belief + * structures, if any, that agent reasoning may refer to. + * + *

A Performer is a {@link Capability} in that it has {@link Goal} + * hierarchies, and inner capabilities that define its reasoning + * processes. + * + *

The goals of performer may be grouped to belong to named "to-do + * groups", which are represented by the inner class {@link + * Performer.TodoGroup}. The goals of a TodoGroup are managed such + * that only one at a time is performed, and the group is associated + * with meta-level reasoning for deciding which goal to perform next. + */ +public class Performer extends Capability { + + /** + * The {@link Data.Element} name of the Todogroup object concerned + * when performing TodoGroups meta-level reasoning. + */ + public static final String TODOGROUP = "todogroup"; + + /** + * Holds the Performer's name. + */ + public String name; + + /** + * Default constructor. When this is used, the {@link #name} field + * should be assigned before the performer is made to fill a role. + */ + public Performer() { + this( "nobody" ); + } + + /** + * Constructor with {@link #name} given. + */ + public Performer(String n) { + name = n; + performer = this; + } + + /** + * Returns a text representation of this performer. + */ + public String toString() { + return getClass().getName() + ": " + name; + } + + /** + * Overrides {@link Capability#addCapability} so as to link up + * added capabilities with this performer. + */ + public void addCapability(Capability c) { + c.setPerformer( this ); + super.addCapability( c ); + } + + // + // The performer's rule set, if any. + // + + /** + * The Performer's rule set. + */ + public RuleSet rule_set = new RuleSet() {{ + addObserver( new Observer() { + public void update(Observable x,Object y) { + signalExecutor(); + } + } ); + }}; + + /** + * Utility method to apply all rules exhaustively. + */ + public void propagateBeliefs() { + rule_set.run(); + } + + // + // TodoGroup based execution support + // + + /** + * A constant goal name for a generic TodoGroup meta goal. + * Presently "todogroup meta goal". + */ + public final static String TODOGROUP_META_GOAL = + TODOGROUP + " meta goal"; + + /** + * This performer's {link TodoGroup} objects. + */ + public Hashtable/**/ todogroups = + new Hashtable/**/(); + + /** + * Utility method to find a named {@link TodoGroup}. This method + * will create a new {@link TodoGroup} rather than return + * null, and the new {@link TodoGroup} will have its + * {@link TodoGroup#meta_goal} string set to null by + * default (meaning no meta goal), unless the system property + * gorite.todo.classic is set, in which case {@link + * #TODOGROUP_META_GOAL} is used as meta goal name. + * @param rn the {@link TodoGroup} name + * @return the {@link TodoGroup} object of that name among this + * performer's {@link #todogroups}. + */ + public TodoGroup getTodoGroup(String rn) { + if ( rn == null ) + return null; + TodoGroup r = (TodoGroup) todogroups.get( rn ); + if ( r == null ) { + if ( Goal.isTracing() ) { + System.err.println( "-- " + name + " adds TodoGroup " + rn ); + } + String mg = null; + if ( System.getProperty( "gorite.todo.classic" ) != null ) + mg = TODOGROUP_META_GOAL; + r = new TodoGroup( rn, mg ); + addTodoGroup( r ); + } + return r; + } + + /** + * Utility method to add a TodoGroup with given name and meta goal. + */ + public void addTodoGroup(String n,String mg) { + addTodoGroup( new TodoGroup( n, mg ) ); + } + + /** + * Utility method to add a given TodoGroup. + */ + public void addTodoGroup(TodoGroup r) { + if ( todogroups == null ) + todogroups = new Hashtable/**/(); + todogroups.put( r.name, r ); + } + + /** + * Control flag for TodoGroup execution. + */ + private long todo_added = 0; + + /** + * Utility method to add an instance to its todogroup. + */ + public Goal.States execute( + boolean reentry,Goal.Instance instance,String group) + throws LoopEndException, ParallelEndException + { + if ( Goal.isTracing() ) + System.err.println( "-- execute: " + group + " " + reentry ); + //todo_added += 1; + return getTodoGroup( group ).execute( reentry, instance ); + } + + /** + * Index of TodoGroup to execute next. + */ + private int todo_index = 0; + + /** + * Control method to run all todogroups until stopped or blocked. + */ + synchronized public Goal.States runPerformer() { + if ( Goal.isTracing() ) + System.err.println( + "-- " + name + " running " + + ( todogroups == null? 0 : todogroups.size() ) ); + propagateBeliefs(); + if ( todogroups == null || todogroups.size() == 0 ) + return Goal.States.BLOCKED; + boolean running = true; + Goal.States s = Goal.States.BLOCKED; + while ( running ) { + long todo_added_cache = todo_added; + TodoGroup [] groups = (TodoGroup []) todogroups.values().toArray( + new TodoGroup [ todogroups.size() ] ); + running = false; + propagateBeliefs(); + for ( int i = 0; i < groups.length; i++ ) { + if ( todo_index >= groups.length ) + todo_index = 0; + s = groups[ todo_index++ ].runGroup(); + if ( Goal.isTracing() ) + System.err.println( "-- runGroup = " + s ); + propagateBeliefs(); + running = todo_added != todo_added_cache; + if ( s == Goal.States.BLOCKED ) { + continue; + } + if ( s == Goal.States.STOPPED ) + break; + running = true; + s = Goal.States.BLOCKED; + } + } + if ( Goal.isTracing() ) + System.err.println( "-- " + name + " (" + s + ")" ); + return s; + } + + /** + * The TodoGroup class provides a meta-level goal execution + * facility, allowing multiple parallel executions be + * synchronised. + * + *

A Goal that has its {@link Goal#group} attribute set will + * be executed by adding its execution instance to the nominated + * TodoGroup. The instance execution will be progressed as a + * TodoGroup execution task, when the execution thread gets around + * to it. + * + *

The TodoGroup is associated with a meta goal {@link + * #meta_goal}, which is performed by the execution machinery when + * the TodoGroup state changes: when a goal instance is added, + * when its execution gets blocked, and when it completes. The + * meta goal execution may inspect the TodoGroup and promote any + * of the pending goal instance executions to be the one to + * progress next. + */ + public class TodoGroup implements Observer { + + /** + * A name attribute for the group. There is no particular + * significance in this name except that it uniquely + * identifies a TodoGroup object for a {@link Performer}. + */ + public String name = null; + + /** + * This names the {@link BDIGoal} to be invoked when {@link + * Goal.Instance} objects are added to, or removed from the + * TodoGroup, as well as when the intention execution returns + * blocked. The meta goal is invoked for the purpose of + * deciding which intention to pursue next. + * + *

The default name is {@link + * Performer#TODOGROUP_META_GOAL}. + * + *

When the meta goal is invoked, it is provided with the + * following data elements: + *

+ *
named by {@link Goal#PERFORMER}
this {@link + * Performer} object
+ *
named by {@link #TODOGROUP}
this TodoGroup + * object
+ *
"added"
{@link Vector} of the {@link + * Goal.Instance} objects just added to the group, if any.
+ *
"removed"
{@link Vector} of the {@link + * Goal.Instance} objects just removed from the group, if any.
+ *
"top"
the current top-of-stack {@link + * Goal.Instance} object. When an instance terminates, it is + * added to the "removed" set, then removed from the stack + * before the meta goal gets invoked.
+ *
+ * + *

Note that the "added" and "removed" collections have + * been processed, and their contents have been added and + * removed respectively before the meta goal is performed. + * + *

A meta goal plan typically uses {@link #promote} to + * change the {@link #stack}. Still, no other code is supposed + * to operate on the stack concurrently, so the meta goal plan + * may change the stack in more complex ways. + * + * @see TodoGroupSkipBlocked + * @see TodoGroupRoundRobin + * @see TodoGroupParallel + */ + public String meta_goal = null; + + /** + * Constructor given name and meta goal name. If the meta goal + * name is null, then the default meta goal name ({@link + * Performer#TODOGROUP_META_GOAL}) is used. + */ + public TodoGroup(String n,String mg) { + name = n; + meta_goal = mg; + } + + /** + * This is the stack of executions in this TodoGroup. + */ + public Vector/**/ stack = + new Vector/**/(); + + public Vector/**/ added = + new Vector/**/(); + + public Vector/**/ removed = + new Vector/**/(); + + /** + * Keeping track of an ongoing meta goal execution. This is in + * particular used if a meta goal execution is blocked or + * stopped, in which case the pursuit of TodoGroup tasks is + * stopped. The ongoing meta goal execution must terminate + * (passed, failed or cancel) before the next TodoGroup task + * is pursued. + * + *

Note that new TodoGroup tasks can be added during an + * ongoing meta goal execution, and that this will not trigger + * any new meta goal execution. However, it will cause {@link + * #added_while_ongoing} to be set, which in turn results in + * an additional meta goal execution to follow the ongoing + * one. + */ + public Goal.Instance ongoing_meta_goal = null; + + /** + * A flag that indicates that a TodoGroup task was added + * during an ongoing meta goal execution. This will cause a + * subsequent meta goal execution before any TodoGroup task is + * progressed. + */ + public boolean added_while_ongoing = false; + + /** + * Invoked prior to pursuing the top element of the + * stack. This method adds the added instances onto the stack, + * cleares out the removed instances. It creates a meta goal + * instance if either forced is true, or the some instance was + * added or removed. + * + *

Note that this method will change the {@link #stack} by + * removing completed tasks, and actually adding tasks pending + * to be added. This happens before the invocation of the meta + * goal (if any). + */ + private Goal.Instance enterGroup(boolean forced) { + Vector/**/ coming = null; + Vector/**/ gone = null; + + // pick up the added and removed instances + // the added vector changes by parallel threads + synchronized ( added ) { + coming = added; + added = new Vector/**/(); + gone = removed; + removed = new Vector/**/(); + } + + // handle removed instances + for ( Iterator/**/ i = gone.iterator(); + i.hasNext(); ) { + Goal.Instance instance = (Goal.Instance) i.next(); + instance.data.deleteObserver( instance.thread_name, this ); + } + + // handle added instances + for ( Iterator/**/ i = coming.iterator(); + i.hasNext(); ) { + Goal.Instance instance = (Goal.Instance) i.next(); + stack.add( instance ); + instance.data.addObserver( instance.thread_name, this ); + } + + if ( meta_goal == null ) + return null; + + if ( coming.size() + gone.size() == 0 ) { + if ( ! forced ) + return null; + if ( System.getProperty( "gorite.todo.classic" ) != null ) + return null; + } + + if ( Goal.isTracing() ) { + System.err.println( + "-- " + Performer.this.name + "/" + name + + " meta goal " + added + " \"" + meta_goal + "\"" ); + } + + Goal.Instance mg = new BDIGoal( meta_goal ) + .instantiate( name, null ); + mg.performer = Performer.this; + mg.data = new Data() + .setValue( Goal.PERFORMER, Performer.this ) + .setValue( TODOGROUP, this ) + .setValue( "added", coming ) + .setValue( "removed", gone ) + .setValue( "top", stack.size() == 0? null : stack.get( 0 ) ); + mg.data.link( mg.thread_name ); + + return mg; + } + + /** + * A transient handle to the most recent blocking intention on + * the actual stack. + */ + public Goal.Instance blocking = null; + + /** + * Run the group. This means to run its top-of-stack goal + * instance. If that passes or fails, it is removed from the + * group, and the meta goal is called upon to possibly + * re-order remaining goal instances. If it returns STOPPED, + * the group execution returns STOPPED. If it returns BLOCKED, + * the meta goal is invoked in order to possibly re-arrange + * the group, and if so, the group execution continues with + * the new top goal instance. Otherwise the group execution + * returns blocked. + */ + public Goal.States runGroup() { + if ( Goal.isTracing() ) { + System.err.println( + "-- TodoGroup " + Performer.this.name + "/" + name + + " " + stack.size() ); + } + for ( ;; ) { + if ( ongoing_meta_goal != null ) { + if ( Goal.isTracing() ) { + System.err.println( + "-- TodoGroup " + Performer.this.name + + "/" + name + " ongoing meta goal " + + stack.size() ); + } + Goal.States s = ongoing_meta_goal.action(); + if ( Goal.isTracing() ) { + System.err.println( + "-- " + Performer.this.name + "/" + name + + " meta goal " + " \"" + meta_goal + "\" " + + s ); + } + if ( s == Goal.States.STOPPED ) { + continue; // Don't interrupt meta goal + } + if ( s == Goal.States.BLOCKED ) { + return s; + } + } + ongoing_meta_goal = enterGroup( ongoing_meta_goal == null ); + if ( ongoing_meta_goal != null ) + continue; + if ( stack.size() == 0 ) + return Goal.States.BLOCKED; + + Goal.Instance x = (Goal.Instance) stack.get( 0 ); + + if ( x == blocking ) { + if ( ! x.data.isRunning( x.thread_name ) ) { + return Goal.States.BLOCKED; + } + if ( Goal.isTracing() ) { + System.err.println( "-- BLOCKING " + x.head + + " without trigger" ); + } + blocking = null; + //return Goal.States.BLOCKED; + } + blocking = null; + Goal.States s = x.action(); + if ( Goal.isTracing() ) { + System.err.println( "-- noting " + x.head + " " + s ); + } + if ( s == Goal.States.STOPPED ) { + return s; + } + if ( s == Goal.States.BLOCKED ) { + blocking = x; + continue; + } + // s == Goal.States.PASSED + // s == Goal.States.FAILED + // s == Goal.States.CANCEL + x.setMonitoring( s ); + if ( stack.remove( x ) ) + removed.add( x ); + } + } + + /** + * Adds a goal for execution as part of this todo + * group. Returns the instance monitoring state. + */ + public Goal.States execute( + boolean reentry,Goal.Instance instance) + throws LoopEndException, ParallelEndException + { + if ( ! reentry ) { + todo_added += 1; + signalExecutor(); + synchronized ( added ) { + instance.setMonitoring( Goal.States.STOPPED ); + added.add( instance ); + } + signalExecutor(); + } else { + instance.throwSavedException(); + } + return instance.state; + } + + /** + * Utility method to obtain stack size. + */ + public int size() { + return stack.size(); + } + + /** + * Utility method to access the i:th stack element. + * @throws ArrayIndexOutOfBoundsException if the stack is + * empty, or i is negative. + */ + public Instance get(int i) { + return (Instance) stack.get( i ); + } + + /** + * Utility method to remove the i:th stack element. + * @throws ArrayIndexOutOfBoundsException if appropriate + */ + public Instance remove(int i) { + return (Instance) stack.remove( i ); + } + + /** + * Utility method to insert an element to be the i:th stack + * element. + * @throws ArrayIndexOutOfBoundsException if appropriate + */ + public void add(int i,Instance x) { + stack.add( i, x ); + } + + /** + * Utility method to move a selected {@link Goal.Instance} to + * be the one to pursue next. + */ + public void promote(int index) { + if ( index != 0 ) + stack.insertElementAt( stack.remove( index ), 0 ); + } + + /** + * Utility method to move the top {@link Goal.Instance} to + * stack bottom. + */ + public void rotate() { + if ( stack.size() > 1 ) + stack.add( stack.remove( 0 ) ); + } + + /// + /// Observer interface implementation + /// + + /** + * Implements {@link Observer#update} by pretending that a + * todo group is added, as way of signalling that the + * performer should process its todogroups again + */ + public void update(Observable x,Object y) { + todo_added += 1; + } + + } // End class TodoGroup + + /** + * The {@link Executor} for this performer. + */ + public Executor executor = null; + + /** + * Utility method to signal the {@link #executor} that this + * performer wants some attention. This will use the current + * executor, and attach to the default executor if none is + * assigned. + */ + public void signalExecutor() { + if ( executor == null ) { + executor = Executor.getDefaultExecutor(); + shareInquirables( null ); + executor.addPerformer( this ); + } + executor.signal(); + } + + /** + * Utility method to instantiate the goal for given the head, and + * then keep invoking the perform method with the given data + * context, until it passes or fails. + */ + synchronized public boolean performGoal(Goal goal,String head,Data d) { + signalExecutor(); + try { + Goal.Instance instance = goal.instantiate( head, d ); + Goal.States s = Goal.States.PASSED; + for ( ;; ) { + if ( Goal.isTracing() ) + System.err.println( "** perform " + instance.head ); + d.replaceValue( Goal.PERFORMER, this ); + s = instance.perform( d ); + if ( s == Goal.States.PASSED || s == Goal.States.FAILED ) + break; + boolean idle = s == Goal.States.BLOCKED; + if ( executor != null ) { + if ( executor.runPerformersBlocked() ) + idle = false; + } + if ( idle ) + instance.waitDone(); // ignore result + } + if ( executor != null ) { + while ( executor.runPerformersBlocked() ) + ; + } + return s == Goal.States.PASSED; + } catch (LoopEndException e) { + e.printStackTrace(); + return false; + } catch (ParallelEndException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Alternative performGoal method, with input and output held as + * Vector collections. + */ + public boolean performGoal( + Goal goal,String head, Vector/**/ ins,Vector/**/ outs) { + Data d = new Data(); + d.set( ins ); + boolean b = performGoal( goal, head, d ); + d.get( outs, true ); + return b; + } + + /** + * Alternative performGoal method, with goal given as a {@link + * String}, implying a BDI goal, and input and output held as + * Vector collections. + */ + public boolean performGoal( + String goal,String head, Vector/**/ ins,Vector/**/ outs) { + return performGoal( new BDIGoal( goal ), head, ins, outs ); + } + + /** + * Returns true if the performer execution should change focus. + */ + public boolean changeFocus() { + return executor != null && executor.changeFocus(); + } + + /** + * Returns a {@link RoleFilling} object for filling a given {@link + * Team.Role}. The object represents that this performer fills the + * given {@link Team.Role}. This method may be overridden in a + * specific performer, either as way of providing an extended + * {@link RoleFilling} object, or to possibly refuse the {@link + * Team.Role} filling, by returning null. + */ + public RoleFilling fillRole(Team.Role r) { + return new RoleFilling( r ); + } + + /** + * This class is used for representing this performer when acting + * in a given {@link Team.Role}. It in particular provides a + * {@link #lookup(String)} method that provides a combined plan + * lookup in both the filled {@link Team.Role} (qua {@link + * Capability}) and the enclosing {@link Performer} (qua {@link + * Capability}), and thereby extending the performer's capability + * with the role plans. + */ + public class RoleFilling extends Capability { + + /** + * The {@link Team.Role} being filled. + */ + public Team.Role role; + + /** + * Constructor. The enclosing performer is set to fill the + * given role. + */ + public RoleFilling(Team.Role r) { + role = r; + } + + /** + * Extends the base class method {@link Capability#lookup} by + * wrapping the result goals into {@link TransferGoal} goals. + */ + public Vector/**/ lookup(String name) { + Vector/**/ v = Performer.this.lookup( name ); + Vector/**/ vx = role.lookup( name ); + vx.addAll( super.lookup( name ) ); + for ( Iterator/**/ gi = v.iterator(); gi.hasNext(); ) { + vx.add( goalForPerformer( (Goal) gi.next() ) ); + } + return vx; + } + + /** + * Extends the base class method {@link Capability#hasGoals} by + * formarding to the performer + */ + public boolean hasGoal(String name) { + return role.hasGoal( name ) || + super.hasGoal( name ) || + Performer.this.hasGoal( name ); + } + + /** + * Returns the {@link Performer} of this RoleFilling. + */ + public Performer getPerformer() { + return Performer.this; + } + + /** + * Returns a text representation of this performer object. + */ + public String toString() + { + return "role filler " + Performer.this; + } + } + + /** + * Utility method to obtain the {@link Team} that has established + * a {@link Team.Role} filler under a given name in the {@link + * Data}. + */ + static public Team team(String rolename,Data d) { + RoleFilling r = (RoleFilling) d.getValue( rolename ); + return r.role.team(); + } + + /** + * Utility method to create a {@link TransferGoal} object aimed + * at this performer. + */ + public Goal goalForPerformer(Goal g) { + return new TransferGoal( this, g ); + } + + /** + * A method that returns {@link #name}. + */ + public String getName() { + return name; + } + + /// + /// Plan choice support + /// + + /** + * The association between (BDI) goals and their plan choice + * modifiers. These take effect in the BDI plan choice processing, + * to provide the decision for choosing a next applicable plan + * instance. The modifier should be either a {@link + * java.util.Random Random} object + * (for making a random draw among highest precedence plan + * options0, or a {@link String} (for making the choice by meta + * level plan processing). + * + * @see BDIGoal + */ + public Hashtable/**/ plan_choices = null; + + /** + * Utility method to declare a plan choice modifier for a + * goal. The modifier should be either a {@link java.util.Random + * Random} object, or a {@link String}. + * + * @see BDIGoal + */ + public void setPlanChoice(String goal,Object choice_method) { + if ( plan_choices == null ) { + plan_choices = new Hashtable/**/(); + } + plan_choices.put( goal, choice_method ); + } + + /** + * Utility method to obtain the plan choice modifier, if any, + * associated with the given goal. + */ + public Object getPlanChoice(String goal) { + return plan_choices == null? null : plan_choices.get( goal ); + } +} diff --git a/com/intendico/gorite/Plan.java b/com/intendico/gorite/Plan.java new file mode 100644 index 0000000..927cf55 --- /dev/null +++ b/com/intendico/gorite/Plan.java @@ -0,0 +1,167 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; +import com.intendico.data.Equals; + +/** + * The Plan class is a {@link Goal} that implements {@link Context} + * and {@link Precedence}. + * + *

The typical use of Plan object is as the top level {@link Goal} + * objects that get added to a {@link Capability}. + * + * @see BDIGoal + * @see AnyGoal + * @see AllGoal + * @see DataGoal + * @see TeamGoal + */ +public class Plan extends SequenceGoal implements Context, Precedence { + + /** + * Holds a plan name. This name is not used for anything within + * GORITE, but may serve as an identifier for a plan for a + * developer. + */ + private String plan_name; + + /** + * Utility method that creates a context Query for defining a data + * element value. + */ + public static Query define(String name,Object value) { + Ref r = new Ref( name ); + r.set( value ); + return new Equals( new Object [] { r } ); + } + + /** + * Constructor for a given plan name, goal name and sub goals. The + * given sub goals comprise the plan body. The goal name is that + * which the plan is intended to achieve. The plan name is held in + * {@link #plan_name}. + */ + public Plan(String pn,String n,Goal [] sg) { + super( n, sg ); + plan_name = pn; + } + + /** + * Constructor for a given plan name, goal name and without sub + * goals. The goal name is that which the plan is intended to + * achieve. The plan name is held in {@link #plan_name}. + */ + public Plan(String pn,String n) { + this( pn, n, null ); + } + + /** + * Constructor for a given goal name and sub goals. The given sub + * goals comprise the plan body. The goal name is that which the + * plan is intended to achieve. + */ + public Plan(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Constructor for a given goal name without sub goals. A body may + * be assigned to this plan at initialisation or later, or + * alternatively, this plan would be a task by virtue of an + * overriding {@link Goal#execute} implementation. The goal name + * is that which the plan is intended to achieve. + */ + public Plan(String n) { + this( null, n, null ); + } + + /** + * Construction for a goal name given as Class. Actual goal name + * is the name of the Class. + */ + public Plan(Class goal) { + this( null, goal.getName(), null ); + } + + /** + * Construction for a goal name given as Class, with plan + * name. Actual goal name is the name of the Class. + */ + public Plan(String name,Class goal) { + this( name, goal.getName(), null ); + } + + /** + * Construction for a goal name given as Class, with sub + * goals. Actual goal name is the name of the Class. + */ + public Plan(Class goal,Goal [] sg) { + this( null, goal.getName(), sg ); + } + + /** + * Construction for a goal name given as Class, with plan name and + * sub goals. Actual goal name is the name of the Class. + */ + public Plan(String name,Class goal,Goal [] sg) { + this( name, goal.getName(), sg ); + } + + /** + * The default precedence level for goals. + */ + public final static int DEFAULT_PRECEDENCE = 5; + + /** + * Default context method. + */ + public Query context(Data d) throws Exception { + return null; + } + + /** + * Default precedence method. Returns {@link + * #DEFAULT_PRECEDENCE}. + */ + public int precedence(Data d) { + return DEFAULT_PRECEDENCE; + } + + /** + * Returns a {@link String} representation of this object. + */ + public String toString() { + String n = "Plan:"; + if ( plan_name != null ) + n += " " + plan_name; + return n + "\n" + super.toString(); + } + + /** + * Returns the {@link #plan_name} attribute. + */ + public String getPlanName() { + return plan_name; + } + +} diff --git a/com/intendico/gorite/PlanChoiceGoal.java b/com/intendico/gorite/PlanChoiceGoal.java new file mode 100644 index 0000000..8b10515 --- /dev/null +++ b/com/intendico/gorite/PlanChoiceGoal.java @@ -0,0 +1,181 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.gorite.Goal.States; +import com.intendico.data.*; +import java.util.Vector; + +/** + * This is a utility class used by BDIGoal execution, to inject a plan + * choice goal for choosing the plan instance to pursue. It is not + * intended for explicit use within goal hierarchies. + * + *

The {@link BDIGoal} uses the {@link + * Performer#getPlanChoice(String)} method to determine whether or not + * a goal is asoicated with a plan choice goal. If this is the case, + * then it will create a PlanChoiceGoal instance for handling the + * choice of which of the applicable plan options to pursue next, and + * the subsequent execution of that option. + * + *

The choice is performed by triggering a (freschly created) + * {@link BDIGoal} for the plan choice goal, with a separate Data + * object that is initialised with "options", "failed" and "data". As + * the plan choice goal succeeds, the resulting "choice" data is used + * as being the choice of plan option, and then that plan option is + * executed as an attempt of achieving the original goal. If that + * succeeds, the original goal succeeds without further ado, and if it + * fails, then the BDIGoal forms a new, possibly empty collection of + * applicable (non-failed) plan options, and trigger a new + * PlanChoiceGoal for that collection. + * + *

If the plan choice goal fails, then all applicable options at + * this point are added to the failed set, the {@link BDIGoal} + * continues by forming a new, possibly empty collection of applicable + * (non-failed) plan options, and triggering a new PlanChoiceGoal for + * that collection. + * + *

If the plan choice goal fails for the final triggering, i.e, + * with an empty collection of applicable (non-failed) plan options, + * then the original goal fails. In fact, the orginal goal fails also + * if the final plan choice goal succeeds, except if this is also the + * first triggering and thus, there are no failed plan options + * either. In that case, the original goal will succeed if the plan + * choice goal succeeds. + * + * @see Performer#getPlanChoice(String) + * @see Performer#setPlanChoice(String,Object) + */ +public class PlanChoiceGoal extends Goal { + + /** + * The invocation data. + */ + public Data choice_data; + + /** + * Status flag for plan choice processing. + */ + public States done = States.STOPPED; + + /** + * The initial first option. + */ + public Goal first; + + /** + * The choice goal. This is set by the plan choice processing. + */ + public Goal choice; + + /** + * The currently executing goal instance. + */ + public Instance instance = null; + + /** + * Sub goal head, which is set when this goal is instantiated + */ + public String head; + + /** + * Constructor for a given plan choice goal and collection of plan + * instances. + */ + public PlanChoiceGoal( + Performer p,String pg,Vector/**/ plans,Vector/**/ failed) { + super( pg ); + choice_data = new Data(); + choice_data.setValue( Goal.PERFORMER, p ); + choice_data.setValue( "options", plans ); + choice_data.setValue( "failed", failed ); + if ( plans.size() > 0 ) + first = (Goal) plans.get( 0 ); + } + + /** + * Instantiating this goal for execution. + */ + public Goal.Instance instantiate(String h,Data d) { + head = h; + choice_data.setValue( "data", d ); + instance = new BDIGoal( + getGoalName() ).instantiate( h + "?", choice_data ); + return super.instantiate( h, d ); + } + + /** + * Control callback whereby the goal gets notified that an (its) + * intention is cancelled. This will forward the cancellation to + * the currently progressing instance, if any. + */ + public void cancelled(Instance which) { + if ( instance != null ) + instance.cancel(); + } + + /** + * Excution of this goal. If choice is null, then the execution is + * like a BDIGoal of the plan choice goal, with its data holding + * the "options", for setting the "choice". If that execution is + * successful, then the choice is taken, otherwise the original + * first choice is taken. If choice is set, then the execution + * executes that goal. + */ + public States execute(Data data) + throws ParallelEndException { + for ( ;; ) { + States s = States.FAILED; + try { + s = instance.perform( choice == null? choice_data: data ); + } catch (LoopEndException e) { + s = States.FAILED; + } catch (ParallelEndException e) { + s = States.FAILED; + if ( done != States.STOPPED ) + throw e; + } + if ( done != States.STOPPED ) + return s; + if ( s != States.PASSED && s != States.FAILED ) + return s; + done = s; + if ( s == States.FAILED ) { + Vector/**/ options = (Vector/**/) + choice_data.getValue( "options" ); + Vector/**/ failed = (Vector/**/) + choice_data.getValue( "failed" ); + failed.addAll( options ); + choice = first; + return States.FAILED; + } + choice = (Goal) instance.getValue( "choice" ); + if ( choice == null ) + choice = first; + if ( choice == null ) { + Vector/**/ failed = (Vector/**/) + choice_data.getValue( "failed" ); + return failed.size() == 0? States.PASSED : States.FAILED; + } + instance = choice.instantiate( head + "!", data ); + } + } + +} diff --git a/com/intendico/gorite/PoolExecutor.java b/com/intendico/gorite/PoolExecutor.java new file mode 100644 index 0000000..d6d4436 --- /dev/null +++ b/com/intendico/gorite/PoolExecutor.java @@ -0,0 +1,92 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +/** + * This is an Executor extension intended for applications containing + * multiple models (i.e., Executors), and multiple threads to execute + * them. The PoolExecutor is tied to a {@link + * java.util.concurrent.Executor} on concstruction. It adds itself for + * running whenever it gets runnable by signalling, or any performer + * ended in STOPPED rather than BLOCKED. This approach allows an + * execution pool of many threads, where one thread at a time is used + * for executing all each Executor's performers once, then + * interleaving the models in between these one-shot runs. + */ +public class PoolExecutor extends Executor { + + /** + * Cache of the execution queue interface. + */ + private java.util.concurrent.Executor runner; + + /** + * Control flag for capturing signal calls during performer + * execution. This is reset by the {@link #run()} method before it + * executes performers, and set by the {@link #signal()} method + * whenever called. + */ + private boolean signaled = false; + + /** + * Constructor with a given {@link java.util.concurrent.Executor}. + */ + public PoolExecutor(java.util.concurrent.Executor x) { + super( "PoolExecutor" ); + runner = x; + x.execute( this ); + } + + /** + * Utility method to "tickle" the executor with, just in case it + * is idle. Unless marked as running, this Executor is entered + * into the execution queue (and marked as running). + */ + synchronized public void signal() { + signaled = true; + if ( ! running ) { + running = true; + runner.execute( this ); + notifyAll(); + } + } + + /** + * Overrides {@link Executor#run()} for a one-shot execution of + * all performers. If any performer ends in STOPPED rather than + * BLOCKED, or there is a {@link #signal()} while the execution is + * in progress, then this Executor is re-entered into the + * execution pool. + */ + public void run() { + signaled = false; + if ( runPerformersOnce() != Goal.States.BLOCKED ) { + runner.execute( this ); // continue asap + return; + } + boolean b; + synchronized ( this ) { + b = running = signaled; + } + if ( b ) { + runner.execute( this ); // continue asap + } + } +} diff --git a/com/intendico/gorite/Precedence.java b/com/intendico/gorite/Precedence.java new file mode 100644 index 0000000..cd74844 --- /dev/null +++ b/com/intendico/gorite/Precedence.java @@ -0,0 +1,49 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; + +/** + * The Precedence interface is intended to be implemented by a {@link + * Goal} class that wants to suggest its precedence among alternative + * {@link Plan} options for a {@link BDIGoal} goal. + */ +public interface Precedence { + + /** + * The precedence method is invoked by {@link + * BDIGoal.BDIInstance#action} in order to sort the collection of + * plans such that higher precedence plans are attempted before + * lower precedence plans. + * + *

A {@link Goal} without Precedence implementation is + * assigned the precedence value of 5. + * + *

Note that this method is invoked for each of any multiple + * bindings defined by a {@link Plan#context} {@link Query}, with + * the relevant bindings assigned into the {@link Data} object + * provided. The same {@link Data} object will be used for the + * successive {@link #precedence} calls, but with the bindings + * successively updated to pin point the relevant plan + * alternative. + */ + public int precedence(Data d); +} diff --git a/com/intendico/gorite/RepeatGoal.java b/com/intendico/gorite/RepeatGoal.java new file mode 100644 index 0000000..5dd6da2 --- /dev/null +++ b/com/intendico/gorite/RepeatGoal.java @@ -0,0 +1,118 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * A RepeatGoal is achieved by means of achieving all its + * instantiations in parallel, where each instantiation is achieved by + * achieving the given sub goals in sequence. The goal definition + * includes a context specification, which enumerates the + * instantiations of the goal arising. Each instantiation refers to + * the same sequence of sub goals, but with different focus in the + * data. + * + *

The following code snippet illustrates the use of a RepeatGoal + * to process a multi-valued data element (named "options" in the + * example). + * + *

+ * new RepeatGoal( "process options", new Goal [] {
+ *     // This goal sequence has its "options" data focussed
+ *     // on one of the (previosuly multi-valued) options.
+ *     new BDIGoal( "do this with options" ),
+ *     new BDIGoal( "do that with options" ),
+ *     new BDIGoal( "do more with options" ),
+ * } ) {{ control = "options"; }} // Replicate for all the values of "options"
+ * 
+ * + * @see LoopGoal + * @see BDIGoal + */ +public class RepeatGoal extends BranchingGoal { + + /** + * Constructor with a given control name, goal name and sub goals. + */ + public RepeatGoal(String c,String n,Goal [] sg) { + super( n, sg ); + setGoalControl( c ); + } + + /** + * Constructor with a given goal name and sub goals. + */ + public RepeatGoal(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Convenience constructor without sub goals. + */ + public RepeatGoal(String n) { + this( n, null ); + } + + /** + * Creates and returns an instance object for achieving + * a RepeatGoal. + */ + public Instance instantiate(String head,Data d) { + return new RepeatInstance( head ); + } + + /** + * Implements re-instatiation of this goal relative something. + */ + public class RepeatInstance extends MultiInstance { + + /** + * Constructor. + */ + public RepeatInstance(String h) { + super( h ); + } + + /** + * To decide whether yet another branch should be made. + */ + public boolean more(int i,Data d) { + return i < d.size( getGoalControl() ); + } + + /** + * The branch is created be instantiating this goal as a + * sequence goal. + */ + public Instance getBranch(int i,String head,Data d) { + Goal g = new SequenceGoal( getGoalName(), getGoalSubgoals() ); + g.setGoalControl( getGoalControl() ); + g.setGoalGroup( getGoalGroup() ); + return g.instantiate( head, d ); + } + } + +} diff --git a/com/intendico/gorite/SequenceGoal.java b/com/intendico/gorite/SequenceGoal.java new file mode 100644 index 0000000..84c7b3c --- /dev/null +++ b/com/intendico/gorite/SequenceGoal.java @@ -0,0 +1,111 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * A SequenceGoal is achieved by means of achieving its sub goals in + * order. The goal fails when and if any sub goal fails. + */ +public class SequenceGoal extends Goal { + + /** + * Constructor. + */ + public SequenceGoal(String n,Goal [] sg) { + super( n, sg ); + } + + /** + * Convenience constructor without sub goals. + */ + public SequenceGoal(String n) { + this( n, null ); + } + + /** + * Creates and returns an instance object for achieving + * a SequenceGoal. + */ + public Instance instantiate(String head,Data d) { + return new SequenceInstance( head ); + } + + /** + * Implements sequential goal execution. + */ + public class SequenceInstance extends Instance { + + /** + * Constructor. + */ + public SequenceInstance(String h) { + super( h ); + } + + /** + * Keeps the currently ongoing subgoal instance + */ + public Instance ongoing = null; + + /** + * Index of the currently ongoing sub goal. + */ + public int index = 0; + + /** + * Cancels execution. + */ + public void cancel() { + super.cancel(); + if ( ongoing != null ) + ongoing.cancel(); + } + + /** + * Instantiates and performs sub goals in sequence. + */ + public States action(String head,Data d) + throws LoopEndException, ParallelEndException { + Goal [] subgoals = getGoalSubgoals(); + if ( subgoals == null ) + return super.action( head, d ); + while ( index < subgoals.length ) { + if ( ongoing == null ) { + ongoing = subgoals[ index ].instantiate( + head + "." + index, d ); + } + States s = ongoing.perform( d ); + if ( s != States.PASSED ) + return s; + index += 1; + ongoing = null; + } + return States.PASSED; + } + } + +} diff --git a/com/intendico/gorite/Team.java b/com/intendico/gorite/Team.java new file mode 100644 index 0000000..a5c86e7 --- /dev/null +++ b/com/intendico/gorite/Team.java @@ -0,0 +1,500 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; +import com.intendico.data.Relation; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; + +/** + * The Team class is a base class for a structured task performer, + * i.e. a {@link Performer} that combine sub performers. A particular + * team type is defined by extending the Team class. The structure of + * an instance of a team type is defined by calls to the {@link + * #addPerformer} method. This provides a lookup context for inner + * {@link TaskTeam} objects, which are used to define particular sub + * team constallations for particular team tasks. + */ + +public class Team extends Performer { + + /** + * Default constructor. + */ + public Team() { + } + + /** + * Constructor for team of given name. + */ + public Team(String name) + { + super( name ); + } + + /** + * This is the current role filling for this team -- it's + * obligation structure. + */ + public Vector/**/ performers = new Vector/**/(); + + /** + * This is a table of TaskTeam objects, to support longer term + * constallations. + */ + public Hashtable/**/ groupings = + new Hashtable/**/(); + + /** + * Adds a particular performer in a named role filling. + */ + synchronized public void addPerformer(Performer performer) + { + performers.add( performer ); + for ( Iterator/**/ ti = groupings.values().iterator(); + ti.hasNext(); ) { + ((TaskTeam) ti.next()).updateFillers( performer ); + } + } + + /** + * Adds multiple, named performers. + */ + public void addPerformers(Performer [] p) + throws Exception + { + if ( p != null ) { + for ( int i = 0; i < p.length; i++ ) + addPerformer( p[i] ); + } + } + + /** + * Utility method to pick a performer. Return null when the index + * is out of bounds. + */ + synchronized public Performer getPerformer(int i) { + if ( i < 0 || i >= performers.size() ) + return null; + return (Performer) performers.get( i ); + } + + /** + * Utility method to pick a performer by name. Returns the first + * Performer of this team with the given name, or null. + */ + public Performer getPerformer(String name) { + for ( Iterator/**/ i = + performers.iterator(); i.hasNext(); ) { + Performer p = (Performer) i.next(); + if ( name.equals( p.name ) ) { + return p; + } + } + return null; + } + + /** + * Utility method to remove a performer by name. Removes and + * returns the first Performer with the given name, or null; + */ + public Performer removePerformer(String name) { + for ( Iterator/**/ i = + performers.iterator(); i.hasNext(); ) { + Performer p = (Performer) i.next(); + if ( name.equals( p.getName() ) ) { + i.remove(); + return p; + } + } + return null; + } + + /** + * Utility method to access a long term TaskTeam by name. + */ + public TaskTeam getTaskTeam(String name) { + return (TaskTeam) groupings.get( name ); + } + + /** + * Utility method to define a long term TaskTeam by unique name. + */ + public void setTaskTeam(String name,TaskTeam group) { + groupings.put( name, group ); + group.updateFillers( performers ); + } + + /** + * A TaskTeam represents a sub-grouping of a team's performers + * into roles as appropriate for particular team activities. Each + * TaskTeam is populated to hold a selection of performers that + * are nominated for the task team roles according to their + * abilities. This role filling defines the candidates for acting + * in the roles when the TaskTeam is "established" in a particular + * intention. + * + *

The role filling in a TaskTeam as well as the established + * role filling in an intention may be changed at any time. When + * the TaskTeam is installed for a {@link Team}, the current + * {@link #performers} collection is revide and performers are + * nominated to roles as appropriate. Thereafter each subsequently + * added {@link Performer} is automatically considered, and + * nominated to roles of installed TaskTeams as appropriate. The + * detail nomination decision is made by the overridable {@link + * Role#canFill(Performer)} method, which by default ensures + * distinct, singular role filling, and that the nominated + * {@link Performer} has plans for all required goals. + * + *

The TaskTeam is deployed on demand for intentions, + * typically by by means of {@link #deploy(String)} goal in team + * plans. When deployed in this way, the TaskTeam role filling is + * established in the intention {@link Data} via {@link + * #establish(Data)}, with a further filtering through the + * overridable {@link Role#canAct(Data,Performer)} method, which + * by default is "true". + */ + public class TaskTeam { + + /** + * The roles of the task team. + */ + public Vector/**/ roles = new Vector/**/(); + + /** + * The population of this task team, held as a {@link + * Relation} beteen roles and performers. + */ + public Relation fillers = new Relation( "fillers", 2 ); + + /** + * Utility method to add a role to the task team. + */ + public void addRole(Role r) { + r.group = this; + roles.add( r ); + } + + /** + * Utility method to remove a role from the task team. This + * also removes any population for the role. + */ + public void removeRole(Role r) { + r.group = this; + roles.remove( r ); + filling( r, new Ref( "$p" ) ).remove(); + } + + /** + * Utility method to return a role filling {@link Query} + * object for a given role and performer, either of which is + * given as a constant of its type, null, or a {@link Ref} + * object, which may be (appropriately) bound or unbound. + */ + public Query filling(Object r,Object p) { + try { + return fillers.get( new Object [] { r, p } ); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Utility method to define a role filler for a role. + */ + public void fillRole(Role r,Performer p) { + filling( r, p ).add(); + } + + /** + * Utility method to clear all filling. + */ + public void clearFilling() { + filling( null, null ).remove(); + } + + /** + * Updates the role nominations for the given {@link Performer + * performers}. This method simply iterates over the + * collection and updates each performer one by one via {@link + * #updateFillers(Performer)}. + */ + public void updateFillers(Vector/**/ performers) { + for ( Iterator/**/ pi = performers.iterator(); + pi.hasNext(); ) { + updateFillers( (Performer) pi.next() ); + } + } + + /** + * Updates the role nominations for the given {@link + * Performer} as appropriate for roles in this taks team. + * Which roles it is nominated to, if any, is determined by + * {@link Role#canFill(Performer)}. + */ + public void updateFillers(Performer p) { + for ( Iterator/**/ ri = roles.iterator(); ri.hasNext(); ) { + Role r = (Role) ri.next(); + Query q = filling( r, p ); + if ( r.canFill( p ) ) { + if ( Goal.isTracing() ) { + System.err.println( + "** can Fill: " + r.name + " " + p ); + } + q.add(); + } else { + if ( Goal.isTracing() ) { + System.err.println( + "** cannot Fill: " + r.name + " " + p ); + } + q.remove(); + } + } + } + + /** + * Utility method to establish the role filling in the given + * intention data. This will establish the roles one by one in + * declaration order, using the current fillers as filtered by + * the {@link Role#canAct} method. + * + *

Note that role filling is set up as data elements using + * the role names. The value for a role name is the collection + * of fillers that can act in the role, qua {@link + * Capability}, but actually represented by {@link + * Performer.RoleFilling} objects as returned by the {@link + * Performer#fillRole(Team.Role)} method. + * @return true if all roles have some filling + */ + public boolean establish(Data d) { + if ( Goal.isTracing() ) { + System.err.println( "** establish task team" ); + } + for ( Iterator/**/ ri = roles.iterator(); + ri.hasNext(); ) { + Role role = (Role) ri.next(); + Ref $p = new Ref( "$p" ); + Query q = filling( role, $p ); + d.forget( role.name ); + boolean filled = false; + HashSet actors = Team.getActors( role.name, d ); + try { + while ( q.next() ) { + Performer candidate = (Performer) $p.get(); + if ( actors.contains( candidate ) ) { + if ( Goal.isTracing() ) { + System.err.println( + "** already Acting: " + role.name + + " " + candidate ); + } + } else if ( role.canAct( d, candidate ) ) { + if ( Goal.isTracing() ) { + System.err.println( + "** can Act: " + role.name + + " " + candidate ); + } + Capability c = candidate.fillRole( role ); + if ( c != null ) { + d.setValue( role.name, c ); + filled = true; + } else if ( Goal.isTracing() ) { + System.err.println( + "** refuses to Act: " + role.name + + " " + candidate ); + } + } else if ( Goal.isTracing() ) { + System.err.println( + "** cannot Act: " + role.name + + " " + candidate ); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + if ( ! filled ) { + if ( Goal.isTracing() ) { + System.err.println( + "** Role not established: " + role.name ); + } + return false; // couldn't establish the role at + // all + } + } + return true; + } + + /** + * Utility method that creates a {@link Goal} of establishing + * this task team in the intention data when the goal is to be + * achieved. + * @see #establish(Data) + */ + public Goal deploy(String name) { + return new Goal( "deploy " + name ) { + public States execute(Data d) { + return establish( d )? States.PASSED : States.FAILED; + } + }; + } + + /** + * Utility method to collate all actors of this TaskTeam's + * roles in the given Data. + * @return a Hashtable of {@link Performer} HashSets, keyed by + * the {@link Role} names of the TaskTeam's roles. + */ + public Hashtable/*>*/ getActors(Data d) { + Hashtable result = new Hashtable(); + for ( Iterator/**/ ri = roles.iterator(); ri.hasNext(); ) { + Role r = (Role) ri.next(); + HashSet/**/ actors = Team.getActors( r.name, d ); + if ( actors != null ) + result.put( r.name, actors ); + } + return result; + } + + /** + * Utility method to return the enclosing team wherein this + * TaskTeam object is created. + */ + public Team team() { + return Team.this; + } + } + + /** + * Utility method to collate the performers defined to act in the + * given role in a given Data. + * @see TaskTeam#getActors(Data) + */ + static public HashSet/**/ getActors(String r,Data d) { + HashSet/**/ result = new HashSet/**/(); + Data.Element e = d.find( r ); + if ( e != null ) { + for ( Iterator/**/ vi = e.values.iterator(); + vi.hasNext(); ) { + result.add( ((Capability) vi.next()).getPerformer() ); + } + } + return result; + } + + /** + * The Role class is a base class for representing team members in + * a team. Role extends {@link Capability}, so as to hold {@link + * Goal} methods that are tied to the role within the context of a + * {@link Team}. It further contains an attribute {@link + * #required}, which indicate goals that are required by a {@link + * Performer} of the role. + */ + public class Role extends Capability { + + /** + * The reference name for this role. + */ + public String name; + + /** + * The goal names required by a role filler. + */ + public String [] required; + + /** + * The {@link TaskTeam} that this role is a role of. This is + * set by the {@link TaskTeam#addRole} method when the role is + * added. + */ + public TaskTeam group; + + /** + * Constructor that takes an array of required goals. + */ + public Role(String n,String [] r) { + name = n; + required = r; + setPerformer( Team.this ); + } + + /** + * Overrides {@link Capability#addCapability} so as to link up + * added capabilities with this team qua performer. + */ + public void addCapability(Capability c) { + c.setPerformer( Team.this ); + super.addCapability( c ); + } + + /** + * Returns the team of the role. + */ + public Team team() { + return Team.this; + } + + /** + * Overridable method that determines if the given performer + * can fill this role (within its {@link TaskTeam} {@link + * #group}). The default decision implemented by this + * method ensures that:
    + * + *
  1. the performer is not already filling another role, + * + *
  2. the role is not already filled by another performer, + * and + * + *
  3. the performer has some plans to achieve the required + * goals. + * + *
+ */ + public boolean canFill(Performer p) { + try { + if ( group.filling( null, p ).next() ) + return false; + if ( group.filling( this, null ).next() ) + return false; + return p.hasGoals( required ); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * Overridable method that determines whether the given {@link + * Performer} can act in this role within the {@link + * Goal.Instance intention} represented by the given {@link + * Data}. The default decision is "yes". + * @see Team#getActors(String,Data) + */ + public boolean canAct(Data d,Performer p) { + return true; + } + + } + +} diff --git a/com/intendico/gorite/TeamGoal.java b/com/intendico/gorite/TeamGoal.java new file mode 100644 index 0000000..25155ac --- /dev/null +++ b/com/intendico/gorite/TeamGoal.java @@ -0,0 +1,118 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; +import com.intendico.data.Ref; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; + +/** + * A TeamGoal is a {@link Goal} that is achieved as a {@link BDIGoal} + * by a given {@link Team.Role}. The TeamGoal looks up {@link + * Team.Role} fillers in the {@link Data} under the given role name, + * and presents itself as a {@link BDIGoal} to be performed by the + * fillers. + * + *

Note that the TeamGoal distributes like a {@link RepeatGoal} + * over the role name data, requesting all fillers to achieve the goal + * in parallel. The TeamGoal succeeds when all the fillers succeed, + * and fails if any role filler fails. In the latter case, the + * branches for the fillers in progress are cancelled. + * + *

If the TeamGoal is implemented by a {@link Team.Role} plan that + * + * @see Team + * @see Team.TaskTeam + * @see Performer#fillRole + * @see ControlGoal + */ +public class TeamGoal extends BranchingGoal { + + /** + * Constructor for TeamGoal. + */ + public TeamGoal(String role,String n) { + super( n, null ); + setGoalControl( role ); + } + + /** + * Cache of construction object (when not a String). + */ + public Object specific; + + /** + * Constructor using an object other than String. Then the class + * name of the object is used as goal name, and the object is held + * as {@link #specific}. However, if the given object is a {@link + * String}, then its value (rather than its type) is used as goal + * name anyhow. + * @see BDIGoal#BDIGoal(Object) + */ + public TeamGoal(String role,Object n) { + super( ( n instanceof String )? (String) n : n.getClass().getName() ); + specific = n; + setGoalControl( role ); + } + + /** + * Make an Instance for executing this goal. + */ + public Instance instantiate(String head,Data d) { + return new TeamInstance( head ); + } + + /** + * Creates and returns an instance object for achieving + * a TeamGoal. + */ + public class TeamInstance extends MultiInstance { + + /** + * Constructor. + */ + public TeamInstance(String h) { + super( h ); + } + + /** + * To decide whether yet another branch should be made. + */ + public boolean more(int i,Data d) { + return i < Math.max( 1, d.size( getGoalControl() ) ); + } + + /** + * The branch is created be instantiating a {@link BDIGoal}. + */ + public Instance getBranch(int i,String head,Data d) { + Goal g = specific != null? + new BDIGoal( specific ) : new BDIGoal( getGoalName() ); + g.setGoalGroup( getGoalGroup() ); + g.setGoalControl( getGoalControl() ); + return g.instantiate( head, d ); + } + + } + +} diff --git a/com/intendico/gorite/TransferGoal.java b/com/intendico/gorite/TransferGoal.java new file mode 100644 index 0000000..a014239 --- /dev/null +++ b/com/intendico/gorite/TransferGoal.java @@ -0,0 +1,198 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite; + +import com.intendico.data.Query; + +/** + * The TransferGoal class is utility class for wrapping a goal that is + * transferred into a different target performer context. + */ +public class TransferGoal extends Plan { + + /** + * Holds the target performer. + */ + public Performer target; + + /** + * Holds the transferred goal. + */ + public Goal goal; + + /** + * Constructor. + */ + public TransferGoal(Performer to,Goal g) { + super( g.getGoalName() ); + target = to; + goal = g; + } + + /** + * Constructor without Performer, which instead is looked up + * at instantiation using the control string. + */ + public TransferGoal(Goal g) { + super( g.getGoalName() ); + target = null; + goal = g; + } + + /** + * Overrides the base class method {@link Goal#instantiate} to + * provide a wrapper instance for the transferred goal. + */ + public Instance instantiate(String h,Data d) { + return new TransferInstance( h, d ); + } + + /** + * Transfer goals are equals by the goals they transfer. + */ + public boolean equals(Object x) { + return x instanceof TransferGoal && equals( (TransferGoal) x ); + } + + /** + * Transfer goals are equals by the goals they transfer. + */ + public boolean equals(TransferGoal x) { + return goal.equals( x.goal ); + } + + /** + * Wrapper class for transfer goal instances. + */ + public class TransferInstance extends Instance { + + /** + * Holds the instance of the transferred goal. + */ + public Instance instance; + + public Performer into; + /** + * Constructor. Constructs an instance of the transferred + * goal while temporarily shifting into the target + * performer. + */ + public TransferInstance(String h,Data d) { + super( h ); + instance = goal.instantiate( h + "|", d ); + } + + /** + * Control callback whereby the intention gets notified that + * it is cancelled. This will forward the cancellation to the + * currently progressing instance, if any. + */ + public void cancel() { + if ( instance != null ) + instance.cancel(); + } + + /** + * Implements the instance action, which sets the target + * performer as {@link Goal#PERFORMER}, then invokes the + * target instance, and thereafter restores the performer. + */ + public States action(String h,Data d) + throws LoopEndException, ParallelEndException { + Performer from = (Performer) d.getValue( PERFORMER ); + try { + if ( target == null ) { + into = (Performer) d.getValue( getGoalControl() ); + } else { + into = target; + } + // Note the use of setValue() rather than + // replaceValue() below. This is done to ensure + // that PERFORMER is set in the local data + // context, without replacing PERFORMER in the + // parent context. + // + // Also, the new PERFORMER is pushed on top of + // any existing value, showing the chain of + // performers involved in the computation. Unless + // the data context is in "goldfish" mode of + // course. + d.setValue( PERFORMER, into ); + if ( isTracing() ) + System.err.println( ">> " + into ); + return instance.perform( d ); + } finally { + // Note that restoreValue only affects the local + // data context. + d.restoreValue( PERFORMER, from ); + if ( isTracing() ) + System.err.println( "<< " + into ); + } + } + } + + /** + * Implements the {@link Context#context} method by forwarding to + * the wrapped goal. + */ + public Query context(Data d) throws Exception { + if ( goal instanceof Context ) { + Capability from = (Capability) d.getValue( PERFORMER ); + try { + if ( isTracing() ) { + System.err.println( "~(context)> (" + target + ")" ); + } + d.setValue( PERFORMER, target ); + return ((Context) goal).context( d ) ; + } finally { + if ( isTracing() ) { + System.err.println( "<(context)~ (" + target + ")" ); + } + d.restoreValue( PERFORMER, from ); + } + } else { + return null ; + } + } + + /** + * Implements the {@link Precedence#precedence} method by + * forwarding to the wrapped goal. + */ + public int precedence(Data d) { + if ( goal instanceof Precedence ) { + Capability from = (Capability) d.getValue( PERFORMER ); + try { + if ( isTracing() ) { + System.err.println( "~(precedence)> (" + target + ")" ); + } + d.setValue( PERFORMER, target ); + return ((Precedence) goal).precedence( d ); + } finally { + if ( isTracing() ) { + System.err.println( "<(precedence)~ (" + target + ")" ); + } + d.replaceValue( PERFORMER, from ); + } + } else { + return DEFAULT_PRECEDENCE ; + } + } +} diff --git a/com/intendico/gorite/addon/.cvsignore b/com/intendico/gorite/addon/.cvsignore new file mode 100644 index 0000000..6b468b6 --- /dev/null +++ b/com/intendico/gorite/addon/.cvsignore @@ -0,0 +1 @@ +*.class diff --git a/com/intendico/gorite/addon/BellBoy.java b/com/intendico/gorite/addon/BellBoy.java new file mode 100644 index 0000000..4fcb45d --- /dev/null +++ b/com/intendico/gorite/addon/BellBoy.java @@ -0,0 +1,242 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.gorite.*; +import java.util.Hashtable; +import java.util.Set; + +/** + * This is a {@link Capability} (type) that provides a simple + * inter-model messaging service within a single JVM. It uses the + * {@link Performer#getName()} method of the containing {@link + * Performer} as messaging end point identity. The idea is that all + * communicating performers are set up to have their own instance of + * this capability type. The following is an example of use, with a + * receive plan outline: + * + *

+ * new Performer( "morgan" ) {{
+ *     addCapability( new BellBoy() );
+ *     addPlan( new Plan( BellBoy.RECV, new Goal [] {
+ *         .... deal with received message in "percept"
+ *     } );
+ * }};
+ * 
+ * + *

Sending Messages + * + *

The capability handles a {@link #SEND "Send BellBoy message"} + * {@link Goal goal}, with {@link #MSG "bellboy message"} holding a + * {@link BellBoy.Envelope message envelope}, by delivering that + * message to the {@link Envelope#getWhom() indicated recipient}. + * + *

Sending can also be done directly in Java code by using the + * static {@link BellBoy#send(Envelope)} or {@link + * BellBoy#send(String,Object)} methods. + * + *

Receiving Messages + * + *

Upon receiving a message, the capability triggers a {@link + * #RECV "Got BellBoy message"} {@link BDIGoal} goal, on the {@link + * #TODO "bellboy"} {@link com.intendico.gorite.Performer.TodoGroup + * TodoGroup} and with {@link Perceptor#data_name "percept"} holding + * the message, + * + *

Reception can also be triggered in Java code by using the + * {@link #receive(Object)} method. + * + *

NOTE + * + *

Messages are delivered through in-core sharing, and they are not + * copied! Not copied! This means that the sender and receiver + * end up sharing the one message object. In proper use, the sender + * should release all its handles to the message object after having + * sent it. The capability supports this by {@link + * Data#forget(String) forgetting} {@link #MSG "bellboy message"} + * after having sent the message. + */ +public class BellBoy extends Capability { + + /** + * The interface for standard BellBoy messages. + */ + public interface Envelope { + /** + * Returns message recipient identity. + */ + public String getWhom(); + + /** + * Returns the message payload, i.e., the "actual" message. + */ + public Object getPayload(); + } + + /** + * Convenience constant for the send goal. + */ + public final static String SEND = "Send BellBoy message"; + + /** + * Convenience constant for the receive goal. + */ + public final static String RECV = "Got BellBoy message"; + + /** + * Convenience constant for the send goal data element. + */ + public final static String MSG = "bellboy message"; + + /** + * Convenience constant for BellBoy messaging {@link + * com.intendico.gorite.Performer.TodoGroup TodoGroup}. + */ + public final static String TODO = "bellboy"; + + /** + * The table of communicating BellBoy instances within this JVM. + */ + public static Hashtable/**/ club = + new Hashtable/**/(); + + /** + * The {@link Perceptor} for receiving BellBoy messages. This is + * set up by the {@link #initialize()}, which is invoked + * automatically by GORITE. The installed {@link Perceptor} + * triggers {@link #RECV "Got BellBoy message"} goals on the + * {@link #TODO "bellboy"} {@link + * com.intendico.gorite.Performer.TodoGroup TodoGroup} with the + * received messages as the {@link Perceptor#data_name "percept"} + * data. + */ + public Perceptor recv; + + /** + * Binds a BellBoy to be a {@link #club} member under a given + * name. This replaces any previous binding, and binding + * null removes the previous binding. + */ + public static void bind(String name,BellBoy b) { + if ( b == null ) { + synchronized ( club ) { + club.remove( name ); + } + } else { + synchronized ( club ) { + club.put( name, b ); + } + } + } + + /** + * Returns the BellBoy bound to a given name. + */ + public static BellBoy find(String name) { + synchronized ( club ) { + return (BellBoy) club.get( name ); + } + } + + /** + * Returns the names of the current {@link BellBoy#club} members. + */ + public static Set/**/ getMembers() { + synchronized ( club ) { + return club.keySet(); + } + } + + /** + * Creates a standard BellBoy {@link Envelope}. + */ + public static Envelope pack(final String whom,final Object what) { + return new Envelope() { + public String getWhom() { + return whom; + } + public Object getPayload() { + return what; + } + }; + } + + /** + * Receives a message. This causes a percept on the {@link #recv} + * {@link Perceptor}. The method returns false if {@link #recv} is + * unassigned, otherwise it returns true. + */ + public boolean receive(Object m) { + if ( recv == null ) + return false; + recv.perceive( m ); + return true; + } + + /** + * Sends a named recipient a message. + */ + public static boolean send(String to,Object message) { + BellBoy whom = find( to ); + return ( whom == null )? false : whom.receive( message ); + } + + /** + * Sends an envelope. + */ + public static boolean send(Envelope m) { + return ( m == null )? false : send( m.getWhom(), m.getPayload() ); + } + + /** + * Initializes the capability with the abilities to send and + * receive messages, and then it joins the BellBoy club. + * + *

The capability is set up to handle a {@link #SEND "Send + * BellBoy message"} {@link Goal goal}, with {@link #MSG "bellboy + * message"} holding a {@link BellBoy.Envelope message envelope}, + * by delivering that message to the {@link Envelope#getWhom() + * indicated recipient}. + * + * The goal fails if {@link #MSG "bellboy message"} is null. If + * it's non-null but not a {@link BellBoy.Envelope}, then a {@link + * ClassCastException} is thrown. + * + * The goal also fails if the {@link BellBoy#club} doesn't include + * the desired recipient. + */ + public void initialize() { + // Prepare to receive messages + recv = new Perceptor( getPerformer(), RECV, TODO ); + // Define ability to send message as goal + addGoal( new Goal( SEND ) { + public States execute(Data data) { + if ( send( (Envelope) data.getValue( MSG ) ) ) { + data.forget( MSG ); + return States.PASSED; + } + return States.FAILED; + } + } ); + // Join the club + bind( getPerformer().getName(), this ); + } + +} diff --git a/com/intendico/gorite/addon/Handshake.java b/com/intendico/gorite/addon/Handshake.java new file mode 100644 index 0000000..eeea2fb --- /dev/null +++ b/com/intendico/gorite/addon/Handshake.java @@ -0,0 +1,101 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.gorite.Goal; +import com.intendico.gorite.Data; +import java.util.Observable; + +/** + * Utility class to deal with request-response protocols between + * in-core performers with BellBoy capability. The requester would + * include a {@link Handshake#create()} step in the requesting plan, + * with data elements "responder" and "request" set up, and the + * responder, who gets a Handshake object as BellBoy percept, uses the + * {@link Handshake#reply(Object)} method to issue their reply. + * + *

Note: a null response is not a response, but will make the + * requester keep waiting. + */ +public class Handshake extends Observable { + + /** + * The request message of this Handshake. + */ + public Object message; + + /** + * The response message of this Handshake. + */ + public Object response; + + /** + * Java method to set the reply and notify to the requester + * intention. + */ + public void reply(Object r) { + response = r; + setChanged(); + notifyObservers(); + } + + /** + * Returns a Goal object for performing a Handshake. It uses the + * data elements "responder" and "request" for input details, the + * data element "pending handshake" transiently, and the data + * element "response" for output, i.e. the reply object. + * + * Note that all incoming "pending handshake" values are + * forgotten, but not upon a completed handshake. + */ + public static Goal create() { + return new Goal( "Perform handshake" ) { + /** + * Issue the request when the goal is instantiated. + */ + public Instance instantiate(String head,Data data) { + String who = (String) data.getValue( "responder" ); + Handshake h = new Handshake(); + h.message = data.getValue( "request" ); + data.forget( "pending handshake" ); + if ( BellBoy.send( who, h ) ) { + data.setValue( "pending handshake", h ); + } + return super.instantiate( head, data ); + } + + /** + * Keep blocking until there is a non-null reply + */ + public States execute(Data data) { + Handshake h = (Handshake) data.getValue( "pending handshake"); + if ( h == null ) { + return States.FAILED; + } + if ( h.response == null ) { + data.setTrigger( h ); + return States.BLOCKED; + } + data.setValue( "response", h.response ); + return States.PASSED; + } + }; + } +} diff --git a/com/intendico/gorite/addon/Perceptor.java b/com/intendico/gorite/addon/Perceptor.java new file mode 100644 index 0000000..76e0a8d --- /dev/null +++ b/com/intendico/gorite/addon/Perceptor.java @@ -0,0 +1,199 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.gorite.*; + +/** + * This is a class for implementing a performer percept channel, where + * each percept triggers an intention for handling it, which gets + * added to a {@link com.intendico.gorite.Performer.TodoGroup} for + * eventual processing. + * + *

The Perceptor class is made separate from Performer so as to + * allow multiple, different perceptors and to simplify its use at the + * perception source end. + * + *

A "pure" event driven system would be run via the {@link + * Executor#run} method (rather than using a top level {@link + * Performer#performGoal(Goal,String,Data)} call), and have Perceptor + * objects to cause executions. + */ +public class Perceptor { + + /** + * Keeps track of which {@link Performer} this is a perceptor + * for. This is given as constructor argument, and it typically + * remains unchanged. Though there is no harm in changing it and + * thereby move the Perceptor to another Performer. + */ + public Performer performer; + + /** + * Keeps track of which {@link Goal} hierarchy to use for handling + * a percept. It must not be null, and is given as optional + * constructor argment. The default goal hierarchy is + * + *

+     *     new BDIGoal( "handle percept" )
+     * 
+ * + * which thus allows the actual goal hierarchy be defined among + * the performers capabilities, with possibly many alternative + * handler plans depending on the kinds of percepts. + */ + public Goal handler; + + /** + * Keeps track of which {@link com.intendico.gorite.Data.Element} + * name to use for the percept object. The default name is + * "percept". + */ + public String data_name = "percept"; + + /** + * Keeps track of which {@link + * com.intendico.gorite.Performer.TodoGroup} to use for the + * percept handling intentions. The default group is "percepts". + */ + public String todo_group = "percepts"; + + /** + * Constructor with performer, goal and todo group name. + */ + public Perceptor(Performer p,Goal g,String todo) + { + performer = p; + handler = g; + todo_group = todo; + g.setGoalGroup( todo ); + } + + /** + * Constructor with performer and goal, using default todo group. + */ + public Perceptor(Performer p,Goal g) + { + performer = p; + handler = g; + g.setGoalGroup( todo_group ); + } + + /** + * Alternative constructor with goal name and todo group name. + */ + public Perceptor(Performer p,String goal,String todo) { + this( p, new BDIGoal( goal ), todo ); + } + + /** + * Alternative constructor with performer and name of a BDI goal + * to be created here. + */ + public Perceptor(Performer p,String goal) + { + this( p, new BDIGoal( goal ) ); + } + + /** + * Alternative constructor with performer only, and using the + * default percept handling goal "handle percept" and + * default todo group "percepts". + */ + public Perceptor(Performer p) + { + this( p, "handle percept" ); + } + + /** + * Utility method to reset the perceptor set up. The new settings + * apply for subsequent perceive calls. + */ + public void setup(String goal,String data,String todo) { + handler = new BDIGoal( goal ); + handler.setGoalGroup( todo ); + data_name = data; + todo_group = todo; + } + + /** + * The method to use for making the performer perceive + * something. The given object is set up as value of a named data + * element, and percept handling goal is instantiated for handling + * it. + */ + public void perceive(Object percept) + { + perceive( percept, new Data(), handler ); + } + + /** + * Alternative perception method with a given a Data object. + */ + public void perceive(Object percept,Data data) { + perceive( percept, data, handler ); + } + + /** + * Alternative perception method with a given a Data object and + * special perception goal name. + */ + public void perceive(Object percept,Data data,String goal) { + perceive( percept, data, goal != null? new BDIGoal( goal ) : handler ); + } + + /** + * Alternative perception method with a given a Data object and + * special perception goal name. + */ + public void perceive(Object percept,Data data,Goal goal) { + + if ( ! "X".equals( data.thread_name ) ) { + // This indicates that the Data object is already used or + // in use, which can cause all sorts of funny things + throw new Error( "Schizogenic Data object!!" ); + } + + data.setValue( data_name, percept ); + data.setValue( Goal.PERFORMER, performer ); + + if ( goal == null ) + goal = handler; + Goal.Instance gi = goal.instantiate( todo_group, data ); + + // The following set up is essential to make it appear that + // the goal instace has been performed. (c.f {@link + // Goal.Instance#perform}) + gi.data = data; + gi.performer = performer; + data.link( gi.thread_name ); + + //System.err.println( "Data = " + data ); + //System.err.println( "Data:" + data_name + " = " + percept ); + + try { + performer.execute( false, gi, todo_group); + } catch (Exception e) { + // Not good :-( + e.printStackTrace(); + } + performer.signalExecutor(); + } +} diff --git a/com/intendico/gorite/addon/Reflector.java b/com/intendico/gorite/addon/Reflector.java new file mode 100644 index 0000000..f84d3d7 --- /dev/null +++ b/com/intendico/gorite/addon/Reflector.java @@ -0,0 +1,259 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.gorite.Performer; +import com.intendico.gorite.Data; +import com.intendico.gorite.Goal; +import com.intendico.gorite.BDIGoal; +import com.intendico.data.Query; +import com.intendico.data.Ref; +import com.intendico.data.Snapshot; + +import java.util.IdentityHashMap; +import java.util.Observer; +import java.util.Vector; +import java.util.Iterator; +import java.util.Observable; + +/** + * A Reflector is a reasoning faculty to translate situation changes + * into percepts. The Reflector is populated with some number of + * {@link Reflection} objects, which each combines a {@link Query} + * with a {@link Goal}, so as to intend the {@link Goal} as a percept + * when the {@link Query} changes state, from false to true or from + * true to false, for particular bindings. + * + *

For example, consider the following code snippet: + *

+ *     new Reflector( performer, "grandparent", "inference" ) {{
+ *         Ref p = new Ref( "parent" );
+ *         addQuery( new And( parent.get( new Ref( "gp"), p ),
+ *                            parent.get( p, new Ref( "ch") ) ) );
+ *     }};
+ * 
+ * + * The example code would keep observing the "parent" relation, and + * when it is updated to present a different grandparent-parent-child + * linkage from before, the goal "grandparent" is intended on the + * "inference" {@link com.intendico.gorite.Performer.TodoGroup + * Performer.TodoGroup}, with the data elements "gp", "parent" and + * "ch" set. Further, the data element "reflection event" is set to + * "added" or "lost" to indicate whether the change is of adding or + * losing a matching binding for the query. + * + * + *

Technicallly each added {@link Query} gets wrapped into a + * {@link Snapshot} in a {@link Reflector.Reflection} object, which is + * set up as {@link Observer} to observe any changes to the {@link + * Query} sources. The {@link Snapshot} wrapping acts as a filter to + * ensure that only changes cause percepts. The percept will hold the + * input query as "percept" data element, and all {@link Ref} object + * values of the query as corresponding data elemens. + * + */ +public class Reflector extends Perceptor { + + /** + * The name of the data element to indicate whether its an + * added or lost event + */ + public String REFLECTION_EVENT = "reflection event"; + + /** + * Constructor that ties the Reflector to the given {@link + * Performer}, causing goals as named in the {@link + * com.intendico.gorite.Performer.TodoGroup Performer.TodoGroup} + * as named. + */ + public Reflector(Performer p,String goal,String todo) { + super( p, goal, todo ); + } + + /** + * Utility class for observing the query sources, and trigger + * goals when bindings change state. + */ + public class Reflection implements Observer { + + /** + * Holds the query wrapped into a {@link Snapshot} + */ + public Snapshot snap; + + /** + * Holds the perception goal caused by this reflection. + */ + public Goal goal; + + /** + * Holds the reflection mode; postive for reacting to added + * bindings, negative for reacting to lost bindings and zero + * for both. + */ + public int mode = 1; + + /** + * Alternative constructor, with a goal name. + */ + public Reflection(Query q,String g) { + this( q, g != null? new BDIGoal( g ) : null ); + } + + /** + * Constructor, which applies the {@link Snapshot} wrapping. + */ + public Reflection(Query q,Goal g) { + snap = new Snapshot( q ); + snap.addObserver( this ); + goal = g; + } + + /** + * Implements {@link Observer#update} by reviewing the query + * and generating percepts for new bindings. + */ + synchronized public void update(Observable x,Object y) { + try { + snap.reset(); + Vector/**/ refs = snap.getRefs( new Vector/**/() ); + if ( mode >= 0 ) { + // Process added bindings + while ( snap.next() ) { + Data d = new Data(); + d.set( refs ); + d.setValue( REFLECTION_EVENT, "added" ); + perceive( snap.query, d, goal ); + } + } + if ( mode <= 0 ) { + // Process lost bindings + for ( Iterator/**/ i = + snap.lost.iterator(); i.hasNext(); ) { + Vector v = (Vector) i.next(); + Data d = new Data(); + Ref.bind( refs, v ); + d.set( refs ); + d.setValue( REFLECTION_EVENT, "lost" ); + perceive( snap.query, d, goal ); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Cancels the observation of query sources. + */ + public void cancel() { + snap.deleteObserver( this ); + } + } + + /** + * Keeps the current collection of queries, which are contained in + * {@link Reflection} objects + */ + public IdentityHashMap/**/ reflections = + new IdentityHashMap/**/(); + + /** + * Adds a {@link Reflection} that uses the given {@link Query}. + */ + public void addQuery(Query q) { + addQuery( q, (String) null ); + } + + /** + * Adds a {@link Reflection} query using the given {@link + * Goal#name}. + */ + public void addQuery(Query q,String goal) { + addQuery( q, goal, 1 ); + } + + /** + * Adds a {@link Reflection} query using the given {@link + * Goal#name}. + */ + public void addQuery(Query q,String goal,int m) { + Reflection r = (Reflection) reflections.get( q ); + if ( r == null ) { + r = new Reflection( q, goal ); + r.mode = m; + reflections.put( q, r ); + r.update( null, null ); + } else if ( r.mode != m ) { + r.mode = 0; + r.update( null, null ); + } + } + + /** + * Adds a reflection query using the given Reflection goal. + */ + public void addQuery(Query q,Goal goal) { + addQuery( q, goal, 1 ); + } + + /** + * Adds a reflection query using the given Reflection goal. + */ + public void addQuery(Query q,Goal goal,int m) { + Reflection r = (Reflection) reflections.get( q ); + if ( r == null ) { + r = new Reflection( q, goal ); + r.mode = m; + reflections.put( q, r ); + r.update( null, null ); + } else if ( r.mode != m ) { + r.mode = 0; + r.update( null, null ); + } + } + + /** + * Removes the {@link Reflection} that uses the given {@link + * Query}. + */ + public void removeQuery(Query q) { + Reflection r = (Reflection) reflections.remove( q ); + if ( r != null ) { + r.cancel(); + } + } + + /** + * Returns a String representation of this object. + */ + public String toString() { + StringBuilder s = new StringBuilder(); + s.append( "Reflector(" + performer + "," + + handler + ",\"" + todo_group + "\"):" ); + for ( Iterator/**/ ri = reflections.keySet().iterator(); + ri.hasNext(); ) { + Query q = (Query) ri.next(); + s.append( "\n query: " ); + s.append( q.toString() ); + } + return s.toString(); + } +} diff --git a/com/intendico/gorite/addon/RemotePerforming.java b/com/intendico/gorite/addon/RemotePerforming.java new file mode 100644 index 0000000..8d6888e --- /dev/null +++ b/com/intendico/gorite/addon/RemotePerforming.java @@ -0,0 +1,198 @@ +/********************************************************************* +Copyright 2013, Ralph Ronnquist. +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.data.Ref; +import com.intendico.gorite.Capability; +import com.intendico.gorite.Data; +import com.intendico.gorite.Goal.States; +import com.intendico.gorite.Goal; +import java.util.Vector; + +/** + * This capability wraps goals to be performed by a remoteHCyl2 performer, + * i.e., one that exists in a different process. A RemotePerforming + * instance is created towards a remoteHCyl2 performer, and then it manages + * goal transfer to that performer. To this end, the capability is set + * up with a model of the remoteHCyl2 goal handling. This is used for + * filtering which goals to forward, and what to do with data to and + * from those goals. The following is an illustration: + *

+ *     addCapability( new RemotePerforming( my_remote_connector ) {{
+ *         addGoal( new RemoteGoal(
+ *            "review paper",
+ *            String [] { "submission" },
+ *            String [] { "review" },
+ *         ) );
+ *     }} );
+ * 
+ * }
+ * 
+ * Note that my_remote_connector is an implementation of the {@link + * RemotePerforming.Connector} interface, to represent the "logical + * connection" to the remoteHCyl2 performer. When performing a goal, the + * connector is expected to create a {@link + * RemotePerforming.Connection} object to represent a particular + * realisation of the logical connection for the particular goal + * execution. + *

+ * Further, we note that RemotePerforming is a {@link Capability}, and + * as such, it may have other goals and sub capabilities than the + * {@link RemotePerforming.RemoteGoal}. Also, once a RemoteGoal is + * created within a RemotePerforming capability, it may in fact be added + * to another capability than the one it is created within. However, + * doing so usually causes more grief than benefits. + */ +public class RemotePerforming extends Capability { + + /** + * This interface is to be implemented by the remoteHCyl2 channel + * management sub system. + */ + public interface Connector { + /** + * This method should trigger remoteHCyl2 goal execution. It + * creates a {@link Connection} object to represent the + * particular goal execution. Thereafter goal execution will + * be monitored via the {@link Connection} methods. + */ + public Connection perform( + String goal,String head,Vector ins,Vector outs); + } + + /** + * This interface is implemented by the connectivity sub system, + * to represent a particular goal execution connection. + */ + public interface Connection { + /** + * This method is used repeatedly in order to discover the + * status of the remote goal execution. + */ + public States check() throws Throwable; + + /** + * This method will be called if the goal execution is to be + * cancelled form the triggering side. + */ + public void cancel(); + + public Vector results(); + } + + /** + * Holds the remote end connector. + */ + public Connector connector; + + /** + * Constructor with a {@link Connector} implementation to be used + * for interacting with the remote end. + */ + public RemotePerforming(Connector c) { + connector = c; + } + + public RemoteGoal create( String name,String [] i,String [] o ) { + return new RemoteGoal( name, i, o ); + } + + /** + * This class is a wrapper for goals that are to be performed by + * the remote side. + */ + public class RemoteGoal extends Goal { + + /** + * Keeps track of names of goal inputs. + */ + public String [] ins; + + /** + * Keeps track of goal outputs. + */ + public String [] outs; + + /** + * Constructor with goal name, input names and output names. + */ + public RemoteGoal(String name,String [] i,String [] o) + { + super( name ); + ins = i; + outs = o; + } + + /** + * Overrides {@link Goal#instantiate} to create a {@link + * RemoteInstance} to manage remote goal execution. + */ + public Instance instantiate(String h,Data d) + { + return new RemoteInstance( h, d ); + } + + + public class RemoteInstance extends Instance { + + public RemoteInstance(String h,Data d) + { + super( h ); + } + + /** + * The identification of the remote end connection. + */ + public Connection connection = null; + + /** + * Keep track of output Ref objects + */ + public Vector output; + + /** + * Manages remote goal execution. On first call, the + * remote goal is triggered, while STOPPED is returned, + * and on subsequent calls, the {@link Connection} is + * queried about completion. + */ + public States action(String head,Data d) { + + if ( connection == null ) { + Vector input = Ref.create( ins ); + d.get( input, true ); + output = Ref.create( outs ); + connection = connector.perform( + head, getGoalName(), input, output ); + return States.STOPPED; + } + try { + States s = connection.check(); + if ( s == States.PASSED || s == States.FAILED ) { + output = connection.results(); + //System.err.println( output ); + d.set( output ); + } + return s; + } catch (Throwable t) { + t.printStackTrace(); + cancel(); + return States.CANCEL; + } + } + + /** + * Overrides {@link Instance#cancel} to forward + * cancellation to the connection (if any) + */ + public void cancel() + { + if ( connection != null ) + connection.cancel(); + } + } + } +} + diff --git a/com/intendico/gorite/addon/SubGoal.java b/com/intendico/gorite/addon/SubGoal.java new file mode 100644 index 0000000..79062da --- /dev/null +++ b/com/intendico/gorite/addon/SubGoal.java @@ -0,0 +1,289 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.gorite.*; + +/** + * This is a utility class for managing the dynamic state of a sub + * goal execution within a (task) Goal's execute method. The execute + * method that needs to perform sub goals (typically BDIGoal goals) + * will need to obtain its SubGoal object on entry, and then dispatch + * to the execution point appropriate for the SubGoal progress count. + * + *

Note that when performing sub goals, it is also appropriate to + * re-implement the {@link Goal#cancelled} method for propagating + * intention branch cancellation. + * + *

The following is an illustration of using SubGoal. + *

+ * static Goal goal1 = new BDIGoal( "the first sub goal" );
+ * static Goal goal2 = new BDIGoal( "the second sub goal" );
+ * static Goal goal3 = new BDIGoal( "the third sub goal" );
+ *
+ * addPlan( new Plan( "use SubGoal for something" ) {
+ *
+ *     // Propagate intention branch cancellation to sub goal, if any
+ *     public void cancelled(Goal.Instance which) {
+ *         SubGoal.cancel( which );
+ *     }
+ *
+ *     // Plan body in Java, with sub goals:
+ *     //
+ *     // + Perform goal1 in state 0 with goal1;
+ *     //
+ *     // + Perform goal2 repeatedly through states 1-11, but break
+ *     //   the loop and warp to state 12 on a LoopEndException;
+ *     //   (with a trace output to stderr)
+ *     //
+ *     // + Perform goal3 in state 12, and swap its PASSED and FAILED
+ *     //   result to be our result
+ *
+ *     public States execute(Data data) {
+ *         SubGoal sub = SubGoal.get( data );
+ *         if ( sub.inProgress( goal1, 0 ) )
+ *             return sub.state();
+ *         while ( sub.progress() < 12 ) {
+ *             try {
+ *                 if ( sub.inProgress( goal2 ) )
+ *                     return sub.state();
+ *             } catch (LoopEndException e) {
+ *                 System.err.println( "END at state " + sub.progress() );
+ *                 sub.count = 12;
+ *                 break;
+ *             }
+ *         }
+ *         boolean pass = false;
+ *         if ( sub.inProgress( goal3, 12 ) ) {
+ *             if ( sub.state() != FAILED ) {
+ *                 return sub.state();
+ *             }
+ *             pass = true;
+ *         }
+ *         sub.done(); 
+ *         return pass? PASSED : FAILED;
+ *     }
+ * } );
+ * 
+ * + *

Note that an intention branch may only have one active SubGoal + * object at a time, and you have to go out of your way in coding to + * juggle multiple concurrently active SubGoal objects. If you want + * parallel execution, then you should execute an approprate + * ParallelGoal rather than trying to implement your own parallel goal + * execution management. + * + *

The sub goal execution may throw an exception, where it in + * particular may be a {@link LoopEndException} or a {@link + * ParallelEndException}. In most cases, the execute method would + * ignore this, and thus allow them to be propagated up through the + * invocation. In the example above, the goal2 invocation, being a + * loop, catch {@link LoopEndException}, and let that break the loop + * sucessfully. + */ +public class SubGoal { + + /** + * This is a convenience field that an execution method may use + * for marking its progress through its sub goal invocations. Its + * value is automatically appended to the trace head for the next + * sub goal, and it is incremented when a sub goal execution + * terminates. + * @see #progress() + */ + public int count; + + /** + * This is an internal cache of the {@link + * com.intendico.gorite.Goal.Instance Goal.Instance} in progress, + * if any. It is publically accessible, but the execution method + * should refrain from changing it. + * @see #inProgress(Goal,int) + * @see #inProgress(Goal) + */ + public Goal.Instance current; + + /** + * This is an internal cache of the most recent return value of + * the most recent sub goal invocation. It is publically + * accessible, but the execution method should refrain from + * changing it. When the sub goal invocation is incomplete + * (i.e. either BLOCKED or STOPPED), the execution method should + * return this value. + * @see #inProgress(Goal,int) + * @see #inProgress(Goal) + */ + public Goal.States result; + + /** + * This is a handle to the Data object for the executing + * intention. It is publically accessible, but the execution + * method should refrain from changing it. + */ + public Data data; + + /** + * Utility method that looks up a current SubGoal object in the + * given Data, or creates a new one associated with the current + * Data. To do so, it uses the Data element named by {@link + * Data#thread_name}, which is unique within the intention for the + * intention branch. + */ + public static SubGoal get(Data data) { + SubGoal sub = tryGet( data ); + return ( sub == null )? new SubGoal( data ) : sub; + } + + /** + * Utility method that looks up a current SubGoal object in the + * given Data and returns it, or null, if none exists. You'd use + * this in combination with a SubGoal extension class, e.g. for + * carrying your own progress details. Thus, instead of + * using {@link #get} to find the sub goal manager, you would have + * a code snippet like the following: + *

+     * SubGoal sub = SubGoal.tryGet( data );
+     * if ( sub == null ) {
+     *     sub = new MySubGoal( data );
+     * }
+     * 
+ */ + public static SubGoal tryGet(Data data) { + return (SubGoal) data.getValue( data.thread_name ); + } + + /** + * Constructor. Normally SubGoal objects are only created + * indirectly, either via the {@link #get} method, or via + * super calls in extension classes. It always end up in + * this constructor, which associates the newly created object + * with the given {@link Data}, and puts itself as the Data + * element named by the current {@link Data#thread_name}, which is + * unique for the intention branch. + */ + public SubGoal(Data d) { + data = d; + data.setValue( data.thread_name, this ); + } + + /** + * Clean up by forgetting the data element named by the current + * {@link Data#thread_name}. + */ + public void done() { + data.forget( data.thread_name ); + } + + /** + * This method is used by the execution method to trigger and + * pursue a sub goal execution. The method returns true if the + * sub goal invocation returns without passing (i.e., returns + * STOPPED, BLOCKED or FAILED), and it returns false when the sub + * goal invocation returns PASSED. Also, when the sub goal returns + * PASSED or FAILED, the count gets incremented before this method + * returns. + */ + public boolean inProgress(Goal goal) + throws LoopEndException, ParallelEndException { + if ( current == null ) { + String head = data.thread_name; + int i = head.lastIndexOf( "\"" ); + if ( i > 0 ) + i = head.lastIndexOf( "\"", i - 1 ); + i -= 1; + if ( i > 0 ) + head = head.substring( 0, i ); + current = goal.instantiate( head + ":" + count, data ); + } + result = current.perform( data ); + if ( result == Goal.States.PASSED || result == Goal.States.FAILED ) { + current = null; + count += 1; + } + return result != Goal.States.PASSED; + } + + /** + * This method verifies that the progress count is as given, + * before pursuing sub goal execution as per {@link + * #inProgress(Goal)}. Otherwise the method returns false; + */ + public boolean inProgress(Goal goal,int at) + throws LoopEndException, ParallelEndException { + return count == at? inProgress( goal ) : false; + } + + /** + * Utility method to cancel any ongoing sub goal execution, and + * force the count to a given state. If there is no sub goal in + * progress, the method returns false. Otherwise, that sub goal + * execution is cancelled and forgotten, {@link #result} is set to + * CANCEL, and true is returned. + */ + public boolean cancel(int next) { + count = next; + if ( current == null ) + return false; + current.cancel(); + current = null; + result = Goal.States.CANCEL; + return true; + } + + /** + * This method cancels and forgets any sub goal execution in + * progress for the given {@link + * com.intendico.gorite.Goal.Instance Goal.Instance}. + */ + public static void cancel(Goal.Instance which) { + Data data = which.data; + String old = data.setThreadName( which.thread_name ); + SubGoal sub = (SubGoal) data.getValue( which.thread_name ); + if ( sub != null ) { + sub.cancel( sub.count + 1 ); + sub.done(); + } + data.setThreadName( old ); + } + + /** + * Utility method that returns the current progress count. + */ + public int progress() { + return count; + } + + /** + * Utility method that returns the cached sub intention state. + */ + public Goal.States state() { + return result; + } + + /** + * Utility method that tells whether the current state has been + * activated or not; i.e., whether the sub intention for the + * current state has been created or not. + */ + public boolean isActive() { + return current != null; + } + +} diff --git a/com/intendico/gorite/addon/TimeTrigger.java b/com/intendico/gorite/addon/TimeTrigger.java new file mode 100644 index 0000000..b0b5612 --- /dev/null +++ b/com/intendico/gorite/addon/TimeTrigger.java @@ -0,0 +1,98 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.gorite.Data; +import java.util.Observable; +import java.util.Timer; +import java.util.TimerTask; + +/** + * This class defines an Observable that schedules itself for issuing + * a notfication to Observers at a future time. + */ +public class TimeTrigger extends Observable { + + /** + * Utility method to establish a TimeTrigger as a {@link Data} + * element value, and use as execution trigger after the given + * delay. The indicated {@link com.intendico.gorite.Data.Element + * Data.Element} should be unset initially, to be set by this + * method to a TimeTrigger for the given delay. Upon subsequent + * calls, the TimeTrigger is rescheduled until its deadline has + * passed, at which time the given {@link + * com.intendico.gorite.Data.Element Data.Element} is forgotten. + * @return true if a trigger has been set and false otherwise + */ + public static boolean isPending(Data data,String element,long delay) { + TimeTrigger tt = (TimeTrigger) data.getValue( element ); + if ( tt == null ) { + tt = new TimeTrigger( delay ); + data.setValue( element, tt ); + data.setTrigger( tt ); + } + if ( tt.reschedule() ) + return true; + data.forget( element ); + return false; + } + + /** + * A global {@link Timer} for scheduling future events on. + */ + public static Timer timer = new Timer( true ); + + /** + * The deadline time for this TimeTrigger. + */ + public long deadline; + + /** + * Constructor for a given future time. + */ + public TimeTrigger(long delay) { + deadline = System.currentTimeMillis() + delay; + } + + /** + * Reset for triggering at a new future time. + */ + public void reset(long delay) { + deadline = System.currentTimeMillis() + delay; + } + + /** + * This method tests whether the trigger time is reached or not, + * and if not, it sets up a {@link TimerTask} for notifying + * observers when the trigger time is reached. + */ + public boolean reschedule() { + long delay = deadline - System.currentTimeMillis(); + if ( delay <= 0 ) + return false; + timer.schedule( new TimerTask() { + public void run() { + setChanged(); + notifyObservers( this ); + } + }, delay ); + return true; + } +} diff --git a/com/intendico/gorite/addon/TodoGroupParallel.java b/com/intendico/gorite/addon/TodoGroupParallel.java new file mode 100644 index 0000000..497f406 --- /dev/null +++ b/com/intendico/gorite/addon/TodoGroupParallel.java @@ -0,0 +1,44 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.gorite.*; +import com.intendico.gorite.Performer.TodoGroup; +import java.util.Vector; + +/** + * This plan is a {@link TodoGroup} meta goal handler that combines + * the "round-robin" with "skip blocked" management functions. + * @see TodoGroupRoundRobin + * @see TodoGroupSkipBlocked + */ +public class TodoGroupParallel extends Goal { + + /** + * Constructor for providing {@link TodoGroup} management through + * the named meta goal. + */ + public TodoGroupParallel(String name) { + super( name, new Goal [] { + new TodoGroupRoundRobin( "round robin" ), + new TodoGroupSkipBlocked( "skip blocked" ) + } ); + } +} diff --git a/com/intendico/gorite/addon/TodoGroupRoundRobin.java b/com/intendico/gorite/addon/TodoGroupRoundRobin.java new file mode 100644 index 0000000..647dd41 --- /dev/null +++ b/com/intendico/gorite/addon/TodoGroupRoundRobin.java @@ -0,0 +1,75 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.gorite.*; +import com.intendico.gorite.Performer.TodoGroup; +import java.util.Vector; + +/** + * This plan is a {@link TodoGroup} meta goal handler to cycle through + * the stacked intentions, by putting the top one last, except when it + * is newly added. The actual goal name for the meta goal is given at + * construction time, and unless it is the default {@link + * Performer#TODOGROUP_META_GOAL}, the actual {@link TodoGroup} + * instances to be managed by this goal need to be set up explicitly + * using {@link Performer#addTodoGroup}. + * + *

Usage example: + *

+ * new Performer( "example" ) {
+ *     addGoal( new TodoGroupRoundRobin( "round robin todogroup meta goal" ) );
+ *     addTodoGroup( "example", "round robin todogroup meta goal" );
+ * }
+ * 
+ * + *

Another usage example, to make it the default todogroup meta goal: + *

+ * new Performer( "example" ) {
+ *     addGoal( new TodoGroupRoundRobin( TODOGROUP_META_GOAL ) );
+ * }
+ * 
+ */ +public class TodoGroupRoundRobin extends Goal { + + /** + * Constructor for providing {@link TodoGroup} management through + * the named meta goal. + */ + public TodoGroupRoundRobin(String name) { + super( name ); + } + + /** + * Overrides {@link Goal#execute} to provide the meta goal + * implementation. This expects a {@link Data} object with + * elements named by {@link Performer#TODOGROUP}, "coming" and + * "top". The implementation cycles the todogroup unless the "top" + * intention is in the "coming" vector. + */ + public States execute(Data d) { + TodoGroup todogroup = (TodoGroup) d.getValue( Performer.TODOGROUP ); + Vector coming = (Vector) d.getValue( "coming" ); + Goal.Instance top = (Goal.Instance) d.getValue( "top" ); + if ( ! coming.contains( top ) ) + todogroup.rotate(); + return States.PASSED; + } +} diff --git a/com/intendico/gorite/addon/TodoGroupSkipBlocked.java b/com/intendico/gorite/addon/TodoGroupSkipBlocked.java new file mode 100644 index 0000000..520aa86 --- /dev/null +++ b/com/intendico/gorite/addon/TodoGroupSkipBlocked.java @@ -0,0 +1,84 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.gorite.addon; + +import com.intendico.gorite.*; +import com.intendico.gorite.Performer.TodoGroup; +import java.util.Vector; + +/** + * This plan is a {@link TodoGroup} meta goal handler to maintain + * focus to "the first" running intention, i.e., to avoid that the + * whole group is blocked when the top intention gets + * blocked. Instead, when the top intention gets blocked, the + * TodoGroup stack is scanned for its top-most non-blocked intention, + * which then is promoted. The actual goal name for the meta goal is + * given at construction time, and unless it is the default {@link + * Performer#TODOGROUP_META_GOAL}, the actual {@link TodoGroup} + * instances to be managed by this goal need to be set up explicitly + * using {@link Performer#addTodoGroup}. + * + *

Usage example: + *

+ * new Performer( "example" ) {
+ *     addGoal( new TodoGroupSkipBlocked( "skip blocked todogroup meta goal" ) );
+ *     addTodoGroup( "example", "skip blocked todogroup meta goal" );
+ * }
+ * 
+ * + *

Another usage example, to make it the default todogroup meta + * goal: + *

+ * new Performer( "example" ) {
+ *     addGoal( new TodoGroupSkipBlocked( TODOGROUP_META_GOAL ) );
+ * }
+ * 
+ */ +public class TodoGroupSkipBlocked extends Goal { + + /** + * Constructor for providing {@link TodoGroup} management through + * the named meta goal. + */ + public TodoGroupSkipBlocked(String name) { + super( name ); + } + + /** + * Overrides {@link Goal#execute} to provide the meta goal + * implementation. This expects a {@link Data} object with the + * {@link TodoGroup} concerned as the element named by {@link + * Performer#TODOGROUP}. The implementation promotes the first + * non-blocked intention, if any. Returns PASSED if there is a + * non-blocked intention, an FAILED otherwise. + */ + public States execute(Data d) { + TodoGroup tg = (TodoGroup) d.getValue( Performer.TODOGROUP ); + Vector/**/ s = tg.stack; + for ( int i = 0; i < s.size(); i++ ) { + if ( ((Instance) s.get( i )).state == States.STOPPED ) { + tg.promote( i ); + return States.PASSED; + } + } + return States.FAILED; + } + +} diff --git a/com/intendico/gorite/addon/remote/Connection.java b/com/intendico/gorite/addon/remote/Connection.java new file mode 100644 index 0000000..afc60e7 --- /dev/null +++ b/com/intendico/gorite/addon/remote/Connection.java @@ -0,0 +1,33 @@ +/********************************************************************* +Copyright 2013, Ralph Ronnquist. +**********************************************************************/ + +package com.intendico.gorite.addon.remote; + +import com.intendico.gorite.addon.*; +import com.intendico.data.Ref; +import com.intendico.gorite.Capability; +import com.intendico.gorite.Data; +import com.intendico.gorite.Goal.States; +import com.intendico.gorite.Goal; +import java.util.Vector; + + /** + * This interface is implemented by the connectivity sub system, + * to represent a particular goal execution connection. + */ + public interface Connection { + /** + * This method is used repeatedly in order to discover the + * status of the remote goal execution. + */ + public States check() throws Throwable; + + /** + * This method will be called if the goal execution is to be + * cancelled form the triggering side. + */ + public void cancel(); + + public Vector results(); + } diff --git a/com/intendico/gorite/addon/remote/Connector.java b/com/intendico/gorite/addon/remote/Connector.java new file mode 100644 index 0000000..62016a8 --- /dev/null +++ b/com/intendico/gorite/addon/remote/Connector.java @@ -0,0 +1,28 @@ +/********************************************************************* +Copyright 2013, Ralph Ronnquist. +**********************************************************************/ + +package com.intendico.gorite.addon.remote; + +import com.intendico.gorite.addon.*; +import com.intendico.data.Ref; +import com.intendico.gorite.Capability; +import com.intendico.gorite.Data; +import com.intendico.gorite.Goal.States; +import com.intendico.gorite.Goal; +import java.util.Vector; + + /** + * This interface is to be implemented by the remoteHCyl2 channel + * management sub system. + */ + public interface Connector { + /** + * This method should trigger remoteHCyl2 goal execution. It + * creates a {@link Connection} object to represent the + * particular goal execution. Thereafter goal execution will + * be monitored via the {@link Connection} methods. + */ + public Connection perform( + String goal,String head,Vector ins,Vector outs); + } \ No newline at end of file diff --git a/com/intendico/gorite/addon/remote/RemotePerforming.java b/com/intendico/gorite/addon/remote/RemotePerforming.java new file mode 100644 index 0000000..f9b54c2 --- /dev/null +++ b/com/intendico/gorite/addon/remote/RemotePerforming.java @@ -0,0 +1,164 @@ +/********************************************************************* +Copyright 2013, Ralph Ronnquist. +**********************************************************************/ + +package com.intendico.gorite.addon.remote; + +import com.intendico.data.Ref; +import com.intendico.gorite.Capability; +import com.intendico.gorite.Data; +import com.intendico.gorite.Goal.States; +import com.intendico.gorite.Goal; +import java.util.Vector; + +/** + * This capability wraps goals to be performed by a remote performer, + * i.e., one that exists in a different process. A RemotePerforming + * instance is created towards a remote performer, and then it manages + * goal transfer to that performer. To this end, the capability is set + * up with a model of the remote goal handling. This is used for + * filtering which goals to forward, and what to do with data to and + * from those goals. The following is an illustration: + *
+ *     addCapability( new RemotePerforming( my_remote_connector ) {{
+ *         addGoal( new RemoteGoal(
+ *            "review paper",
+ *            String [] { "submission" },
+ *            String [] { "review" },
+ *         ) );
+ *     }} );
+ * 
+ * }
+ * 
+ * Note that my_remote_connector is an implementation of the {@link + * RemotePerforming.Connector} interface, to represent the "logical + * connection" to the remote performer. When performing a goal, the + * connector is expected to create a {@link + * RemotePerforming.Connection} object to represent a particular + * realisation of the logical connection for the particular goal + * execution. + *

+ * Further, we note that RemotePerforming is a {@link Capability}, and + * as such, it may have other goals and sub capabilities than the + * {@link RemotePerforming.RemoteGoal}. Also, once a RemoteGoal is + * created within a RemotePerforming capability, it may in fact be added + * to another capability than the one it is created within. However, + * doing so usually causes more grief than benefits. + */ +public class RemotePerforming extends Capability { + + /** + * Holds the remote end connector. + */ + public Connector connector; + + /** + * Constructor with a {@link Connector} implementation to be used + * for interacting with the remote end. + */ + public RemotePerforming(Connector c) { + connector = c; + } + + // added so that a remote goal could be created non-anonymously + public RemoteGoal create( String name,String [] i,String [] o ) { + return new RemoteGoal( name, i, o ); + } + + /** + * This class is a wrapper for goals that are to be performed by + * the remote side. + */ + public class RemoteGoal extends Goal { + + /** + * Keeps track of names of goal inputs. + */ + public String [] ins; + + /** + * Keeps track of goal outputs. + */ + public String [] outs; + + /** + * Constructor with goal name, input names and output names. + */ + public RemoteGoal(String name,String [] i,String [] o) + { + super( name ); + ins = i; + outs = o; + } + + /** + * Overrides {@link Goal#instantiate} to create a {@link + * RemoteInstance} to manage remote goal execution. + */ + public Instance instantiate(String h,Data d) + { + return new RemoteInstance( h, d ); + } + + + public class RemoteInstance extends Instance { + + public RemoteInstance(String h,Data d) + { + super( h ); + } + + /** + * The identification of the remote end connection. + */ + public Connection connection = null; + + /** + * Keep track of output Ref objects + */ + public Vector output; + + /** + * Manages remote goal execution. On first call, the + * remote goal is triggered, while STOPPED is returned, + * and on subsequent calls, the {@link Connection} is + * queried about completion. + */ + public States action(String head,Data d) { + + if ( connection == null ) { + Vector input = Ref.create( ins ); + d.get( input, true ); + output = Ref.create( outs ); + connection = connector.perform( + head, getGoalName(), input, output ); + return States.STOPPED; + } + try { + States s = connection.check(); + if ( s == States.PASSED || s == States.FAILED ) { + output = connection.results(); + //System.err.println( output ); + d.set( output ); + } + return s; + } catch (Throwable t) { + t.printStackTrace(); + cancel(); + return States.CANCEL; + } + } + + /** + * Overrides {@link Instance#cancel} to forward + * cancellation to the connection (if any) + */ + public void cancel() + { + if ( connection != null ) + connection.cancel(); + } + } + } +} + diff --git a/com/intendico/sdp/BNFGrammar.java b/com/intendico/sdp/BNFGrammar.java new file mode 100644 index 0000000..32e618b --- /dev/null +++ b/com/intendico/sdp/BNFGrammar.java @@ -0,0 +1,738 @@ +/********************************************************************* +Copyright 2012, Ralph Ronnquist. + +This file is part of GORITE. + +GORITE is free software: you can redistribute it and/or modify it +under the terms of the Lesser GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GORITE is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the Lesser GNU General Public +License along with GORITE. If not, see . +**********************************************************************/ + +package com.intendico.sdp; + +import java.util.Vector; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.TreeSet; + +/** + * The BNFGrammar class is a base class for grammars that make use of + * the LLBNF translator for defining their syntax. + */ +public class BNFGrammar extends Grammar { + + /** + * Utility method to scan for blank or comment. + */ + public int findBlankOrComment(CharSequence s,int from) { + int end = s.length(); + for ( ; from < end; from++ ) { + if ( Character.isWhitespace( s.charAt( from ) ) ) + return from; + if ( s.charAt( from ) != '/' ) + continue; + if ( from + 1 >= end ) + return end; + if ( s.charAt( from + 1 ) == '*' ) + return from; + if ( s.charAt( from + 1 ) == '/' ) + return from; + } + return end; + } + + // + // Predefined lexical scanners. + // + + /** + * A Nothing is a lexical token that accepts. + */ + public class Nothing extends Lexical { + + /** + * Constructor. + */ + public Nothing(String r) { + super( r ); + } + + /** + * Scanning nothing. + * Requires nothing. + */ + public int scan(CharSequence s,int from) { + return from; + } + } + + /** + * An AnySymbol is a lexical token between blanks or comments. + */ + public class AnySymbol extends Lexical { + + /** + * Constructor. + */ + public AnySymbol(String r) { + super( r ); + } + + /** + * Scanning anything unto next blank or comment or end. + * Requires something. + */ + public int scan(CharSequence s,int from) { + int to = findBlankOrComment( s, from ); + return from == to? -1 : to ; + } + } + + /** + * An End is a lexical token for nothing after blanks or comments. + */ + public class End extends Lexical { + + /** + * Constructor. + */ + public End(String r) { + super( r ); + } + + /** + * Requires there to remain nothing. + */ + public int scan(CharSequence s,int from) { + return from == s.length()? from : -1 ; + } + } + + /** + * An Identifier is a lexical token of Java identifier characters. + */ + public class Identifier extends Lexical { + + /** + * Constructor. + */ + public Identifier(String r) { + super( r ); + } + + /** + * Scanning a Java identifier. + */ + public int scan(CharSequence s,int from) { + int end = s.length(); + if ( from == end ) + return -1; + + if ( ! Character.isJavaIdentifierStart( s.charAt( from++ ) ) ) + return -1; + + for ( ; from < end; from++ ) { + if ( ! Character.isJavaIdentifierPart( s.charAt( from ) ) ) + return from; + } + return end; + } + } + + /** + * A PackedChars is a lexical token consisting of a string of + * characters of a given collection, in any order. + */ + public class PackedChars extends Lexical { + + /** + * The accepted characters. + */ + public String characters; + + /** + * Constructor. + */ + public PackedChars(String r,String set) { + super( r ); + characters = set; + } + + /** + * Scanning a PackedChars. + */ + public int scan(CharSequence s,int from) { + int end = s.length(); + if ( from == end ) + return -1; + + if ( characters.indexOf( s.charAt( from++ ) ) < 0 ) + return -1; + + for ( ; from < end; from++ ) { + if ( characters.indexOf( s.charAt( from ) ) < 0 ) + return from; + } + return end; + } + } + + /** + * A Number is a lexical token of digits. Optionally preceded by a + * single "-". + */ + public class Number extends Lexical { + + /** + * Constructor. + */ + public Number(String r) { + super( r ); + } + + /** + * Scanning an integer, with an optional preceding "-". + */ + public int scan(CharSequence s,int from) { + int end = s.length(); + + if ( from == end ) + return -1; + + if ( inSet( s, from, end, "+-" ) ) + from++; + + int h = scanSet( s, from, end, "0123456789" ); + return h == 0? -1 : from + h; + } + } + + /** + * A Number is a lexical token of digits. Optionally preceded by a + * single "-". + */ + public class RealNumber extends Lexical { + + /** + * Constructor. + */ + public RealNumber(String r) { + super( r ); + } + + /** + * Scanning a real number, with an optional preceding "-" or '+'. + */ + public int scan(CharSequence s,int from) { + int end = s.length(); + int h = 0; + int f = 0; + + if ( from == end ) + return -1; + + if ( inSet( s, from, end, "+-" ) ) { + from++; + if ( from == end ) + return -1; + h = scanSet( s, from, end, "0123456789" ); + } else { + if ( s.charAt( from++ ) == '0' ) { + // possible lead-in for 0xh* or otherwise octal? + if ( from == end ) + return end; + if ( s.charAt( from++ ) == 'x' ) { + // found 0x lead-in + h = scanSet( s, from+2, end, "01234567abcdefABCDEF" ); + if ( h == 0 ) + return -1; + return from + h; + } + from--; + return from + scanSet( s, from, end, "01234567" ); + } + from--; // + h = scanSet( s, from, end, "0123456789" ); + if ( h == 0 ) + return -1; + } + + from += h; + if ( from == end ) + return h == 0? -1 : end; + + if ( s.charAt( from++ ) == '.' ) { + // there's a fraction part + f = scanSet( s, from, end, "0123456789" ); + if ( h == 0 && f == 0 ) + return -1; + from += f; + if ( from >= end ) + return end; + if ( inSet( s, from, end, "fF" ) ) + return from + 1; + } else { + // There's no fractional part + from--; + if ( h == 0 ) + return -1; + if ( inSet( s, from, end, "lL" ) ) + return from + 1; + } + + if ( ! inSet( s, from++, end, "eE" ) ) + return from - 1; + + // scan exponent + if ( inSet( s, from, end, "+-" ) ) + from++; + f = scanSet( s, from, end, "0123456789" ); + return f == 0? -1 : from + f; + } + } + + /** + * Scans past the given set of characters, and tells how far it + * has scanned. + */ + static public int scanSet(CharSequence s,int from,int end,String set) { + int start = from; + for ( ; from < end; from++ ) + if ( set.indexOf( s.charAt( from ) ) < 0 ) + return from - start ; + return from - start; + } + + /** + * Tells if the character in s at from is one of those in the set. + */ + static public boolean inSet(CharSequence s,int from,int end,String set) { + return from < end? set.indexOf( s.charAt( from ) ) >= 0 : false; + } + + /** + * Scan a compound character after the '\': + * \ a - letter or any non-digit + * \ ooo - octal + * \ x hh - hexadecimal + * \ u nnnn - digits + */ + static public int compound(CharSequence s,int from,int end) { + if ( from >= end ) + return 0; + switch ( s.charAt( from ) ) { + case 'x': + return 3; + case 'u': + return 5; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + return 3; + default: + return 1; + } + } + + /** + * A String is a lexical token between a mark characters + * (double-quotes by default), but without a new-line in it. + */ + public class QuotedString extends Lexical { + + public char mark; + + /** + * Constructor. + */ + public QuotedString(String r,char m) { + super( r ); + mark = m; + } + + /** + * Utility constructor to use double-quote as mark character. + */ + public QuotedString(String r) { + this( r, '"' ); + } + + /** + * Scanning a String + */ + public int scan(CharSequence s,int from) { + int end = s.length(); + + if ( from == end || s.charAt( from++ ) != mark ) + return -1; + + tryToken( this, from ); // Special marker + + for ( ; from < end; ) { + char c = s.charAt( from++ ); + if ( c == mark ) + return from; + if ( c == '\n' ) + return -1; // bad string + if ( c == '\\' ) { + // lead-in for compound characters. + from += compound( s, from, end ); + if ( from > end ) + return -1; + } + } + // Bad string 2 + return -1; + } + + /** + * The error name for a buggy string. + */ + public String errorName() { + return "terminated and well-formed <" + rule + ">" ; + } + } + + /** + * A QuotedCharacter is a lexical token between a pair of + * single-quotes, with restrictions. + */ + public class QuotedCharacter extends Lexical { + + /** + * Constructor. + */ + public QuotedCharacter(String r) { + super( r ); + } + + /** + * Scanning a String + */ + public int scan(CharSequence s,int from) { + int end = s.length(); + + if ( from == end || s.charAt( from++ ) != '\'' ) + return -1; + + tryToken( this, from ); // Special marker + + if ( from == end ) + return -1; + + switch ( s.charAt( from++ ) ) { + case '\'': + return -1; + case '\\': + from += compound( s, from, end ); + break; + default: + from += 1; + break; + } + if ( from >= end ) + return -1; + if ( s.charAt( from++ ) != '\'' ) + return -1; + return from; + } + + /** + * The error name for a buggy string. + */ + public String errorName() { + return "terminated and well-formed <" + rule + ">" ; + } + } + + + /** + * A lexical scanner to match multi-word phrases from a given + * collection. It greedily consumes its longest match. + */ + public class PhraseSet extends Lexical { + + /** + * Matching phrases. This contains the successive heads of the + * matching phrases, mapped to 0, 1, or 2. 0 means that the + * phrase ends there, 1 means it must continue, and 2 means it + * may end or continue. The scanner always consumes as much as + * possible. + */ + public TreeSet/**/ phrases = new TreeSet/**/(); + + /** + * Cache of defining phrases. + */ + public Vector/**/ definition = new Vector/**/(); + + /** + * Constructor for a given name and collection of phrases. + */ + public PhraseSet(String name,Vector/**/ set) { + super( name ); + addSet( set ); + } + + /** + * Utility method to add a collection of phrases. + */ + public void addSet(Vector/**/ set) { + for ( Iterator si = set.iterator(); si.hasNext(); ) { + String phrase = (String) si.next(); + definition.add( phrase ); + phrases.add( phrase.toLowerCase() ); + } + } + + /** + * Returns index to first character following a matching + * token. + */ + public int scan(CharSequence input,int from) { + CharSequence phrase = input.subSequence( from, input.length() ); + if ( from >= input.length() ) + return -1; + char first = phrase.charAt( 0 ); + for ( Iterator/**/ pi = + phrases.headSet( phrase, true ).descendingIterator(); + pi.hasNext(); ) { + String key = (String) pi.next(); + if ( key.charAt( 0 ) != first ) { + System.err.println( "failed: " + key ); + return -1; + } + if ( startsWith( phrase, 0, key ) ) { + return from + key.length(); + } + } + return -1; + } + + /** + * Returns this collection as a defintion clause: + * name = { .... } + */ + public String toString() { + StringBuilder s = new StringBuilder(); + s.append( rule ); + s.append( " = {" ); + String separator = "\n "; + //for ( String phrase : definition ) { + for ( Iterator pi = phrases.iterator(); pi.hasNext(); ) { + String phrase = (String) pi.next(); + s.append( separator ); + s.append( phrase ); + separator = ",\n "; + } + s.append( "\n}\n" ); + return s.toString(); + } + } + + // + // Predefined utility actions + // + + /** + * Utility method to add elements individually from a Vector + * operand, if it begins with the operator. + */ + static public void addArguments(Vector args,Object op,Object opnd) { + if ( opnd instanceof Vector ) { + Vector v = (Vector) opnd; + if ( v.size() > 0 && op.equals( v.get( 0 ) ) ) { + v.remove( 0 ); + args.addAll( v ); + return; + } + } + args.add( opnd ); + } + + /** + * Helper class for infix syntax + */ + public class Infix extends Production { + + /** + * Default Infix action is to move the middle element to + * beginning, if there are three arguments, and otherwise + * re-use the Grammar.Production action. + */ + public Object action(Vector v) throws Exception { + if ( v.size() != 3 ) + return super.action( v ); + + v.add( 0, v.remove( 1 ) ); + return v; + } + } + + /** + * Helper class for postfix syntax + */ + public class Postfix extends Production { + + /** + * Default Postfix action is to move the last element to + * beginning, if there are more than one argument, and otherwise + * re-use the Grammar.Production action. + */ + public Object action(Vector v) throws Exception { + if ( v.size() <= 1 ) + return super.action( v ); + + v.add( 0, v.remove( v.size() - 1 ) ); + return v; + } + } + + /** + * Helper class for associative infix syntax + */ + public class Associative extends Production { + + /** + * Default Associative action is to move the middle element to + * beginning, if there are three arguments, and then flatten + * out the first and last argument of they are vectors with + * the same operator. If there are not three arguments, then + * re-use the Grammar.Production action. + */ + public Object action(Vector v) throws Exception { + if ( v.size() != 3 ) + return super.action( v ); + + Object op = v.get( 1 ); + Vector args = new Vector(); + args.add( op ); + addArguments( args, op, v.get( 0 ) ); + addArguments( args, op, v.get( 2 ) ); + return args; + } + } + + /** + * Helper class for lists + */ + public class Enlist extends Production { + + /** + * Default Enlist action is coalesc arguments into a Vector. + */ + public Object action(Vector v) { + if ( v.size() <= 1 ) + return v; + + Object last = v.remove( v.size() - 1 ); + if ( last instanceof Vector ) { + v.addAll( (Vector) last ); + } else { + v.add( last ); + } + return v; + } + } + + /** + * Helper class for left-associative lists + */ + public class Leftlist extends Production { + + /** + * Default Enlist action is coalesc arguments into a ".." + * headed Vector. + */ + public Object action(Vector v) { + if ( v.size() <= 1 ) + return v; + + Object first = v.remove( 0 ); + + if ( first instanceof Vector ) { + ((Vector) first).addAll( v ); + return first; + } + + v.add( 0, first ); + return v; + } + } + + /** + * Installs a grammar from string rules using LLBNF. + */ + public void install(String rules) { + try { + new LLBNF().addRules( this, rules ); + } catch (Throwable t) { + t.printStackTrace(); + throw new Error( "Grammar failure" ); + } + } + + /** + * Utility method to clip a string after some length + */ + public String clip(String input,int clip) { + int x = input.indexOf( "\n", 0 ); + if ( x > 0 && x < clip ) + return input.substring( 0, x ) + " ..." ; + if ( clip < input.length() ) + return input.substring( 0, clip ) + "..."; + return input; + } + + /** + * Utility method to time parse and process. + */ + public boolean timedProcess(int i,String goal,String input) { + try { + System.out.println( + "(" + i + ") " + goal + ": " + clip( input, 65 ) ); + long time = System.currentTimeMillis(); + ParseTree pr = parse( goal, input ); + long snap = System.currentTimeMillis() - time; + if ( pr != null ) { + //System.out.println( "--> " + pr ); + time = System.currentTimeMillis(); + Object result = process( pr ); + time = System.currentTimeMillis() - time; + System.out.println( "==> " + result ); + System.out.println( + "--------- time = " + snap + "/" + time + " ms" ); + return true; + } else { + System.out.println( lastError( input ) ); + System.out.println( + "--------- time = " + snap + "/-- ms" ); + return false; + } + } catch (Throwable t) { + t.printStackTrace(); + } + return false; + } + + /** + * Utility method to parse and process several tests. + */ + public boolean processAll(String [][] args) { + + for ( int i=0; i. +**********************************************************************/ + +package com.intendico.sdp; + +import java.io.FileReader; +import java.io.IOException; +import java.io.StringWriter; +import java.io.PrintWriter; +import java.text.ParseException; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.Set; +import java.util.Vector; + +/** + * The Grammar class is used for defining a grammar. + */ +public class Grammar { + + // + // Support methods for text processing. + // + + /** + * Utility method to make a string from a named file. + */ + static public String loadFile(String filename) + throws IOException { + FileReader f = new FileReader( filename ); + char [] buffer = new char [ 1024 ]; + int length; + StringBuffer s = new StringBuffer(); + while ( ( length = f.read( buffer ) ) >= 0 ) { + if ( length > 0 ) + s.append( buffer, 0, length ); + } + f.close(); + return s.toString(); + } + + private String strip(String a) { + Class [] c = { BNFGrammar.class, getClass() }; + for ( int i=0; i < c.length; i++ ) { + if ( a.startsWith( c[i].getName() + "$" ) ) + return a.substring( c[i].getName().length() + 1 ); + } + return a; + } + + /** + * Utility method to map a string index into a line number, by + * counting new-lines. + */ + static public int lineNumber(CharSequence s,int index) { + int lineno = 1; + for ( int i = 0; i < index; i++ ) { + if ( s.charAt( i ) == '\n' ) { + lineno++; + } + } + return lineno; + } + + /** + * Utility method that forms a string to mark out the indexed + * position in the input string. + */ + static public String inputPosition(CharSequence input,int index) { + return inputPosition( input, index, 0 ); + } + + /** + * Utility method that forms a string to mark out the indexed + * position and span in the input string. + */ + static public String inputPosition(CharSequence input,int index,int span) { + if ( index < 0 ) + return "" ; + + int from = lastIndexOf( input, "\n", index - 1 ) + 1; + int to = indexOf( input, "\n", index ); + boolean spandots = false; + + if ( to < from ) + to = input.length(); + + int lineno = lineNumber( input, from ); + int at = index - from; + + if ( span < 1 ) + span = 1; + + String line = lineno + ": "; + + if ( span > 43 ) { + span = 40; + spandots = true; + } + + if ( at + span > 60 ) { + // Adjust to fit on a line + int clip = at + span - 55; + at -= clip; + if ( at < 0 ) { + clip -= at; + at = 0; + } + from += clip; + line += "..."; + } + + if ( to - from > 70 ) + to = from + 70; + + at += line.length(); + line += input.subSequence( from, to ).toString(); + if ( at + span > to ) + span = to - at; + + if ( span < 1 ) + span = 1; + + StringBuffer out = new StringBuffer( line ); + out.append( "\n" ); + + for (int i=0; i input.length() ) + return false; + return what.contentEquals( input.subSequence( from, end ) ); + } + + /** + * Returns the last occurence of a given string before a given index. + */ + static public int lastIndexOf(CharSequence s,String what,int from) { + int endw = what.length(); + if ( endw == 0 ) + return from; + int end = s.length(); + int end1 = end - endw; + char first = what.charAt( endw - 1 ); + for ( from = from - 1; from >= endw; from-- ) { + if ( s.charAt( from ) != first ) + continue; + for ( int p = from - 1, k = endw - 2; p >= 0; p--, k-- ) { + if ( k < 0 ) + return from; + if ( s.charAt( p ) != what.charAt( k ) ) + break; + } + } + return -1; + } + + /** + * Returns the first occurence of a given string. + */ + static public int indexOf(CharSequence s,String what,int from) { + int endw = what.length(); + if ( endw == 0 ) + return from; + int end = s.length(); + int end1 = end - endw; + char first = what.charAt( 0 ); + for ( ; from < end1; from++ ) { + if ( s.charAt( from ) != first ) + continue; + for ( int p = from + 1, k = 1;; p++, k++ ) { + if ( k >= endw ) + return from; + if ( s.charAt( p ) != what.charAt( k ) ) + break; + } + } + return -1; + } + + /** + * Utility method to scan past blank or comment. + */ + static public int skipBlanksAndComments(CharSequence s,int from) { + int end = s.length(); + for ( ; from < end; from++ ) { + if ( Character.isWhitespace( s.charAt( from ) ) ) + continue; + if ( s.charAt( from ) != '/' ) + return from; + if ( s.length() < 2 ) + return from; + if ( s.charAt( from + 1 ) == '*' ) { + int x = indexOf( s, "*/", from ); + if ( x < 0 ) { + // Unterminated comment + return end; + } + from = x + 1; + continue; + } + if ( s.charAt( from + 1 ) == '/' ) { + int x = indexOf( s, "\n", from ); + if ( x < 0 ) { + // Unterminated comment + return end; + } + from = x; + continue; + } + return from; + } + return end; + } + + /** + * The Parse interface represents the result from parsing, prior + * to semantic processing. + */ + public interface ParseTree { + + /** + * Returns the first character position spanned by this parse + * tree. + */ + public int getFrom(); + + /** + * Returns the first significant character position within the + * region spanned by this parse tree. + */ + public int getFirst(); + + /** + * Returns the first character position after the region + * spanned by this parse tree. + */ + public int getTo(); + + /** + * Returns the grammar rule index for this result. + */ + public int getIndex(); + + /** + * Returns the grammar rule for this result, if any. + */ + public String getRule(); + + /** + * Returns the 'substance' portion of the span. + */ + public String getText(); + + /** + * Returns the parsetree's generic syntax name, which is the + * rule name for production syntax, and ":" otherwise. + */ + public String getSyntaxName(); + + /** + * Utility method to form a string representation of this + * parse tree element, with an indentation argument. + */ + public String toString(String head); + + /** + * Forms a string representation of two lines to indicate the + * span of this result on the input line. + */ + public String inputSpan(); + + /** + * Forms a string representation of the span region. + */ + public String spanString(); + + /** + * Processing method for this parse tree. + */ + public Object process() throws Throwable; + + /** + * Processing method for the children of this parse tree. + */ + public Vector processChildren() throws Throwable; + } + + /** + * The Result class is for carrying parsing result. + */ + public class Result implements ParseTree { + + /** + * Refers to the input being parsed. + */ + public final CharSequence input; + + /** + * The character position in the input from where this Result + * object spans. + */ + public final int from; + + /** + * The first character position recognised as not whitespace + * or comment; the beginning of 'substance'. + */ + public final int first; + + /** + * The character position in the input to where this Result + * object spans. Thus, the span is input.substring(from,to). + */ + public final int to; + + /** + * Flag as to whether or not the text should be preserved upon + * semantic processing. + */ + public final boolean preserve; + + /** + * Returns the first character position spanned by this parse + * tree. + */ + public int getFrom() { + return from; + } + + /** + * Returns the first significant character position within the + * region spanned by this parse tree. + */ + public int getFirst() { + return first; + } + + /** + * Returns the first character position after the region + * spanned by this parse tree. + */ + public int getTo() { + return to; + } + + /** + * Constructor. + */ + public Result(CharSequence s,int a,int b,int c) { + this( s, a, b, c, false ); + } + + /** + * Constructor. + */ + public Result(CharSequence s,int a,int b,int c,boolean p) { + from = a; + first = b; + to = c; + input = s; + preserve = p; + } + + /** + * Returns the 'substance' portion of the span. + */ + public String getText() { + return input.subSequence( first, to ).toString(); + } + + /** + * Returns the grammar rule index for this result. + */ + public int getIndex() { + return -1; + } + + /** + * Returns the grammar rule for this result. + */ + public String getRule() { + return null; + } + + /** + * Returns the parsetree's generic syntax name, which is the + * rule name for production syntax, and ":" otherwise. + */ + public String getSyntaxName() { + return ":"; + } + + /** + * Returns the canonical token string for this result. + */ + public String getToken() { + return "'" + getText(); + } + + /** + * Forms a String representaion of this Result object. + * The argument is ignored. + */ + public String toString(String head) { + return toString(); + } + + /** + * Forms a string representation of the region spanned. + */ + public String spanString() { + return "[" + from + "," + to + "]"; + } + + /** + * Presents this result bmo of an input context on one line, + * followed by a 'marker' for the span beneath it. + */ + public String inputSpan() { + return inputPosition( input, first, to - first ); + } + + /** + * Forms a String representaion of this Result object. + */ + public String toString() { + return "{" + from + "," + first + "," + to + + ( preserve? "=!'" : "='" ) + + getText() + "'}" ; + } + + /** + * Default result processing returns null. + */ + public Object process() throws Throwable { + if ( preserve ) + return getText(); + return null; + } + + /** + * Processing method for the children of this parse tree. + */ + public Vector processChildren() throws Throwable { + return null; + } + } + + /** + * The ProductionResult class is for carrrying production rule + * result. + */ + public class ProductionResult extends Result { + + /** + * Tells the name of the non-terminal produced. + */ + public String rule; + + /** + * Tells the index for the production used. + */ + public int index; + + /** + * Holds the result objects of associated with production rule + * tokens. + */ + public ParseTree [] results; + + /** + * Returns the grammar rule index for this result. + */ + public int getIndex() { + return index; + } + + /** + * Returns the grammar rule for this result. + */ + public String getRule() { + return rule; + } + + /** + * Constructor intended for Lexical rules. + */ + public ProductionResult(String r,CharSequence s,int a,int b,int c) { + super( s, a, b, c ); + rule = r; + index = -1; + } + + /** + * General constructor. + */ + public ProductionResult(String r,CharSequence s,int i,ParseTree [] rs) { + this( r, s, + rs == null? 0 : rs[0].getFrom(), + rs == null? 0 : rs[0].getFirst(), + rs == null? -1 : rs[ rs.length - 1 ].getTo() ); + index = i; + results = rs; + } + + /** + * Returns the parsetree's generic syntax name, which is the + * rule name for production syntax, and ":" otherwise. + */ + public String getSyntaxName() { + return rule; + } + + /** + * Returns the canonical token string for this result. + */ + public String getToken() { + return "<" + getRule() + ">"; + } + + /** + * Forms a string representation of the parse tree. + */ + public String toString() { + return toString( "" ); + } + + /** + * Forms a string representation of the parse tree, using the + * given string as indentation prefix. + */ + public String toString(String head) { + StringBuffer s = new StringBuffer(); + s.append( rule ); + s.append( ":" ); + s.append( index ); + s.append( "[" ); + head += " "; + if ( results == null ) { + s.append( super.toString() ); + } else { + for ( int i=0; i 0 ) { + s.append( "," ); + } + s.append( "\n" ); + s.append( head ); + s.append( results[i].toString( head ) ); + } + } + s.append( "]" ); + return s.toString(); + } + + /** + * Default result processing invokes the process method of the + * syntax rule. + */ + public Object process() throws Throwable { + try { + return getDefinition( rule ).process( this ); + } catch (Throwable e) { + throw hideParse( e ); + } + } + + /** + * Utility method to process the children + */ + public Vector processChildren() throws Throwable { + Vector v = new Vector(); + if ( results != null ) { + for ( int i=0; i from? current_from - from : 0; + text = inputPosition( input, from, span ) + "\n" + text; + } + current_from = from; + + System.err.println( text ); + } + + /** + * Utility method to test trace mode. + */ + public boolean tracing(int mode) { + return ( trace & mode ) != 0; + } + + /** + * Table of successful parse results. + */ + public Hashtable results = null; + + /** + * Table of known failed parse postions. + */ + public HashSet failed = null; + + /** + * Current parsing level. + */ + public int level = 0; + + /** + * Maps positions to tried lexical rules and terminals + */ + public TreeMap errors; + + /** + * Support method to create a hash key for a rule at a position. + */ + public String key(int from,String rule) { + return rule + " at " + from ; + } + + /** + * Support method to create a hash key for a rule at a position. + */ + public String key(int from,String rule,int index) { + return rule + ":" + index + " at " + from; + } + + /** + * Returns the last parse result for a rule at a position. + */ + public ParseTree last(int from,String rule) { + if ( results == null ) + return null; + return (ParseTree) results.get( key( from, rule ) ); + } + + /** + * Support method to set the parse result for a rule at a position. + */ + public void saveLast(int from,String rule,ParseTree x) { + if ( x == null ) + return; + if ( results == null ) + results = new Hashtable(); + results.put( key( from, rule ), x ); + } + + /** + * Support method to remove the parse result for a rule at a + * position. + */ + public void clearLast(int from,String rule) { + if ( results != null ) + results.remove( key( from, rule ) ); + } + + /** + * Tells whether a parse position is known to be failed. + */ + public boolean isFailed(int from,String rule) { + return failed != null && failed.contains( key( from, rule ) ); + } + + /** + * Marks a parse position as failed. + */ + public void setFailed(int from,String rule) { + if ( failed == null ) + failed = new HashSet(); + failed.add( key( from, rule ) ); + } + + /** + * Unmarks a parse position as failed. + */ + public void clearFailed(int from,String rule) { + if ( failed != null ) + failed.remove( key( from, rule ) ); + } + + /** + * Tells whether a parse position is known to be failed. + */ + public boolean isFailed(int from,String rule,int index) { + return failed != null && failed.contains( key( from, rule, index ) ); + } + + /** + * Marks a parse option position as failed. + */ + public void setFailed(int from,String rule,int index) { + if ( failed == null ) + failed = new HashSet(); + failed.add( key( from, rule, index ) ); + } + + /** + * Unmarks a parse position as failed. + */ + public void clearFailed(int from,String rule,int index) { + if ( failed != null ) + failed.remove( key( from, rule, index ) ); + } + + // + // Error handling... + // + + /** + * Support method to log an attempted lexical match. + */ + public void tryToken(ErrorToken token,int from) { + if ( errors == null ) + errors = new TreeMap( + new Comparator() { + public int compare(Object a,Object b) + { + return ((Integer) a).compareTo( (Integer) b ); + } + } ); + Integer i = new Integer( from ); + TreeSet t = (TreeSet) errors.get( i ); + if ( t == null ) { + t = new TreeSet( new Comparator() { + public int compare(Object a,Object b) { + return compare( (ErrorToken) a, (ErrorToken) b ); + } + + public int compare(ErrorToken a,ErrorToken b) { + return a.errorName().compareTo( b.errorName() ); + } + } ); + errors.put( i, t ); + } + t.add( token ); + } + + /** + * Report all error tokens. + */ + public String allErrors() { + StringBuffer s = new StringBuffer(); + for ( Iterator i = errors.keySet().iterator(); i.hasNext(); ) { + Integer index = (Integer) i.next(); + TreeSet list = getErrors( index.intValue() ); + s.append( "Pos " ); + s.append( index.toString() ); + s.append( ":" ); + for ( Iterator e = list.iterator(); e.hasNext(); ) { + s.append( " " ); + s.append( (String) e.next() ); + } + s.append( "\n" ); + } + return s.toString(); + } + + /** + * Utility method that presents the deepest error in format. + * : + * ^ + * Expected: ... + */ + public String lastError(CharSequence input) { + return errors == null? null : lastError( input, lastErrorPosition() ); + } + + public int lastErrorPosition() { + if ( errors == null ) + return -1; + return ((Integer) errors.lastKey()).intValue(); + } + + /** + * Returns a string of the error names produced by the indicated + * error tokens. + */ + public TreeSet getErrors(int from) { + return (TreeSet) errors.get( new Integer( from ) ); + } + + /** + * Utility method that presents the deepest error in format. + * : + * ^ + * Expected: ... + */ + public String lastError(CharSequence input,int from) { + StringBuffer out = new StringBuffer(); + out.append( inputPosition( input, from ) ); + out.append( " [" + from + "]" ); + out.append( "\nExpected: " ); + + TreeSet set = getErrors( from ); + + int at = set.size(); + int i = at; + + for ( Iterator e = set.iterator(); e.hasNext(); i--) { + if ( i == 1 ) { + if ( at > 1 ) + out.append( " or " ); + } else { + if ( i != at ) + out.append( ", " ); + } + out.append( ((ErrorToken) e.next()).errorName() ); + } + out.append( "." ); + return out.toString(); + } + + /** + * The ErrorToken interface is for producing error token name. + */ + public interface ErrorToken { + + /** + * Returns the token name. + */ + public String errorName(); + } + + + // + // Parsing elements + // + + /** + * A ProductionList defines a rule by a list of alternative + * productions. + */ + public class ProductionList implements Rule { + + /** + * The rule defined by the productions. + */ + public final String rule; + + /** + * The list of optional productions for this rule. + */ + public final Production [] options; + + /** + * Constructor. + */ + public ProductionList(String r,Production [] list) { + rule = r; + options = list; + if ( list != null ) { + for ( int i=0; i is not defined.", from ); + } + + boolean recursive = usage == from; // Re-entering this rule + + ParseTree last = last( from, rule ); + ParseTree result = null; + + boolean reuse = reuse_mode < 2 && last != null && + ( reuse_mode == 0 || recursive ); + + if ( reuse ) { + if ( tracing( TRACE_OPTIONS ) ) + log( input, from, rule, + "re-using last (mode=" + reuse_mode + ")" ); + return last; + } + + int prior_usage = usage; + usage = from; + + boolean again = false; + + try { + do { + trace_count ++; + again = false; + result = null; + for (int i=0; result == null && i 1 ) { + if ( tracing( TRACE_OPTIONS ) ) + log( input, from, rulex, "recursive fail"); + return null; + } + // Recursive singleton + if ( tracing( TRACE_OPTIONS ) ) + log( input, from, rulex, "recursive singleton"); + return null; + } + + if ( tracing( TRACE_OPTIONS ) ) + log( input, from, rulex, "previous usage = " + usage ); + + int prior = usage; + usage = from; + + try { + ParseTree [] results = new ParseTree [ tokens.length ]; + ParseTree result = null; + for ( int i=0; i>-- " + (++level) + " -->>" ); + } + + Rule r = getDefinition( rule ); + if ( r == null ) { + throw new ParseException( + "<" + rule + "> is not defined.", from ); + } + + ParseTree result = r.parse( input, from ); + + if ( result == null ) { + if ( tracing( TRACE_PARSE ) ) + log( input, from, rule, + "<<-- " + (level--) + " --<< failed" ); + return null; + } + + if ( tracing( TRACE_PARSE ) ) + log( input, from, rule, "<<-- " + (level--) + " --<< " + + result.spanString() ); + + return result; + } catch (ParseException e) { + throw (ParseException) hideParse( e ); + } catch (Throwable e) { + ParseException pe = new ParseException( e.toString(), from ); + pe.initCause( e ); + throw pe; + } + } + + /** + * Forms a string representation of a non-terminal token. + */ + public String toString() { + return "<" + rule + ">" ; + } + } + + /** + * The Lexical class is a base class for lexical tokens. + */ + abstract public class Lexical implements Rule, ErrorToken { + + /** + * The rule name. + */ + final public String rule; + + /** + * Constructor. + */ + public Lexical(String r) { + rule = r; + } + + /** + * Returns the rule name. + */ + public String getName() { + return rule; + } + + /** + * Equality for terminals are measured on their error name + * strings. + */ + public boolean equals(Object x) { + return x instanceof Lexical && + errorName().equals( ((Lexical) x).errorName() ) ; + } + + /** + * Hash code method; uses the key string. + */ + public int hashCode() { + return errorName().hashCode(); + } + + /** + * Handle token ingress, which typically means to skip + * whitespace. + */ + public int ingress(CharSequence input,int from) { + return skipBlanksAndComments( input, from ); + } + + /** + * The default parsing of a lexical rule: skips blanks and + * comments, and then invokes scan. + */ + public ParseTree parse(CharSequence input,int from) + throws ParseException { + ParseTree last = last( from, rule ); + if ( isFailed( from, rule ) ) { + if ( tracing( TRACE_OPTIONS ) ) + log( input, from, rule, "lexical known failed" ); + return null; + } + if ( last != null ) { + if ( tracing( TRACE_OPTIONS ) ) + log( input, from, rule, + "lexical reuse " + last.spanString() ); + return last; + } + tryToken( this, from ); + int first = ingress( input, from ); + int to = scan( input, first ); + if ( to < 0 ) { + setFailed( from, rule ); + if ( tracing( TRACE_OPTIONS ) ) + log( input, from, rule, "lexical failed" ); + return null; + } + last = new ProductionResult( rule, input, from, first, to ); + saveLast( from, rule, last ); + if ( tracing( TRACE_OPTIONS ) ) + log( input, from, rule, "lexical success" ); + return last; + } + + /** + * The lexical function, which returns the end of the span + * covered by the lexical rule, or -1. The method is given the + * input string and the position to scan, which is neither + * whitespace nor in a comment. + */ + abstract public int scan(CharSequence s,int from); + + /** + * Forms a string representation of this lexical rule. + */ + public String toString() { + return + rule + " is lexical " + strip( getClass().getName() ) + "\n" ; + } + + /** + * The standard error name for a lexical rule. + */ + public String errorName() { + return "<" + rule + ">"; + } + + /** + * Semantic processing for a lexical will return the matched + * string unless it's an empty string. + */ + public Object process(ParseTree pr) { + return pr.getFirst() == pr.getTo()? null : pr.getText(); + } + } + + /** + * The Link class is a base class for rules that link to + * other grammars. + */ + public class Link implements Rule { + + /** + * The grammar linked to. + */ + public final Grammar grammar; + + /** + * The name of the non-terminal in this grammar. + */ + public final String rule; + + /** + * The corresponding name in the linked grammar. + */ + public final String goal; + + /** + * Constructor. + */ + public Link(String r,String g,Grammar gr) { + grammar = gr; + rule = r; + goal = g; + } + + /** + * Constructor. Alternative constructor using the same rule + * name in both grammars. + */ + public Link(String r,Grammar gr) { + this( r, r, gr ); + } + + /** + * Returns the 'local' rule name + */ + public String getName() { + return rule; + } + + /** + * Parse input, save result, and return true if successful. + */ + public ParseTree parse(CharSequence input,int from) + throws ParseException { + if ( grammar.tracing( TRACE_LINK ) ) + grammar.log( input, from, rule, "link: " + goal ); + int trace_start = grammar.trace_count; + + ParseTree p = grammar.parse( goal, input, from ); + + trace_count += grammar.trace_count - trace_start; + int index = grammar.lastErrorPosition(); + TreeSet t = grammar.getErrors( index ); + for ( Iterator i = t.iterator(); i.hasNext(); ) + tryToken( (ErrorToken) i.next(), index ); + + if ( p != null ) { + if ( tracing( TRACE_LINK ) ) + log( + input, from, rule, "link return: " + goal + " = " + + p.spanString() + " with last error at " + index ); + } else { + if ( tracing( TRACE_LINK ) ) + log( + input, from, rule, + "link return: " + goal + " failed" ); + } + return p; + } + + /** + * Semantic processing for a Link + */ + public Object process(ParseTree pr) throws Throwable { + // The parse tree is contained in the other grammar. + return grammar.process( pr ); + } + + public String toString() { + String pkg = Grammar.this.getClass().getName(); + pkg = pkg.substring( 0, pkg.lastIndexOf( "." ) + 1 ); + String name = grammar.getClass().getName(); + if ( name.startsWith( pkg ) ) + name = name.substring( pkg.length() ); + return + rule + " is linked to <" + goal + "> in " + name + "\n"; + } + } + + // + // Additional Grammar methods + // + + /** + * Constructor. Invokes the initialisation() method. + */ + public Grammar() { + initialisation(); + } + + /** + * Overridable method to perform grammar initialisation. + */ + public void initialisation() { + } + + /** + * Utility method to add a syntax rule. + */ + public void addRule(Rule rule) { + rules.put( rule.getName(), rule ); + } + + /** + * Applies a syntax rule to a string. + */ + public ParseTree parse(String rule,CharSequence input) + throws ParseException { + for ( Iterator i = getGrammars().iterator(); i.hasNext(); ) { + Grammar g = (Grammar) i.next(); + g.reset(); + } + return parse( rule, input, 0 ); + } + + /** + * Resets the parsing machinery. + */ + public void reset() { + errors = null; + results = null; + failed = null; + trace_count = 0; + } + + /** + * Applies a syntax rule to a string. + */ + public ParseTree parse(String rule,CharSequence input,int from) + throws ParseException { + Rule r = getDefinition( rule ); + if ( r == null ) + throw new ParseException( + "<" + rule + "> is not defined.", from ); + return r.parse( input, from ); + } + + /** + * Utility method to change the stack trace of an exception by + * removing all Grammar elements. + */ + public Throwable hideParse(Throwable e) { + return hideParse( e, Grammar.class.getName() ); + } + + /** + * Utility method to change the stack trace of an exception by + * removing all elements with agiven class name prefix. + */ + public Throwable hideParse(Throwable e,String prefix) { + if ( tracing( TRACE_STACK ) ) + return e; + + for ( Throwable t = e; t != null; t = t.getCause() ) { + StackTraceElement[] stack = t.getStackTrace(); + Vector v = new Vector(); + for ( int i=0; i. +**********************************************************************/ + +package com.intendico.sdp; + +import java.lang.reflect.Constructor; +import java.util.Vector; +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * Class LLBNF defines a standard recursive descent BNF, using "<" and + * ">" to indicate non-terminals, and "'" to indicate terminals. + */ +public class LLBNF extends BNFGrammar { + + /** + * Utility method to instantiate a Production object. The method + * first tries to instantiate with the name given, thereafter by + * assuming its an inner class of the target grammar + */ + Class findClass(String clsname) + throws ClassNotFoundException + { + ClassNotFoundException ex = null; + // Try the name as given + try { + return Class.forName( clsname ); + } catch (ClassNotFoundException e) { + ex = e; + } + // Trying the name as inner class of the target class, or + // any of the target class base classes. + for (Class tc = target.getClass(); tc != null; + tc = tc.getSuperclass() ) { + try { + return Class.forName( tc.getName() + "$" + clsname ); + } catch (ClassNotFoundException e) { + } + } + + // Trying the name as a class of the same package as the + // target class or any of its base classes + for (Class tc = target.getClass(); tc != null; + tc = tc.getSuperclass() ) { + String pkg = tc.getName(); + int x = pkg.lastIndexOf( '.' ); + if ( x > 0 ) { + try { + return Class.forName( + pkg.substring( 0, x ) + "." + clsname ); + } catch (ClassNotFoundException e) { + } + } + } + throw ex; + } + + Object create(String clsname) + throws Exception + { + Class cls = findClass( clsname ); + Class ctx = cls.getDeclaringClass(); + if ( ctx == null ) + return cls.newInstance(); + return cls.getConstructor( + new Class [] { ctx } ).newInstance( new Object [] { target } ); + } + + Object createNamed(String clsname,String name) + throws Exception + { + Class cls = findClass( clsname ); + Class ctx = cls.getDeclaringClass(); + if ( ctx == null ) + return cls.getConstructor( + new Class [] { String.class } ). + newInstance( new Object [] { name } ); + return cls.getConstructor( + new Class [] { ctx, String.class } ). + newInstance( new Object [] { target, name } ); + } + + Hashtable grammars = new Hashtable(); + + Grammar getGrammar(String grammar) + throws Exception + { + Class c = findClass( grammar ); + grammar = c.getName(); + Grammar g = (Grammar) grammars.get( grammar ); + if ( g == null ) { + g = (Grammar) create( grammar ); + grammars.put( grammar, g ); + } + return g; + } + + Link linkGrammar(String rule,String goal,String grammar) + throws Exception + { + return target.new Link( rule, goal, getGrammar( grammar ) ); + } + + class BackLink { + + final Grammar grammar; + final Link link; + + BackLink(String rule,String goal,String g,Grammar target) + throws Exception + { + grammar = getGrammar( g ); + link = grammar.new Link( rule, goal, target ); + } + + void addRule() + { + grammar.addRule( link ); + } + } + + BackLink backlinkGrammar(String rule,String goal,String grammar) + throws Exception + { + return new BackLink( rule, goal, grammar, target ); + } + + /** + * Generic Helper class that tags with a string. + */ + public static class Tag extends Production { + + public final String tag; + public final Grammar grammar; + + public Tag(Grammar g,String t) + { + g.super(); + tag = t; + grammar = g; + } + + public Object action(Vector v) + throws Exception + { + RuleAction action = (RuleAction) grammar.rule_actions.get( tag ); + if ( action != null ) + return action.action( v ); + if ( v.size() == 0 ) + return tag; + v.add( 0, tag ); + return v; + } + + public String toString() { + StringBuffer s = new StringBuffer(); + + for ( int i=0; i" ), + } ) + } ) ); + addRule( + new ProductionList( + "terminal", + new Production [] { + new Production( new Token [] { + new Terminal( "!", true ), + new NonTerminal( "any" ) + } ), + new Production( new Token [] { + new Terminal( "'", true ), + new NonTerminal( "any" ) + } ), + new Production( new Token [] { + new Terminal( "?", true ), + new NonTerminal( "any" ) + } ) + } ) ); + addRule( new AnySymbol( "any" ) ); + addRule( new Identifier( "identifier" ) ); + addRule( new End( "end" ) ); + } + + /** + * Utility method to define rules using LLBNF syntax. + */ + public Grammar addRules(Grammar g,String rules) + throws Throwable + { + try { + //trace = TRACE_STACK; + target = g; + grammars.put( g.getClass().getName(), target ); + + Vector v = (Vector) parseAndProcess( "rules", rules ); + for ( Enumeration e = v.elements(); e.hasMoreElements(); ) { + Object r = e.nextElement(); + if ( r instanceof BackLink ) + ((BackLink) r).addRule(); + else + target.addRule( (Rule) r ); + } + return target; + } catch (Throwable t) { + throw hideParse( t, LLBNF.class.getName() ); + } + } + + /** + * A Test main method. + */ + public static void main(String [] args) + throws Throwable + { + LLBNF g = new LLBNF(); + String rules = g.toString() ; + Grammar x = g.addRules( new Grammar(), rules ); + System.out.println( x ); + } +} diff --git a/examples/planchoice/Main.java b/examples/planchoice/Main.java new file mode 100644 index 0000000..663a046 --- /dev/null +++ b/examples/planchoice/Main.java @@ -0,0 +1,193 @@ +package examples.planchoice; + +import com.intendico.gorite.*; +import com.intendico.data.*; +import com.intendico.data.addon.Language; +import java.util.Vector; +import java.util.Map; +import java.util.Hashtable; + +/** + * This is an example of using a plan choice plan for choosing which + * plan instance to pursue first / next for a goal. + */ +public class Main { + + /** + * Debug: dump the plan choice options + */ + static private void debugOptions(Data data) { + Vector options = (Vector) + data.getValue( "options" ); + Vector failed = (Vector) + data.getValue( "failed" ); + int n = 0; + System.err.println( "=============" ); + for ( ContextualGoal cg : failed ) { + Plan p = (Plan) cg.getGoalSubgoals()[0]; + if ( p instanceof TransferGoal ) { + p = (Plan) ((TransferGoal) p).goal; + } + System.err.printf( + "Failed %d = %s (%s)\n", n++, cg.context, p.getType() ); + } + n = 0; + for ( ContextualGoal cg : options ) { + Plan p = (Plan) cg.getGoalSubgoals()[0]; + if ( p instanceof TransferGoal ) { + p = (Plan) ((TransferGoal) p).goal; + } + System.err.printf( + "Option %d = %s (%s)\n", n++, cg.context, p.getType() ); + } + } + + /** + * Application entry point. + */ + static public void main(String [] args) throws Exception { + //Goal.tracing = true; + Performer ralph = new Performer( "ralph" ) {{ + setPlanChoice( "go for it", "go for it choice" ); + putInquirable( new Relation( "unary", 1 ) ); + getInquirable( "unary" ).get( new Object [] { "AAA" } ).add(); + getInquirable( "unary" ).get( new Object [] { "BBB" } ).add(); + getInquirable( "unary" ).get( new Object [] { "CCC" } ).add(); + + addGoal( new Plan( "go for it", new Goal [] { + new Goal( "print $x" ) { + public States execute(Data data) { + System.err.println( + "First: " + data.getValue( "$x" ) ); + return States.FAILED; + } + } + } ) { + public Query context(Data d) { + return Language.textToQuery( + "unary( $x )", getInquirables().values(), null, d ); + } + } ); + + addGoal( new Plan( "go for it", new Goal [] { + new Goal( "print $y" ) { + public States execute(Data data) { + System.err.println( + "Second: " + data.getValue( "$y" ) ); + return States.FAILED; + } + } + }) { + public Query context(Data d) { + return Language.textToQuery( + "unary( $y )", getInquirables().values(), null, d ); + } + } ); + + /** + * Add a plan choice plan that inspects the bindings for + * the applicable plan instance options, then selects the + * first of highest character code of the first binding. + * + * Note that this particular choice logic could also be + * implemented as precedence methods of the object level + * plans. + */ + addGoal( new Plan( "go for it choice", new Goal [] { + new Goal( "select by planCode" ) { + @Override + public States execute(Data data,Goal.Instance i) { + /** + Input data elements: + "options" = Vector of ContextualGoal + "failed" = Vector of ContextualGoal + Output data elements: + "choice" = ContextualGoal (or null) + **/ + debugOptions( data ); + int code = -1; + Vector options = + (Vector) + data.getValue( "options" ); + if ( options.size() >= 1 ) { + System.out.println( + "Cause: " + + options.get( 0 ).goal_data.thread_name ); + } + ContextualGoal choice = null; + for ( ContextualGoal g : options ) { + int c = planCode( g ); + System.out.printf( + "code for %s is %d\n", g.context, c ); + if ( c <= code ) + continue; + choice = g; + code = c; + } + if ( choice != null ) { + data.setValue( "choice", choice ); + } + return code > (int)'A'? States.PASSED : States.FAILED; + } + } + } ) ); + }}; + Team team = new Team( "everyone" ) {{ + setTaskTeam( "group", new TaskTeam() {{ + addRole( new Role( "sub", null ) {{ + putInquirable( new Relation( "unary", 1 ) ); + getInquirable( "unary" ).get( + new Object [] { "ABC" } ).add(); + getInquirable( "unary" ).get( + new Object [] { "DEF" } ).add(); + addGoal( new Plan( "go for it", new Goal [] { + new Goal( "print $y" ) { + public States execute(Data data) { + System.err.println( + "Third: " + + data.getValue( "$y" ) ); + return States.FAILED; + } + } + }) { + public Query context(Data d) { + return Language.textToQuery( + "unary( $y )", + getInquirables().values(), null, d ); + } + } ); + }} ); + }} ); + addGoal( new Plan( "go for it", new Goal [] { + deploy( "group" ), + new TeamGoal( "sub", "go for it" ) + } ) ); + }}; + team.addPerformer( ralph ); + team.performGoal( new BDIGoal( "go for it" ), "TOP", new Data() ); + } + + /** + * Utility method that gives a given plan option a code, which + * generally is 0, except when the goal represents a contextual + * plan instance with an instantiation context that offers a + * non-empty String for the first context variable, and in that + * case, the character code of the first character of that string + * is used. + */ + static public int planCode(ContextualGoal g) { + if ( g.context == null ) { + return 0; + } + Vector cg = (Vector) g.context; + if ( cg == null || cg.size() == 0 ) + return 0; + Object x = Ref.deref( cg.get( 0 ) ); + if ( ! ( x instanceof String ) ) + return 0; + String s = (String) x; + if ( s.length() == 0 ) + return 0; + return (int) s.charAt( 0 ); + } +} diff --git a/examples/planchoice/filter b/examples/planchoice/filter new file mode 100755 index 0000000..a961e78 --- /dev/null +++ b/examples/planchoice/filter @@ -0,0 +1,7 @@ +# Defines a pipeline filter on the application output for reducing +# it to regression test output. +# Repeatable output should use: cat +# Repeatable with internal pointers may use: sed -e 's/#[0-9]\+/#nnn/g' +# Non repeatable should use: sort +# Extreme cases may use special filter +cat diff --git a/examples/planchoice/output b/examples/planchoice/output new file mode 100644 index 0000000..4657922 --- /dev/null +++ b/examples/planchoice/output @@ -0,0 +1,328 @@ +** can Fill: sub com.intendico.gorite.examples.planchoice.Main$1: ralph +** Sharing inquirables in com.intendico.gorite.examples.planchoice.Main$2: everyone +** perform TOP +=> TOP "go for it" (group=null) +** Lookup "go for it" +** "go for it" attempt 1 +=> TOP*0 "go for it" (group=null) +=> TOP*0{} "go for it" (group=null) +=> TOP*0{}.0 "deploy group" (group=null) +** establish task team +** can Act: sub com.intendico.gorite.examples.planchoice.Main$1: ralph +-- noting everyone:TOP*0{}.0 "deploy group" (PASSED) +<= everyone:TOP*0{}.0 "deploy group" (PASSED) +=> TOP*0{}.1 "go for it" (group=null) +=> TOP*0{}.1:0 "go for it" (group=null) +** Lookup "go for it" +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +** "go for it" attempt 1 +=> TOP*0{}.1:0*0 "go for it choice" (group=null) +=> TOP*0{}.1:0*0? "go for it choice" (group=null) +** Lookup "go for it choice" +** "go for it choice" attempt 1 +=> TOP*0{}.1:0*0?*0 "go for it choice" (group=null) +=> TOP*0{}.1:0*0?*0{} "go for it choice" (group=null) +=> TOP*0{}.1:0*0?*0{}.0 "select by planCode" (group=null) +com.intendico.gorite.ContextualGoal [ABC]Plan: +"go for it" (Main$2$1$1$2) +1 "print $y" (Main$2$1$1$1) +com.intendico.gorite.ContextualGoal [DEF]Plan: +"go for it" (Main$2$1$1$2) +1 "print $y" (Main$2$1$1$1) +com.intendico.gorite.ContextualGoal [BBB]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [CCC]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [BBB]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [CCC]Plan: +"go for it" (TransferGoal) +-- noting ralph:TOP*0{}.1:0*0?*0{}.0 "select by planCode" (PASSED) +<= ralph:TOP*0{}.1:0*0?*0{}.0 "select by planCode" (PASSED) +-- noting ralph:TOP*0{}.1:0*0?*0{} "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*0?*0{} "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*0?*0 "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*0?*0 "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*0? "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*0? "go for it choice" (PASSED) +=> TOP*0{}.1:0*0! "go for it" (group=null) +=> TOP*0{}.1:0*0!{$y=DEF} "go for it" (group=null) +=> TOP*0{}.1:0*0!{$y=DEF}.0 "print $y" (group=null) +Third: DEF +-- noting everyone:TOP*0{}.1:0*0!{$y=DEF}.0 "print $y" (FAILED) +<= everyone:TOP*0{}.1:0*0!{$y=DEF}.0 "print $y" (FAILED) +-- noting everyone:TOP*0{}.1:0*0!{$y=DEF} "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*0!{$y=DEF} "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*0! "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*0! "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*0 "go for it choice" (FAILED) +<= everyone:TOP*0{}.1:0*0 "go for it choice" (FAILED) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +** "go for it" attempt 2 +=> TOP*0{}.1:0*1 "go for it choice" (group=null) +=> TOP*0{}.1:0*1? "go for it choice" (group=null) +** Lookup "go for it choice" +** "go for it choice" attempt 1 +=> TOP*0{}.1:0*1?*0 "go for it choice" (group=null) +=> TOP*0{}.1:0*1?*0{} "go for it choice" (group=null) +=> TOP*0{}.1:0*1?*0{}.0 "select by planCode" (group=null) +com.intendico.gorite.ContextualGoal [ABC]Plan: +"go for it" (Main$2$1$1$2) +1 "print $y" (Main$2$1$1$1) +com.intendico.gorite.ContextualGoal [BBB]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [CCC]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [BBB]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [CCC]Plan: +"go for it" (TransferGoal) +-- noting ralph:TOP*0{}.1:0*1?*0{}.0 "select by planCode" (PASSED) +<= ralph:TOP*0{}.1:0*1?*0{}.0 "select by planCode" (PASSED) +-- noting ralph:TOP*0{}.1:0*1?*0{} "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*1?*0{} "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*1?*0 "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*1?*0 "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*1? "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*1? "go for it choice" (PASSED) +=> TOP*0{}.1:0*1! "go for it" (group=null) +=> TOP*0{}.1:0*1!{$x=CCC} "go for it" (group=null) +>> com.intendico.gorite.examples.planchoice.Main$1: ralph +=> +TOP*0{}.1:0*1!{$x=CCC} "go for it" (group=null) +=> +TOP*0{}.1:0*1!{$x=CCC}.0 "print $x" (group=null) +First: CCC +-- noting ralph:+TOP*0{}.1:0*1!{$x=CCC}.0 "print $x" (FAILED) +<= ralph:+TOP*0{}.1:0*1!{$x=CCC}.0 "print $x" (FAILED) +-- noting ralph:+TOP*0{}.1:0*1!{$x=CCC} "go for it" (FAILED) +<= ralph:+TOP*0{}.1:0*1!{$x=CCC} "go for it" (FAILED) +<< com.intendico.gorite.examples.planchoice.Main$1: ralph +-- noting everyone:TOP*0{}.1:0*1!{$x=CCC} "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*1!{$x=CCC} "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*1! "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*1! "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*1 "go for it choice" (FAILED) +<= everyone:TOP*0{}.1:0*1 "go for it choice" (FAILED) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +** "go for it" attempt 3 +=> TOP*0{}.1:0*2 "go for it choice" (group=null) +=> TOP*0{}.1:0*2? "go for it choice" (group=null) +** Lookup "go for it choice" +** "go for it choice" attempt 1 +=> TOP*0{}.1:0*2?*0 "go for it choice" (group=null) +=> TOP*0{}.1:0*2?*0{} "go for it choice" (group=null) +=> TOP*0{}.1:0*2?*0{}.0 "select by planCode" (group=null) +com.intendico.gorite.ContextualGoal [ABC]Plan: +"go for it" (Main$2$1$1$2) +1 "print $y" (Main$2$1$1$1) +com.intendico.gorite.ContextualGoal [BBB]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [BBB]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [CCC]Plan: +"go for it" (TransferGoal) +-- noting ralph:TOP*0{}.1:0*2?*0{}.0 "select by planCode" (PASSED) +<= ralph:TOP*0{}.1:0*2?*0{}.0 "select by planCode" (PASSED) +-- noting ralph:TOP*0{}.1:0*2?*0{} "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*2?*0{} "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*2?*0 "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*2?*0 "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*2? "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*2? "go for it choice" (PASSED) +=> TOP*0{}.1:0*2! "go for it" (group=null) +=> TOP*0{}.1:0*2!{$y=CCC} "go for it" (group=null) +>> com.intendico.gorite.examples.planchoice.Main$1: ralph +=> +TOP*0{}.1:0*2!{$y=CCC} "go for it" (group=null) +=> +TOP*0{}.1:0*2!{$y=CCC}.0 "print $y" (group=null) +Second: CCC +-- noting ralph:+TOP*0{}.1:0*2!{$y=CCC}.0 "print $y" (FAILED) +<= ralph:+TOP*0{}.1:0*2!{$y=CCC}.0 "print $y" (FAILED) +-- noting ralph:+TOP*0{}.1:0*2!{$y=CCC} "go for it" (FAILED) +<= ralph:+TOP*0{}.1:0*2!{$y=CCC} "go for it" (FAILED) +<< com.intendico.gorite.examples.planchoice.Main$1: ralph +-- noting everyone:TOP*0{}.1:0*2!{$y=CCC} "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*2!{$y=CCC} "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*2! "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*2! "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*2 "go for it choice" (FAILED) +<= everyone:TOP*0{}.1:0*2 "go for it choice" (FAILED) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +** "go for it" attempt 4 +=> TOP*0{}.1:0*3 "go for it choice" (group=null) +=> TOP*0{}.1:0*3? "go for it choice" (group=null) +** Lookup "go for it choice" +** "go for it choice" attempt 1 +=> TOP*0{}.1:0*3?*0 "go for it choice" (group=null) +=> TOP*0{}.1:0*3?*0{} "go for it choice" (group=null) +=> TOP*0{}.1:0*3?*0{}.0 "select by planCode" (group=null) +com.intendico.gorite.ContextualGoal [ABC]Plan: +"go for it" (Main$2$1$1$2) +1 "print $y" (Main$2$1$1$1) +com.intendico.gorite.ContextualGoal [BBB]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [BBB]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +-- noting ralph:TOP*0{}.1:0*3?*0{}.0 "select by planCode" (PASSED) +<= ralph:TOP*0{}.1:0*3?*0{}.0 "select by planCode" (PASSED) +-- noting ralph:TOP*0{}.1:0*3?*0{} "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*3?*0{} "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*3?*0 "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*3?*0 "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*3? "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*3? "go for it choice" (PASSED) +=> TOP*0{}.1:0*3! "go for it" (group=null) +=> TOP*0{}.1:0*3!{$x=BBB} "go for it" (group=null) +>> com.intendico.gorite.examples.planchoice.Main$1: ralph +=> +TOP*0{}.1:0*3!{$x=BBB} "go for it" (group=null) +=> +TOP*0{}.1:0*3!{$x=BBB}.0 "print $x" (group=null) +First: BBB +-- noting ralph:+TOP*0{}.1:0*3!{$x=BBB}.0 "print $x" (FAILED) +<= ralph:+TOP*0{}.1:0*3!{$x=BBB}.0 "print $x" (FAILED) +-- noting ralph:+TOP*0{}.1:0*3!{$x=BBB} "go for it" (FAILED) +<= ralph:+TOP*0{}.1:0*3!{$x=BBB} "go for it" (FAILED) +<< com.intendico.gorite.examples.planchoice.Main$1: ralph +-- noting everyone:TOP*0{}.1:0*3!{$x=BBB} "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*3!{$x=BBB} "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*3! "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*3! "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*3 "go for it choice" (FAILED) +<= everyone:TOP*0{}.1:0*3 "go for it choice" (FAILED) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +** "go for it" attempt 5 +=> TOP*0{}.1:0*4 "go for it choice" (group=null) +=> TOP*0{}.1:0*4? "go for it choice" (group=null) +** Lookup "go for it choice" +** "go for it choice" attempt 1 +=> TOP*0{}.1:0*4?*0 "go for it choice" (group=null) +=> TOP*0{}.1:0*4?*0{} "go for it choice" (group=null) +=> TOP*0{}.1:0*4?*0{}.0 "select by planCode" (group=null) +com.intendico.gorite.ContextualGoal [ABC]Plan: +"go for it" (Main$2$1$1$2) +1 "print $y" (Main$2$1$1$1) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [BBB]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +-- noting ralph:TOP*0{}.1:0*4?*0{}.0 "select by planCode" (PASSED) +<= ralph:TOP*0{}.1:0*4?*0{}.0 "select by planCode" (PASSED) +-- noting ralph:TOP*0{}.1:0*4?*0{} "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*4?*0{} "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*4?*0 "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*4?*0 "go for it choice" (PASSED) +-- noting ralph:TOP*0{}.1:0*4? "go for it choice" (PASSED) +<= ralph:TOP*0{}.1:0*4? "go for it choice" (PASSED) +=> TOP*0{}.1:0*4! "go for it" (group=null) +=> TOP*0{}.1:0*4!{$y=BBB} "go for it" (group=null) +>> com.intendico.gorite.examples.planchoice.Main$1: ralph +=> +TOP*0{}.1:0*4!{$y=BBB} "go for it" (group=null) +=> +TOP*0{}.1:0*4!{$y=BBB}.0 "print $y" (group=null) +Second: BBB +-- noting ralph:+TOP*0{}.1:0*4!{$y=BBB}.0 "print $y" (FAILED) +<= ralph:+TOP*0{}.1:0*4!{$y=BBB}.0 "print $y" (FAILED) +-- noting ralph:+TOP*0{}.1:0*4!{$y=BBB} "go for it" (FAILED) +<= ralph:+TOP*0{}.1:0*4!{$y=BBB} "go for it" (FAILED) +<< com.intendico.gorite.examples.planchoice.Main$1: ralph +-- noting everyone:TOP*0{}.1:0*4!{$y=BBB} "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*4!{$y=BBB} "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*4! "go for it" (FAILED) +<= everyone:TOP*0{}.1:0*4! "go for it" (FAILED) +-- noting everyone:TOP*0{}.1:0*4 "go for it choice" (FAILED) +<= everyone:TOP*0{}.1:0*4 "go for it choice" (FAILED) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +** "go for it" attempt 6 +=> TOP*0{}.1:0*5 "go for it choice" (group=null) +=> TOP*0{}.1:0*5? "go for it choice" (group=null) +** Lookup "go for it choice" +** "go for it choice" attempt 1 +=> TOP*0{}.1:0*5?*0 "go for it choice" (group=null) +=> TOP*0{}.1:0*5?*0{} "go for it choice" (group=null) +=> TOP*0{}.1:0*5?*0{}.0 "select by planCode" (group=null) +com.intendico.gorite.ContextualGoal [ABC]Plan: +"go for it" (Main$2$1$1$2) +1 "print $y" (Main$2$1$1$1) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +com.intendico.gorite.ContextualGoal [AAA]Plan: +"go for it" (TransferGoal) +-- noting ralph:TOP*0{}.1:0*5?*0{}.0 "select by planCode" (FAILED) +<= ralph:TOP*0{}.1:0*5?*0{}.0 "select by planCode" (FAILED) +-- noting ralph:TOP*0{}.1:0*5?*0{} "go for it choice" (FAILED) +<= ralph:TOP*0{}.1:0*5?*0{} "go for it choice" (FAILED) +-- noting ralph:TOP*0{}.1:0*5?*0 "go for it choice" (FAILED) +<= ralph:TOP*0{}.1:0*5?*0 "go for it choice" (FAILED) +-- noting ralph:TOP*0{}.1:0*5? "go for it choice" (FAILED) +<= ralph:TOP*0{}.1:0*5? "go for it choice" (FAILED) +-- noting everyone:TOP*0{}.1:0*5 "go for it choice" (FAILED) +<= everyone:TOP*0{}.1:0*5 "go for it choice" (FAILED) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +~(context)> (com.intendico.gorite.examples.planchoice.Main$1: ralph) +<(context)~ (com.intendico.gorite.examples.planchoice.Main$1: ralph) +** "go for it" attempt 7 +=> TOP*0{}.1:0*6 "go for it choice" (group=null) +=> TOP*0{}.1:0*6? "go for it choice" (group=null) +** Lookup "go for it choice" +** "go for it choice" attempt 1 +=> TOP*0{}.1:0*6?*0 "go for it choice" (group=null) +=> TOP*0{}.1:0*6?*0{} "go for it choice" (group=null) +=> TOP*0{}.1:0*6?*0{}.0 "select by planCode" (group=null) +-- noting ralph:TOP*0{}.1:0*6?*0{}.0 "select by planCode" (FAILED) +<= ralph:TOP*0{}.1:0*6?*0{}.0 "select by planCode" (FAILED) +-- noting ralph:TOP*0{}.1:0*6?*0{} "go for it choice" (FAILED) +<= ralph:TOP*0{}.1:0*6?*0{} "go for it choice" (FAILED) +-- noting ralph:TOP*0{}.1:0*6?*0 "go for it choice" (FAILED) +<= ralph:TOP*0{}.1:0*6?*0 "go for it choice" (FAILED) +-- noting ralph:TOP*0{}.1:0*6? "go for it choice" (FAILED) +<= ralph:TOP*0{}.1:0*6? "go for it choice" (FAILED) +-- noting everyone:TOP*0{}.1:0*6 "go for it choice" (FAILED) +<= everyone:TOP*0{}.1:0*6 "go for it choice" (FAILED) +-- noting everyone:TOP*0{}.1:0 "go for it" (FAILED) +<= everyone:TOP*0{}.1:0 "go for it" (FAILED) +-- noting everyone:TOP*0{}.1 "go for it" (FAILED) +<= everyone:TOP*0{}.1 "go for it" (FAILED) +-- noting everyone:TOP*0{} "go for it" (FAILED) +<= everyone:TOP*0{} "go for it" (FAILED) +-- noting everyone:TOP*0 "go for it" (FAILED) +<= everyone:TOP*0 "go for it" (FAILED) +-- noting everyone:TOP "go for it" (FAILED) +<= everyone:TOP "go for it" (FAILED) +** runPerformersBlocked +** runPerformersOnce +-- everyone running 0 +** runPerformersOnce stopped false +** runPerformersBlocked done something false diff --git a/examples/ruleset/Main.java b/examples/ruleset/Main.java new file mode 100644 index 0000000..5580fa2 --- /dev/null +++ b/examples/ruleset/Main.java @@ -0,0 +1,43 @@ +package examples.ruleset; + +import com.intendico.data.*; +import com.intendico.data.addon.*; +import java.util.Vector; + +public class Main { + + // Missing constructor + static Rule asRule(Vector v) { + return new Rule( (Query) v.get( 0 ), (Query) v.get( 1 ) ); + } + + /** + * Application main. + */ + static public void main(String [] args) { + Language logic = Language.getLanguage(); + //logic.trace = -1; + + // Define the belief structures + Vector kb = new Vector(); + kb.add( new Relation( "P", 1 ) ); + + // Define the theory + RuleSet rs = new RuleSet(); + rs.add( asRule( logic.textToRule( "P($x) => Not P($x)", kb ) ) ); + rs.add( asRule( logic.textToRule( "Lost P($x) => P($x)", kb ) ) ); + + // Add a belief + logic.textToQuery( "P(1)", kb, null, null ).add(); + + // Tell the start state + System.out.println( kb ); + System.out.println( rs ); + + // Run infer as per the theory + rs.run(); + + // Tell the result + System.out.println( kb ); + } +} diff --git a/overview.html b/overview.html new file mode 100644 index 0000000..3df552b --- /dev/null +++ b/overview.html @@ -0,0 +1,6 @@ + + GORITE 10.5 + +

GORITE 10.5

+ + -- 2.39.2