/*************************************************************************** * Copyright (C) by Fabrizio Montesi * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * * For details about the authors of this software, see the AUTHORS file. * ***************************************************************************/ package jolie; import java.lang.ref.WeakReference; import jolie.lang.Constants; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.concurrent.Future; import jolie.net.CommChannelHandler; import jolie.net.SessionMessage; import jolie.process.Process; import jolie.runtime.AbstractIdentifiableObject; import jolie.runtime.FaultException; import jolie.runtime.InputOperation; import jolie.runtime.VariablePath; /** * Represents a JolieThread that is able to resolve a VariablePath, referring to a State. * @see JolieThread * @see VariablePath * @see jolie.State * @author Fabrizio Montesi */ public abstract class ExecutionThread extends JolieThread { /** * A Scope object represents a fault handling scope, * containing mappings for fault handlers and termination/compensation handlers. */ protected class Scope extends AbstractIdentifiableObject implements Cloneable { final private Map< String, Process > faultMap = new HashMap< String, Process >(); final private Map< String, Process > compMap = new HashMap< String, Process >(); @Override public Scope clone() { Scope ret = new Scope( id ); ret.compMap.putAll( compMap ); ret.faultMap.putAll( faultMap ); return ret; } /** * Constructor * @param id the name identifier of the Scope instance to be created. */ public Scope( String id ) { super( id ); } /** * Installs a termination/compensation handler for this <code>Scope</code>. * @param process the termination/compensation handler to install. */ public void installCompensation( Process process ) { compMap.put( id, process ); } /** * Installs a fault handler for a fault. * @param faultName the fault name to install this handler for * @param process the fault handler to install */ public void installFaultHandler( String faultName, Process process ) { faultMap.put( faultName, process ); } /** * Returns the installed fault handler for the specified fault name. * If no fault handler is present, the default fault handler is returned instead. * If there is no fault handler and there is no default fault handler, <code>null</code> is returned. * @param faultName the fault name of the fault handler to retrieve * @param erase <code>true</code> if after getting the fault handler it is to be uninstalled from the scope, <code>false</code> otherwise * @return the installed fault handler for the specified fault name */ public Process getFaultHandler( String faultName, boolean erase ) { Process p = faultMap.get( faultName ); if ( erase ) { // Not called by cH (TODO: this is obscure!) if ( p == null ) { // Give the default handler faultName = Constants.Keywords.DEFAULT_HANDLER_NAME; p = faultMap.get( faultName ); } if ( p != null ) { // Could still be null if there was not a default handler faultMap.remove( faultName ); } } return p; } /** * Returns the termination/compensation handler for this scope. * The handler does not get uninstalled. * @return the termination/compensation handler for this scope */ public Process getSelfCompensation() { return compMap.get( id ); } /** * Returns the termination/compensation handler for the specified sub-scope. * The handler gets uninstalled as a result of this method. * @param scopeName the scope name of the termination/compensation handler to retrieve * @return the termination/compensation handler for the specified sub-scope */ public Process getCompensation( String scopeName ) { Process p = compMap.get( scopeName ); if ( p != null ) compMap.remove( scopeName ); return p; } /** * Puts all the compensation handlers defined in the passed <code>Scope</code> in the handler map of this scope. * @param otherScope the scope whose compensation handlers are to be taken */ public void mergeCompensations( Scope otherScope ) { compMap.putAll( otherScope.compMap ); } } protected final Process process; protected final Stack< Scope > scopeStack = new Stack< Scope >(); protected final ExecutionThread parent; private final List< WeakReference< Future< ? > > > futureToCancel = new LinkedList< WeakReference< Future< ? > > >(); private boolean canBeInterrupted = false; private FaultException killerFault = null; /** * Sets if this thread can be interrupted by a fault signal or not. */ public void setCanBeInterrupted( boolean b ) { canBeInterrupted = b; } /** * Constructor * @param process the Process to be executed by this thread * @param parent the parent of this thread */ public ExecutionThread( Process process, ExecutionThread parent ) { super( parent.interpreter() ); this.process = process; this.parent = parent; } /** * Constructor * @param interpreter the Interpreter this thread should refer to * @param process the Process to be executed by this thread */ public ExecutionThread( Interpreter interpreter, Process process ) { super( interpreter ); this.process = process; this.parent = null; } /** * Kills this ExecutionThread, interrupting its activity as soon as possible. * @param fault the fault causing the interruption. */ public void kill( FaultException fault ) { killerFault = fault; WeakReference< Future< ? > > ref; while( !futureToCancel.isEmpty() ) { ref = futureToCancel.get( 0 ); if ( ref.get() != null ) { ref.get().cancel( true ); } futureToCancel.remove( 0 ); } if( canBeInterrupted ) { interrupt(); } } /** * Returns the fault which killed this thread, if any. null otherwise. * @return the fault which killed this thread, if any. null otherwise. */ public FaultException killerFault() { return killerFault; } /** * Resets the killed state of this thread, returning it to normal execution. */ public void clearKill() { killerFault = null; } /** * Returns true if this thread is killed, false otherwise. * @return true if this thread is killed, false otherwise. */ public boolean isKilled() { return (killerFault != null); } @Override public abstract void run(); /** * Returns the compensator of the current executing scope. * @return the compensator of the current executing scope. */ public synchronized Process getCurrentScopeCompensation() { if( scopeStack.empty() && parent != null ) { return parent.getCurrentScopeCompensation(); } return scopeStack.peek().getSelfCompensation(); } /** * Returns the compensator for scope name id. * @param id the scope name owning the compensator to retrieve * @return the compensator for scope name id. */ public synchronized Process getCompensation( String id ) { if ( scopeStack.empty() && parent != null ) { return parent.getCompensation( id ); } return scopeStack.peek().getCompensation( id ); } /** * Returns true if this thread is executing inside a scope. * Use this method to check if calling a variant of popScope is safe. * @see #popScope() * @see #popScope(boolean) * @return true if this thread is executing inside a scope. */ public synchronized boolean hasScope() { return !scopeStack.empty(); } /** * Returns the id of the current executing scope. * @return the id of the current executing scope. */ public synchronized String currentScopeId() { if( scopeStack.empty() && parent != null ) { return parent.currentScopeId(); } return scopeStack.peek().id(); } /** * Registers a future to be cancelled when this thread is killed. * @param f the future to cancel */ public synchronized void cancelIfKilled( Future< ? > f ) { cleanFuturesToKill(); if ( isKilled() ) { f.cancel( true ); } futureToCancel.add( new WeakReference< Future< ? > >( f ) ); } private void cleanFuturesToKill() { WeakReference< Future< ? > > ref; boolean keepAlive = true; while( !futureToCancel.isEmpty() && keepAlive ) { ref = futureToCancel.get( 0 ); if ( ref.get() == null ) { futureToCancel.remove( 0 ); } else { keepAlive = false; } } } /** * Returns the current fault handler for fault id. * @param id the id of the fault handler to retrieve. * @param erase <code>true</code> if the fault handler should be * removed before returning it. * @return the current fault handler for fault id. */ public synchronized Process getFaultHandler( String id, boolean erase ) { if ( scopeStack.empty() && parent != null ) { return parent.getFaultHandler( id, erase ); } return scopeStack.peek().getFaultHandler( id, erase ); } /** * Pushes scope id as the new current executing scope in the scope stack of this thread. * @param id the id of the scope to push. */ public synchronized void pushScope( String id ) { scopeStack.push( new Scope( id ) ); } /** * Pops the current executing scope from the scope stack of this thread. * @param merge <code>true</code> if the popped scope compensators * should be propagated upstream to the parent scope. */ public synchronized void popScope( boolean merge ) { Scope s = scopeStack.pop(); if ( merge ) { mergeCompensations( s ); } } /** * Pops the current executing scope from the scope stack of this thread. * This method is a shortcut for <code>popScope(true)</code>. */ public synchronized void popScope() { popScope( true ); } private synchronized void mergeCompensations( Scope s ) { if ( scopeStack.empty() ) { if ( parent != null ) parent.mergeCompensations( s ); } else scopeStack.peek().mergeCompensations( s ); } /** * Installs process as the compensator for the current scope. * @param process the process to install as compensator for the current scope */ public synchronized void installCompensation( Process process ) { if ( scopeStack.empty() && parent != null ) parent.installCompensation( process ); else scopeStack.peek().installCompensation( process ); } /** * Installs process as the fault handler for fault id. * @param id the fault to be handled by process * @param process the Process to be called for handling fault id */ public synchronized void installFaultHandler( String id, Process process ) { if ( scopeStack.empty() && parent != null ) { parent.installFaultHandler( id, process ); } else { scopeStack.peek().installFaultHandler( id, process ); } } /** * Returns the ExecutionThread the current thread should refer to. * This method can be useful, e.g., for resolving VariablePaths outside the * execution of an ExecutionThread. * @return the ExecutionThread the current thread should refer to. */ public static ExecutionThread currentThread() { Thread currThread = Thread.currentThread(); if ( currThread instanceof ExecutionThread ) { return ((ExecutionThread) currThread); } else if ( currThread instanceof CommChannelHandler ) { return ((CommChannelHandler)currThread).executionThread(); } return null; } /** * Returns the State this ExecutionThread refers to. * @return the State this ExecutionThread refers to * @see jolie.State */ public abstract jolie.State state(); /** * Requests a message from the currently executing session. * @param operation the operation on which the process wants to receive the message * @return a {@link Future} that will return the received message. */ public abstract Future< SessionMessage > requestMessage( InputOperation operation, ExecutionThread ethread ); /** * Requests a message from the currently executing session. * @param operations the map of possible operations on which the process wants to receive the message * @return a {@link Future} that will return the received message. */ public abstract Future< SessionMessage > requestMessage( Map< String, InputOperation > operations, ExecutionThread ethread ); protected Process process() { return process; } public abstract String getSessionId(); }