/* * Copyright 2001-2008 Geert Bevin <gbevin[remove] at uwyn dot com> * Licensed under the Apache License, Version 2.0 (the "License") * $Id: ContinuationContext.java 3928 2008-04-22 16:25:18Z gbevin $ */ package com.uwyn.rife.continuations; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import com.uwyn.rife.continuations.exceptions.ContinuableLocalVariableUncloneableException; import com.uwyn.rife.tools.ExceptionUtils; import com.uwyn.rife.tools.JavaSpecificationUtils; import com.uwyn.rife.tools.UniqueIDGenerator; /** * Contains all contextual data of one particular continuation. * <p>It also provides some static retrieval methods to be able to access * active continuations. * <p>Active continuations are managed in a {@link ContinuationManager} so that * they can be easily retrieved. * * @author Geert Bevin (gbevin[remove] at uwyn dot com) * @version $Revision: 3928 $ * @see ContinuationManager * @since 1.6 */ public class ContinuationContext<T extends ContinuableObject> implements Cloneable { private final static transient ThreadLocal<ContinuationContext> ACTIVE_CONTEXT = new ThreadLocal<ContinuationContext>(); private final static transient ThreadLocal<WeakReference<ContinuationContext>> LAST_CONTEXT = new ThreadLocal<WeakReference<ContinuationContext>>(); private transient ContinuationManager mManager = null; private T mContinuable = null; private CallState mCreatedCallState = null; private CallState mActiveCallState = null; private Object mCallAnswer = null; private String mId = null; private String mParentId = null; private List<String> mRelatedIds = null; private long mStart = -1; private int mLabel = -1; private boolean mPaused = false; private ContinuationStack mLocalVars = null; private ContinuationStack mLocalStack = null; /** * [PRIVATE AND UNSUPPORTED] Creates a new continuation context or resets * its expiration time. * <p>This method is used by the instrumented bytecode that provides * continuations support, it's not intended for general use. * * @return a new {@code ContinuationContext}, or the active one with * its expiration time being reset * @since 1.6 */ public static ContinuationContext createOrResetContext(Object executingInstance) { ContinuationContext context = getActiveContext(); if (null == context) { ContinuationConfigRuntime config = ContinuationConfigRuntime.getActiveConfigRuntime(); ContinuableObject continuable = config.getAssociatedContinuableObject(executingInstance); context = new ContinuationContext(config.getContinuationManager(continuable), continuable); // check if the last continuation created a call continuation, in that case // pass the call state on to this new continuation ContinuationContext last_context = getLastContext(); if (last_context != null) { CallState call_state = null; if (last_context.getCreatedCallState() != null) { call_state = last_context.getCreatedCallState(); } else { call_state = last_context.getActiveCallState(); } if (call_state != null) { context.setActiveCallState(call_state); } } } else { context.resetStart(); } setActiveContext(context); // preserve a reference to the last executed continuation, so that it's // possible to detect call continuations LAST_CONTEXT.set(new WeakReference<ContinuationContext>(context)); return context; } /** * Clears the active currently continuation context for the executing thread. * * @since 1.6 */ public static void clearActiveContext() { if (JavaSpecificationUtils.isAtLeastJdk15()) { ACTIVE_CONTEXT.remove(); } else { ACTIVE_CONTEXT.set(null); } } /** * Retrieves the identifier of the currently active continuation for the * current thread. * * @return the identifier of the currently active continuation as a unique * string; or * <p>{@code null} if no continuation is currently active * @see #getActiveContext * @since 1.6 */ public static String getActiveContextId() { ContinuationContext context = ACTIVE_CONTEXT.get(); if (null == context) { return null; } return context.getId(); } /** * Retrieves the currently active continuation for the executing thread. * * @return the currently active continuation; or * <p>{@code null} if no continuation is currently active * @see #getActiveContextId * @since 1.6 */ public static ContinuationContext getActiveContext() { return ACTIVE_CONTEXT.get(); } /** * Replaces the active continuation context for the executing thread. * * @param context the new {@code ContinuationContext} that will be active; or * {@code null} if no continuation context should be active * @see #setActiveContext * @since 1.6 */ public static void setActiveContext(ContinuationContext context) { ACTIVE_CONTEXT.set(context); } /** * Retrieves the last active continuation for the executing thread. * * @return the last active continuation; or * <p>{@code null} if no continuation was active * @since 1.6 */ public static ContinuationContext getLastContext() { WeakReference<ContinuationContext> reference = LAST_CONTEXT.get(); if (reference != null) { return reference.get(); } return null; } private ContinuationContext(ContinuationManager manager, T continuable) { mManager = manager; mContinuable = continuable; resetId(); resetStart(); mLabel = -1; mLocalVars = new ContinuationStack().initialize(); mLocalStack = new ContinuationStack().initialize(); } /** * Retrieves the manager of this {@code ContinuationContext}. * * @return this continuation's manager instance * @since 1.6 */ public ContinuationManager getManager() { return mManager; } synchronized void setManager(ContinuationManager manager) { mManager = manager; } /** * Registers this continuation in its manager, so that it can be retrieved later. * @since 1.6 */ public void registerContext() { synchronized (mManager) { mManager.addContext(this); } } /** * Makes sure that this {@code ContinuationContext} is not the active * one. * * @since 1.6 */ public void deactivate() { if (this == getActiveContext()) { clearActiveContext(); } } /** * Removes this {@code ContinuationContext} instance from its {@link * ContinuationManager}. * * @since 1.6 */ public synchronized void remove() { mManager.removeContext(mId); deactivate(); } /** * Removes the entire continuation tree that this * {@code ContinuationContext} instance belongs to from its {@link * ContinuationManager}. * * @since 1.6 */ public void removeContextTree() { synchronized (mManager) { mManager.removeContext(mId); if (mRelatedIds != null) { ContinuationContext child; for (String id : mRelatedIds) { child = mManager.getContext(id); if (child != null) { child.removeContextTree(); } } } ContinuationContext parent = getParentContext(); if (parent != null) { parent.removeContextTree(); } deactivate(); } } /** * Retrieves the unique identifier of the parent continuation of this * {@code ContinuationContext} instance. * * @return the parent's identifier as a unique string; or * <p>{@code null} if this {@code ContinuationContext} has no * parent * @see #getParentContext * @since 1.6 */ public String getParentContextId() { return mParentId; } /** * Retrieves the parent {@code ContinuationContext} of this * {@code ContinuationContext} instance. * * @return the parent {@code ContinuationContext}; or * <p>{@code null} if this {@code ContinuationContext} has no * parent * @see #getParentContextId * @since 1.6 */ public ContinuationContext getParentContext() { return mManager.getContext(getParentContextId()); } /** * Retrieves the answer that the call continuation stored in this context. * * @return the call continuation's answer; or * <p>{@code null} if no answer was provided or the corresponding * continuation wasn't a call continuation * @since 1.6 */ public Object getCallAnswer() { return mCallAnswer; } /** * [PRIVATE AND UNSUPPORTED] Sets whether the continuation if paused. * <p>This method is used by the internals that provide continuations * support, it's not intended for general use. * * @param paused {@code true} if the continuation is paused; or * <p>{@code false} otherwise * @see #isPaused() * @since 1.6 */ public synchronized void setPaused(boolean paused) { mPaused = paused; } /** * Indicates whether this continuation is actually paused and can be resumed. * * @return {@code true} if the continuation is paused; or * <p>{@code false} otherwise * @since 1.6 */ public boolean isPaused() { return mPaused; } /** * [PRIVATE AND UNSUPPORTED] Set the number of the bytecode label where * the continuation has to resume execution from. * <p>This method is used by the instrumented bytecode that provides * continuations support, it's not intended for general use. * * @param label the number of the resumed bytecode label * @since 1.6 */ public synchronized void setLabel(int label) { mLabel = label; } /** * [PRIVATE AND UNSUPPORTED] Set the number of the bytecode label where * the continuation has to resume execution from. * <p>This method is used by the instrumented bytecode that provides * continuations support, it's not intended for general use. * * @since 1.6.2 */ public void clearLabel() { setLabel(-1); } /** * [PRIVATE AND UNSUPPORTED] Retrieves the number of the bytecode label * where the continuation has to resume execution from. * <p>This method is used by the instrumented bytecode that provides * continuations support, it's not intended for general use. * * @return the number of the resumed bytecode label; or * <p>{@code -1} if no label number has been set * @since 1.6 */ public int getLabel() { return mLabel; } /** * [PRIVATE AND UNSUPPORTED] Retrieves the local variable stack of this * continuation. * <p>This method is used by the instrumented bytecode that provides * continuations support, it's not intended for general use. * * @return this continuation's local variable stack * @since 1.6 */ public ContinuationStack getLocalVars() { return mLocalVars; } /** * [PRIVATE AND UNSUPPORTED] Retrieves the local operand stack of this * continuation. * <p>This method is used by the instrumented bytecode that provides * continuations support, it's not intended for general use. * * @return this continuation's local operand stack * @since 1.6 */ public ContinuationStack getLocalStack() { return mLocalStack; } private synchronized void resetStart() { mStart = System.currentTimeMillis(); } synchronized void resetId() { mId = UniqueIDGenerator.generate().toString(); } /** * Retrieves the continuation ID. * <p>Note that this ID is not necessarily present in the manager and that * trying to retrieve a continuation afterwards from its ID is never * guaranteed to give a result. * * @return the unique ID of this continuation. * @since 1.6 */ public String getId() { return mId; } /** * Retrieves the ID of this continuation's parent. * * @return the ID of this continuation's parent continuation; or * <p>{@code null} if this continuation has no parent. * @since 1.6 */ public String getParentId() { return mParentId; } /** * [PRIVATE AND UNSUPPORTED] Associates the ID of another continuation to * this continuation. * <p>This method is used by the instrumented bytecode that provides * continuations support, it's not intended for general use. * * @param id the ID of another continuation that's related to this * continuation * @since 1.6 */ public synchronized void addRelatedId(String id) { if (null == mRelatedIds) { mRelatedIds = new ArrayList<String>(); } mRelatedIds.add(id); } /** * [PRIVATE AND UNSUPPORTED] Set the ID of this continuation's parent. * <p>This method is used by the instrumented bytecode that provides * continuations support, it's not intended for general use. * * @param id the ID of this continuation's parent * @see #getParentId() * @since 1.6 */ public synchronized void setParentId(String id) { mParentId = id; } /** * Returns the object instance in which this continuation was executing. * * @return this continuation's active object * @since 1.6 */ public T getContinuable() { return mContinuable; } /** * Sets the call continuation's state when a new call continuation is * created. * <p>This state initiates a call continuation and should be set when * a new call happens, after that it should never be changed. * * @param createdCallState this call continuation's creation state * @see #getCreatedCallState() * @since 1.6 */ public synchronized void setCreatedCallState(CallState createdCallState) { mCreatedCallState = createdCallState; } /** * Retrieves this continuation's call continuation creation state. * <p>If this returns a non-null value, you can detect from it that this * was a call continuation. * * @return this continuation * @see #setCreatedCallState(CallState) * @since 1.6 */ public CallState getCreatedCallState() { return mCreatedCallState; } /** * Sets the active call state for this continuation. * <p>This mainly passes on the call state that was created during a call * continuation. It allows quick retrieval of the active call state when * an answer occurs. * * @param callState the active call state * @see #setCreatedCallState(CallState) * @since 1.6 */ public synchronized void setActiveCallState(CallState callState) { mActiveCallState = callState; } /** * Retrieves the call state that is active during this continuation. * * @return the active {@code CallState}; or * <p>{@code null} if no call state was active for this continuation */ public CallState getActiveCallState() { return mActiveCallState; } long getStart() { return mStart; } /** * Set the answer to a call continuation. * * @param answer the object that will be the call continuation's answer; or * {@code null} if there was no answer * @since 1.6 */ public synchronized void setCallAnswer(Object answer) { mCallAnswer = answer; } /** * [PRIVATE AND UNSUPPORTED] Creates a cloned instance of this * continuation context, this clone is not a perfect copy but is intended * to be a child continuation and all context data is setup for that. * <p>This method is used by the instrumented bytecode that provides * continuations support, it's not intended for general use. * * @return a clone of this continuation for use as a child continuation * @since 1.6 */ public ContinuationContext clone() throws CloneNotSupportedException { ContinuationContext new_continuationcontext = null; try { new_continuationcontext = (ContinuationContext)super.clone(); } ///CLOVER:OFF catch (CloneNotSupportedException e) { // this should never happen Logger.getLogger("com.uwyn.rife.continuations").severe(ExceptionUtils.getExceptionStackTrace(e)); } ///CLOVER:ON new_continuationcontext.mContinuable = (ContinuableObject)mContinuable.clone(); new_continuationcontext.mCallAnswer = null; new_continuationcontext.mId = UniqueIDGenerator.generate().toString(); new_continuationcontext.mParentId = mId; new_continuationcontext.mPaused = false; addRelatedId(new_continuationcontext.mId); try { new_continuationcontext.mLocalVars = mLocalVars.clone(new_continuationcontext.mContinuable); new_continuationcontext.mLocalStack = mLocalStack.clone(new_continuationcontext.mContinuable); } catch (CloneNotSupportedException e) { throw new ContinuableLocalVariableUncloneableException(mContinuable.getClass(), e.getMessage(), e); } return new_continuationcontext; } }