/interpreter/tags/reactive-pattern-matching/src/edu/vub/at/signals/natives/NATSignal.java
http://ambienttalk.googlecode.com/ · Java · 260 lines · 131 code · 50 blank · 79 comment · 14 complexity · d34b339d73c347e1137f2726f868e318 MD5 · raw file
- /**
- * AmbientTalk/2 Project
- * NATSignal.java created on Apr 14, 2008
- * (c) Programming Technology Lab, 2006 - 2007
- * Authors: Tom Van Cutsem & Stijn Mostinckx
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
- package edu.vub.at.signals.natives;
- import java.lang.reflect.Method;
- import java.util.HashSet;
- import java.util.Iterator;
- import edu.vub.at.actors.natives.ELActor;
- import edu.vub.at.eval.Evaluator;
- import edu.vub.at.exceptions.InterpreterException;
- import edu.vub.at.exceptions.XIllegalOperation;
- import edu.vub.at.objects.ATMessage;
- import edu.vub.at.objects.ATNil;
- import edu.vub.at.objects.ATNumber;
- import edu.vub.at.objects.ATObject;
- import edu.vub.at.objects.ATTable;
- import edu.vub.at.objects.natives.NATMethodInvocation;
- import edu.vub.at.objects.natives.NATNumber;
- import edu.vub.at.objects.natives.NATTable;
- import edu.vub.at.objects.natives.NativeATObject;
- import edu.vub.at.signals.ATSignal;
- import edu.vub.at.signals.SignalListener;
- import edu.vub.at.signals.eval.DataflowEngine;
- import edu.vub.at.signals.eval.EncapsulatingSignal;
- import edu.vub.at.util.logging.Logging;
- /**
- * NATSignal is the abstract root class of all AmbientTalk signals. It implements the complete
- * SignalListener interface and provides the required support to ensure that events originating
- * from a (primitive) event source outside of AmbientTalk's event loop are properly translated
- * into asynchronous messages.
- *
- * Additionally, the signal keeps track of the context in which it was created and ensures that
- * if it was created in a reactive activation, it is properly unhooked before the activation is
- * triggered anew.
- *
- * @author smostinc
- */
- public abstract class NATSignal extends NativeATObject implements ATSignal, SignalListener {
-
- private int itsDependencyHeight;
- private boolean hasBeenUnhooked;
- private ELActor itsOwner;
- protected HashSet itsDependents = new HashSet();
- protected final ATObject itsConsumer;
-
- protected NATSignal(int initialDependencyHeight) {
- this(initialDependencyHeight, null);
- }
-
- protected NATSignal(int initialDependencyHeight, ATObject theConsumer) {
- // 1) Set the object that will consume all incoming signal updates
-
- itsConsumer = theConsumer;
-
- // 2) If this listener is created in the context of a reactive activation, the height
- // of the encapsulating activation should be taken into account, when determining the
- // stratum in which this signal listener should be allocated.
-
- DataflowEngine theEngine = Evaluator.getDataflowEngine();
- EncapsulatingSignal theEncapsulator = theEngine.getEncapsulatingSignal();
-
- int theEncapsulatorsDependencyHeight = theEncapsulator!=null ? theEncapsulator.dependencyHeight() : 0;
-
- itsDependencyHeight = Math.max(initialDependencyHeight, theEncapsulatorsDependencyHeight);
-
- // 3) If this listener is created in the context of a reactive activation, updates to
- // that activation imply that this listener becomes invalid and should be unhooked.
-
- hasBeenUnhooked = false;
-
- if(theEncapsulator != null) theEncapsulator.registerDependency(this);
-
- // 4) To properly synchronize signal updates, external event sources should notify us
- // of updates using asynchronous message sends. To distinguish between internal and
- // and external event sources, we keep track of the actor that created the listener.
- // Updates originating from another process can then be considered external.
-
- itsOwner = ELActor.currentActor();
- }
-
- public int dependencyHeight() {
- return itsDependencyHeight;
- }
-
- public ATNumber meta_dependencyHeight() {
- return NATNumber.atValue(itsDependencyHeight);
- }
-
- public void updateDependencyHeight(int newDependencyHeight) {
- Logging.ReactiveProgramming_LOG.debug("Updating stratum of signal: " + this + " from: " + dependencyHeight() + " to: " + newDependencyHeight);
- // Conceptually, updating the height could be as simple as this :
- // itsDependencyHeight = Math.max(itsDependencyHeight, newDependencyHeight);
-
- // However, it is important to ensure that if this listener has been enqueued for
- // recomputation, that it occurs in the correct stratum to avoid update glitches.
- if(newDependencyHeight > itsDependencyHeight) {
- itsDependencyHeight = newDependencyHeight;
-
- DataflowEngine theEngine = Evaluator.getDataflowEngine();
-
- // This ensures that the updated dependency height is reflected in the queue
- // that is kept by the dataflow engine.
- theEngine.reprioritize(this);
-
- // Notify all dependent signals
- for(Iterator dependentComputationIt = itsDependents.iterator();
- dependentComputationIt.hasNext();) {
- SignalListener currentDependentComputation = (SignalListener) dependentComputationIt.next();
-
- // The new height of all dependent computations should at least be our own
- // dependency height plus one.
- currentDependentComputation.updateDependencyHeight(itsDependencyHeight + 1);
- }
- }
- }
-
-
- // Dependency Management
-
- public ATNil meta_addDependent(SignalListener dependent) throws InterpreterException {
- if(dependent.dependencyHeight() > this.dependencyHeight()) {
- itsDependents.add(dependent);
- } else {
- throw new XIllegalOperation("Attempted to add a dependent signal with a lower dependency height than my own");
- }
-
- return Evaluator.getNil();
- }
- public ATNil meta_removeDependent(SignalListener dependent) throws InterpreterException {
- itsDependents.remove(dependent);
-
- return Evaluator.getNil();
- }
-
- public void unhook() {
- hasBeenUnhooked = true;
-
- itsDependents.clear();
- }
-
- // Update Handling
-
- /**
- * This method is declared final, since subclasses should override the protected method
- * inner_scheduleUpdate instead of overriding this method directly.
- */
- public final void scheduleUpdate(ATSignal theParent, ATMessage theUpdate) {
- if( !shouldSynchronize(theParent, theUpdate))
- inner_scheduleUpdate(theParent, theUpdate);
- }
-
- protected void inner_scheduleUpdate(ATSignal theParent, ATMessage theUpdate) {
- DataflowEngine theEngine = Evaluator.getDataflowEngine();
-
- theEngine.schedulePendingUpdate(theParent, this, theUpdate);
- }
- /**
- * This method is declared final, since subclasses should override the protected method
- * inner_performUpdate instead of overriding this method directly.
- */
- public final boolean performUpdate(ATSignal theParent, ATMessage theUpdate) {
- if(!hasBeenUnhooked)
- inner_performUpdate(theParent, theUpdate);
- return !hasBeenUnhooked;
- }
-
- protected void inner_performUpdate(ATSignal theParent, ATMessage theUpdate) {
- try {
- if(itsConsumer == null) {
- propagateNewMessage(theUpdate);
- } else {
- theUpdate.base_sendTo(itsConsumer, theParent);
- }
- } catch (InterpreterException e) {
- // Could not send the message succesfully
- e.printStackTrace();
- }
- };
-
- // Auxiliary Methods
-
- /**
- * This method tests whether the signal update, that has to be scheduled, originates from
- * another process. If the update is external, this method will schedule an asynchronous
- * message in the owning actors event loop.
- */
- private boolean shouldSynchronize(ATSignal theParent, ATMessage theUpdate) {
- try {
- // Update is called from an external Thread : Synchronization is needed here
- if(Thread.currentThread() != itsOwner.processor()) {
- // Get the method to schedule a signal update
- Method update = SignalListener.class.getDeclaredMethod(
- "scheduleUpdate", new Class[] { ATSignal.class, ATMessage.class });
-
- // And schedule it for execution within the owning event loop
- itsOwner.event_symbioticInvocation(this,
- update, new ATObject[] { theParent, theUpdate });
-
- return true;
- }
- } catch (Exception e) {
- // Could not select the method from this object;
- e.printStackTrace();
- }
-
- return false;
- }
-
- protected void propagateNewMessage(ATMessage updateMessage) {
- for(Iterator dependentComputationIt = itsDependents.iterator();
- dependentComputationIt.hasNext();) {
- SignalListener currentDependentComputation = (SignalListener) dependentComputationIt.next();
-
- currentDependentComputation.scheduleUpdate(this, updateMessage);
- }
- }
-
- protected void propagateNewValues(ATTable computedValues) throws InterpreterException {
- propagateNewMessage(new NATMethodInvocation(
- Evaluator._APPLY_,
- NATTable.of(computedValues),
- NATTable.EMPTY));
- }
-
- protected void propagateNewValue(ATObject computedValue) throws InterpreterException {
- propagateNewValues(NATTable.of(computedValue));
- }
- }