/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * ExecutionContextImpl.java * Creation date: Oct 23, 2006. * By: Edward Lam */ package org.openquark.cal.internal.runtime; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.concurrent.CopyOnWriteArraySet; import java.util.regex.Pattern; import org.openquark.cal.foreignsupport.module.Prelude.UnitValue; import org.openquark.cal.runtime.CalFunction; import org.openquark.cal.runtime.CalValue; import org.openquark.cal.runtime.Cleanable; import org.openquark.cal.runtime.DebugSupport; import org.openquark.cal.runtime.ExecutionContext; import org.openquark.cal.runtime.ExecutionContextProperties; import org.openquark.cal.runtime.ResourceAccess; /** * See the superclass {@link ExecutionContext Javadoc} for a basic intro. * * @author Bo Ilic, Raymond Cypher */ public abstract class ExecutionContextImpl extends ExecutionContext { ////////////////////////////////////////////////////////////////////// //tracing related fields /** * true if function tracing information should be printed to the console. * If the the given machine configuration is not capable of tracing this flag has no effect. * Marked as volatile since the execution context may be shared by multiple threads in concurrent mode. */ private volatile boolean tracingEnabled; /** * true if function tracing should also show the name of the thread that is evaluating the given function. * If the the given machine configuration is not capable of tracing this flag has no effect. * Marked as volatile since the execution context may be shared by multiple threads in concurrent mode. */ private volatile boolean traceShowsThreadName = true; /** * true if function tracing should also show the arguments of the function being traced. This has the effect of * calling DebugSupport.showInternal on the arguments and does not modify the program evaluation state. * If the the given machine configuration is not capable of tracing this flag has no effect. * Marked as volatile since the execution context may be shared by multiple threads in concurrent mode. */ private volatile boolean traceShowsFunctionArgs = true; /** * When there is some text that is to be traced, and patterns is non-empty, then that text will * be traced only if it matches one of the patterns. * <P> * The algorithm is copy on write. The list in patterns is never to be modified. * When the list is changed a new list must be saved in patterns. * This field must be marked as volatile (or have access to it synchronized) even though read/writes of * references are guaranteed to be atomic. See Effective Java item 48 pg 191. * The only changer is the UI so there is only one thread doing writes. The list must be accessed by getting * the value of the list and saving it locally and then performing operations on the saved value. Item 48 * allows this to function property. */ private volatile List<Pattern> patterns = Collections.<Pattern>emptyList(); /** * The qualified names of functions for which tracing has been * specifically enabled. Since we have stated that an ExecutionContext cannot * be accessed by two threads simultaneously we don't need to synchronize the set. * <P> * The algorithm is copy on write. The list in tracedFunctions is never to be modified. * When the list is changed a new list must be saved in tracedFunctions. * This field must be marked as volatile (or have access to it synchronized) even though read/writes of * references are guaranteed to be atomic. See Effective Java item 48 pg 191. * The only changer is the UI so there is only one thread doing writes. The list must be accessed by getting * the value of the list and saving it locally and then performing operations on the saved value. Item 48 * allows this to function property. */ private volatile Set<String> tracedFunctions = Collections.<String>emptySet();; ////////////////////////////////////////////////////////////////////// //fundamental (non-debugging or tracing) fields /** * Provides access to information about entities in the workspace as needed to run the given CAL program. * In the case of non-standalone JARs, this is a wrapper on a Program and ResourceAccess objects. In the case of * Standalone JARs, it is something less- just sufficient to get the information needed to support the lookup or * reflection-like capabilities supported by Standalone JARs. */ private RuntimeEnvironment runtimeEnvironment; /** * The ExecutionContextProperties instance encapsulating an immutable map of key-value pairs which is exposed * as system properties from within CAL. */ private final ExecutionContextProperties properties; /** * A List of Cleanables representing the cleanup hooks that have been registered by CAL code to be run whenever * the execution context is cleaned up (explicitly via cleanup() or Program.resetCachedResults()). * * This can be modified by calls to System.registerCleanable, and so access must be synchronized since * the execution context can be used by multiple threads in concurrent mode. */ private final List<Cleanable> cleanables = new Vector<Cleanable>(); ///////////////////////////////////////////////////////////////////// //debuging fields /** The debug controller. */ private final DebugController debugController = new DebugController(); /** Set of String: qualified names of functions and data constructors to break on. * * The algorithm is copy on write. The list in breakOnFunctions is never to be modified. * When the list is changed a new list must be saved in breakOnFunctions. * This field must be marked as volatile (or have access to it synchronized) even though read/writes of * references are guaranteed to be atomic. See Effective Java item 48 pg 191. * The only changer is the UI so there is only one thread doing writes. The list must be accessed by getting * the value of the list and saving it locally and then performing operations on the saved value. Item 48 * allows this to function property. */ private volatile Set<String> breakOnFunctions = Collections.<String>emptySet(); /** * Flag indicating that Execution should proceed in a stepwise fashion. i.e. suspend on * each function or data constructor. */ private volatile boolean stepping; /** * Indicates that all threads should be stepped. This is only checked when {@link #stepping} is true. */ private volatile boolean stepAllThreads; /** * The set of threads that should be stepped. This is a copy-on-write set, and is thread-safe for all operations. * This set is only checked when {@link #stepping} is true and {@link #stepAllThreads} is false. */ private final Set<Thread> threadsToBeStepped = new CopyOnWriteArraySet<Thread>(); /** * Constructor for this abstract base class. * * @param properties * the ExecutionContextProperties instance encapsulating an immutable map of * key-value pairs which is exposed as system properties from within CAL. */ protected ExecutionContextImpl(ExecutionContextProperties properties) { this(properties, null); } /** * Constructor for this abstract base class. * * @param properties * the ExecutionContextProperties instance encapsulating an immutable map of * key-value pairs which is exposed as system properties from within CAL. * @param runtimeEnvironment */ protected ExecutionContextImpl(ExecutionContextProperties properties, RuntimeEnvironment runtimeEnvironment) { if (properties == null) { throw new NullPointerException(); } this.properties = properties; this.runtimeEnvironment = runtimeEnvironment; } /** * Check if debug processing is needed for the named function. * @param functionName * @return true if debug processing is required for the named function * If the the given machine configuration is not capable of debugging this flag has no effect. */ public final boolean isDebugProcessingNeeded (String functionName) { final Set<String> tracedFunctions;//needed for thread safety, since this.tracedFunctions may change... final Set<String> breakOnFunctions; return isTracingEnabled() || ((tracedFunctions = this.tracedFunctions).size() > 0 && tracedFunctions.contains(functionName)) || stepping || ((breakOnFunctions = this.breakOnFunctions).size() > 0 && breakOnFunctions.contains(functionName)); } public final void debugProcessing (String functionName, CalValue[] argValues) { // Do trace if necessary final Set<String> tracedFunctions; //needed for thread safety, since this.tracedFunctions may change... if (isTracingEnabled() || ((tracedFunctions = this.tracedFunctions).size() > 0 && tracedFunctions.contains(functionName))) { final StringBuilder $sb; if (traceShowsThreadName()) { $sb = new StringBuilder(Thread.currentThread().getName()).append("> "); } else { $sb = new StringBuilder(); } $sb.append(functionName); if (traceShowsFunctionArgs()) { $sb.append(DebugSupport.showInternalForArgumentValues( argValues)); } trace($sb.toString()); } // Do suspend if necessary final Set<String> breakOnFunctions; if (stepping) { if (stepAllThreads || threadsToBeStepped.contains(Thread.currentThread())) { suspendInternal(functionName, argValues); } } else if ((breakOnFunctions = this.breakOnFunctions).size() > 0 && breakOnFunctions.contains(functionName)) { suspendInternal(functionName, argValues); } } /** * @return true if function tracing information should be printed to the console. * If the the given machine configuration is not capable of tracing this flag has no effect. */ public final boolean isTracingEnabled() { return tracingEnabled; } public final void setTracingEnabled(final boolean tracingEnabled) { this.tracingEnabled = tracingEnabled; } /** * @return true if function tracing should also show the name of the thread that is evaluating the given function. * If the the given machine configuration is not capable of tracing this flag has no effect. */ public final boolean traceShowsThreadName() { return traceShowsThreadName; } public final void setTraceShowsThreadName(boolean traceShowsThreadName) { this.traceShowsThreadName = traceShowsThreadName; } /** * @return true if function tracing should also show the arguments of the function being traced. This has the effect of * calling DebugSupport.showInternal on the arguments and does not modify the program evaluation state. * If the the given machine configuration is not capable of tracing this flag has no effect. */ public final boolean traceShowsFunctionArgs() { return traceShowsFunctionArgs; } public final void setTraceShowsFunctionArgs(boolean traceShowsFunctionArgs) { this.traceShowsFunctionArgs = traceShowsFunctionArgs; } /** * Set the list of regular expressions to use for filtering trace output. A copy of the * list is made. The regular expression are immutable so the are not copied but rather * directly stored in the list. * * @param patterns The list of regular expression to use for the filter. */ public final void setTraceFilters(List<Pattern> patterns){ if (patterns == null) { throw new NullPointerException("'patterns' cannot be null."); } this.patterns = new ArrayList<Pattern>(patterns); } public final void clearTraceFields() { this.patterns = Collections.<Pattern>emptyList(); } /** * Retrieve an immutable version of the list of regular expressions used to filter * trace output. * @return List - the List of regular expressions used to filter trace output. */ public final List<Pattern> getTraceFilters () { return Collections.unmodifiableList(patterns); } /** * This function is used to print trace messages to the console. * * @param traceMessage The message to be written to the console. */ public final void trace (String traceMessage){ //must assign to a local variable to avoid needing to explicitly synchronize access to patterns. //this is because this.patterns may change between checking the size, and iteration. List<Pattern> patterns = this.patterns; // maintain the original behaviour if (patterns.size() == 0){ System.out.println(traceMessage); return; } // there are some patterns only print out traces that match for(final Pattern pattern : patterns){ if (pattern.matcher(traceMessage).matches()){ System.out.println(traceMessage); } } } /** * Set the RuntimeEnvironment instance in which the execution context is * operating. * @param runtimeEnvironment */ protected final void setRuntimeEnvironment (RuntimeEnvironment runtimeEnvironment) { this.runtimeEnvironment = runtimeEnvironment; } /** * @return the runtime environment. */ protected final RuntimeEnvironment getRuntimeEnvironment () { return runtimeEnvironment; } /** * This function is referenced by Cal.Core.Resource.getResourceAccess. * @return the ResourceAccess instance to provide access to the resources of the current * environment (e.g. from the workspace, or from Eclipse). */ public final ResourceAccess getResourceAccess() { return runtimeEnvironment.getResourceAccess(); } /** * @return the ExecutionContextProperties instance encapsulating an immutable map of key-value pairs which is exposed * as system properties from within CAL. */ public final ExecutionContextProperties getProperties() { return properties; } /** * Retrieves the Class object associated with a CAL foreign type. * * @param qualifiedTypeConsName the fully-qualified type constructor name as a String e.g. "Cal.Core.Prelude.Maybe". * @param foreignName the name of the foreign class as returned by <code>Class.getName()</code>. * @return Class of the foreign type constructor, or null if not a foreign type. * @throws Exception if the class cannot be resolved. */ public final Class<?> getForeignClass(final String qualifiedTypeConsName, final String foreignName) throws Exception { return runtimeEnvironment.getForeignClass(qualifiedTypeConsName, foreignName); } /** * Registers a cleanup hook to be run whenever the execution context is cleaned up * (explicitly via cleanup() or Program.resetCachedResults()). * * @param cleanable the cleanup hook * @throws NullPointerException if the cleanup hook is null. */ public final void registerCleanable(Cleanable cleanable) { if (cleanable == null) { throw new NullPointerException(); } cleanables.add(cleanable); } /** * Registers a cleanup hook to be run whenever the execution context is cleaned up * (explicitly via cleanup() or Program.resetCachedResults()). * The specified CAL function will be called on cleanup. * * @param cleanupFunction the CAL cleanup function * @throws NullPointerException if the cleanup hook is null. */ public final void registerCleanable(final CalFunction /* ()->() */cleanupFunction) { if (cleanupFunction == null) { throw new NullPointerException(); } registerCleanable(new Cleanable() { public void cleanup() { // Invoke the CAL cleanup function. // The argument value will be ingored anyway. cleanupFunction.evaluate(UnitValue.UNIT); } }); } /** * Runs the cleanup hooks that have been registered with this instance, in the order in which they * were originally registered. * * Calling this method has the effect of clearing the list of cleanup hooks afterwards (so that they don't * get run again unless explicitly re-registered). * * This method does not reset cached CAFs. */ @Override public final void cleanup() { synchronized (cleanables) { for (final Cleanable cleanable : cleanables) { cleanable.cleanup(); } cleanables.clear(); } } /** * @return an unmodifiable Set<String> containing the qualified * names of functions for which tracing has been specifically enabled. */ public final Set<String> getTracedFunctions () { return Collections.unmodifiableSet(tracedFunctions); } /** * @param tracedFunctions qualified names of the functions for which tracing * is specifically enabled. Must not be null. The argument set is copied. */ public final void setTracedFunctions(Set<String> tracedFunctions) { if (tracedFunctions == null) { throw new NullPointerException("argument 'tracedFunctions' cannot be null."); } this.tracedFunctions = new HashSet<String>(tracedFunctions); } public final void clearTracedFunctions() { this.tracedFunctions = Collections.<String>emptySet(); } /** * Add a new function to the set of functions for which * tracing is specifically enabled. * Note: this will supersede the general tracing setting. * * Warning: this method is inefficient for adding multiple functions since the underlying * list is copy-on-write. * * @param tracedFunction - the qualified name of the function * for which tracing should be enabled. */ public final void addTracedFunction (String tracedFunction) { if (tracedFunction == null) { throw new NullPointerException("tracedFunction cannot be null."); } Set<String> oldTracedFunctions = getTracedFunctions(); if (!oldTracedFunctions.contains(tracedFunction)) { Set<String> newTracedFunctions = new HashSet<String>(oldTracedFunctions); newTracedFunctions.add(tracedFunction); setTracedFunctions(newTracedFunctions); } } /** * Remove a function from the set of functions for which * tracing is specifically enabled. * * Warning: this method is inefficient for adding multiple functions since the underlying * list is copy-on-write. * * @param tracedFunction - the qualified name of the function * to remove from the set of traced functions. */ public final void removeTracedFunction (String tracedFunction) { if (tracedFunction == null) { throw new NullPointerException("tracedFunction cannot be null."); } Set<String> oldTracedFunctions = getTracedFunctions(); if (oldTracedFunctions.contains(tracedFunction)) { Set<String> newTracedFunctions = new HashSet<String>(oldTracedFunctions); newTracedFunctions.remove(tracedFunction); setTracedFunctions(newTracedFunctions); } } /** * @return an unmodifiable String Set of the qualified names of the functional agents on which breakpoints are set. */ public final Set<String> getBreakpoints () { return Collections.unmodifiableSet(this.breakOnFunctions); } /** * @param breakpoints qualified names of the functional agents on which breakpoints are set. Cannot be null. * The argument set is copied. */ public final void setBreakpoints(Set<String> breakpoints) { if (breakpoints == null) { throw new NullPointerException("Argument 'breakpoints' cannot be null."); } this.breakOnFunctions = new HashSet<String>(breakpoints); } public final void clearBreakpoints() { this.breakOnFunctions = Collections.<String>emptySet(); } /** * Build up a Suspension object which describes the state of the CAL function * from which suspension was initiated and then suspend the calling thread * until it is notified to continue. * @param functionName - the CAL function calling suspend. * @param argValues - the argument values for the CAL function calling suspend. */ private void suspendInternal(String functionName, CalValue[] argValues) { String[][] argNamesAndTypes = runtimeEnvironment.getArgNamesAndTypes(functionName); //todoBI the old code used to pass an empty CalValue array if anything bad happened in getArgNamesAndTypes. This //seems incorrect, since indeed we know how many args we actually have. suspendInternal(functionName, argNamesAndTypes[0], argNamesAndTypes[1], argValues); } /** * Build up a Suspension object which describes the state of the CAL function * from which suspension was initiated and then suspend the calling thread * until it is notified to continue. * @param functionName - the CAL function calling suspend. * @param argNames - the argument names for the CAL function calling suspend. * @param argTypes - the argument types for the the CAL function calling suspend. * @param argValues - the argument values for the CAL function calling suspend. */ private void suspendInternal (String functionName, String[] argNames, String[] argTypes, CalValue[] argValues) { // We throw and catch a NullPointerException as a simple way of // getting a stack trace for this thread. try { throw new NullPointerException(); } catch (NullPointerException e) { // We want to strip off the top of the stack trace the calls into the execution context. // We want to show the user the stack trace at the point the call to suspend originated. StackTraceElement[] stackTrace = e.getStackTrace(); for (int i = 0; i < stackTrace.length; ++i) { StackTraceElement ste = stackTrace[i]; if (ste.getClassName().indexOf("cal_") >= 0) { if (i > 0) { StackTraceElement temp[] = new StackTraceElement[stackTrace.length - i]; System.arraycopy(stackTrace, i, temp, 0, temp.length); } break; } } // Create the suspension description. final SuspensionState suspensionState = new SuspensionState(functionName, argNames, argTypes, argValues, stackTrace); // Suspend the current thread. debugController.suspendCurrentThread(suspensionState); } } /** * Allow all suspended CAL execution threads to resume. * This should be called by debugging clients. */ public void resumeAll() { debugController.resumeAll(); } /** * Resumes the specified suspended thread. If the thread is not suspended then this is a no-op. * This should be called by debugging clients. * @param thread the suspended thread. */ public void resumeThread(final Thread thread) { debugController.resumeThread(thread); } public final boolean isStepping() { return stepping; } public final void setStepping(boolean stepping) { if (!stepping) { // reset the state if not stepping stepAllThreads = false; threadsToBeStepped.clear(); } this.stepping = stepping; } /** * Sets the flag that indicates that all threads should be stepped. * This should be called by debugging clients. * @param value the new value. */ public final void setStepAllThreads(boolean value) { this.stepAllThreads = value; } /** * Adds a thread to the set of threads that should be stepped. * This should be called by debugging clients. * @param thread a thread to be stepped. */ public final void addThreadToBeStepped(final Thread thread) { threadsToBeStepped.add(thread); } /** * Removes a thread to the set of threads that should be stepped. * This should be called by debugging clients. * @param thread a thread not to be stepped. */ public final void removeThreadToBeStepped(final Thread thread) { threadsToBeStepped.remove(thread); } /** * Adds a debug listener (a debugging client). * @param listener a debug listener. */ public void addDebugListener(final DebugListener listener) { debugController.addDebugListener(listener); } /** * Removes a debug listener (a debugging client). * @param listener a debug listener. */ public void removeDebugListener(final DebugListener listener) { debugController.removeDebugListener(listener); } /** * @return a map from suspended threads to their suspension states. */ public Map<Thread, SuspensionState> getThreadSuspensions() { return debugController.getThreadSuspensions(); } /** * @return whether there are any suspended threads. */ public boolean hasSuspendedThreads() { return debugController.hasSuspendedThreads(); } /** * Returns whether the given thread is suspended. * @param thread the thread to check. * @return true if the thread is suspended. */ public boolean isThreadSuspended(final Thread thread) { return debugController.isThreadSuspended(thread); } /** * Represents a listener interested in debugging-related events. * * @author Joseph Wong */ public static interface DebugListener { /** * Handles the event of a thread being suspended. * @param thread the thread being suspended. * @param suspensionState the suspension state of the thread. */ void threadSuspended(Thread thread, SuspensionState suspensionState); } /** * A controller for managing the suspension and resuming of execution. * * @author Joseph Wong */ private static final class DebugController { /** A map from suspended threads to their suspension states. */ private final Map<Thread, SuspensionState> threadSuspensions = new IdentityHashMap<Thread, SuspensionState>(); /** * The set of debug listeners. The use of a CopyOnWriteArraySet means that this collection * is thread-safe for traversal operations. */ private final CopyOnWriteArraySet<DebugListener> listeners = new CopyOnWriteArraySet<DebugListener>(); /** * Adds a debug listener (a debugging client). * @param listener a debug listener. */ synchronized void addDebugListener(final DebugListener listener) { listeners.add(listener); } /** * Removes a debug listener (a debugging client). * @param listener a debug listener. */ synchronized void removeDebugListener(final DebugListener listener) { listeners.remove(listener); } /** * Suspends the current thread. * @param suspensionState the associated suspension state. */ void suspendCurrentThread(final SuspensionState suspensionState) { final Thread currentThread = Thread.currentThread(); synchronized (this) { // put the thread in the suspension map first... this is important, since // during the upcall the client may decide to resume immediately, // and so there should be something in the map for the client to remove // and the absence of the entry would be a signal to the code below // to not even bother with the first wait() threadSuspensions.put(currentThread, suspensionState); } // make upcalls while not holding the lock... for (final DebugListener listener : listeners) { listener.threadSuspended(currentThread, suspensionState); } // actually suspend the thread synchronized (this) { try { // If the map still has the key, the client has not resumed the thread, so call wait(). // Note that it is possible for the loop body to not execute even once, if the client // calls resumeThread() or resumeAll() during the upcall period. while (threadSuspensions.containsKey(currentThread)) { wait(); } } catch (final InterruptedException e) { threadSuspensions.remove(currentThread); } } } /** * Resumes all suspended threads. */ synchronized void resumeAll() { threadSuspensions.clear(); notifyAll(); } /** * Resumes the specified suspended thread. If the thread is not suspended then this is a no-op. * @param thread the suspended thread. */ synchronized void resumeThread(final Thread thread) { threadSuspensions.remove(thread); notifyAll(); } /** * @return a map from suspended threads to their suspension states. */ synchronized Map<Thread, SuspensionState> getThreadSuspensions() { return new IdentityHashMap<Thread, SuspensionState>(threadSuspensions); } /** * @return whether there are any suspended threads. */ synchronized boolean hasSuspendedThreads() { return !threadSuspensions.isEmpty(); } /** * Returns whether the given thread is suspended. * @param thread the thread to check. * @return true if the thread is suspended. */ synchronized boolean isThreadSuspended(final Thread thread) { return threadSuspensions.containsKey(thread); } } /** * Description of the state of a CAL program at the point that suspension was * initiated. * @author RCypher * */ public static final class SuspensionState { /** The name of the CAL function which initiated suspension. */ private final String functionName; /** The argument names for the CAL function which initiated suspension. */ private final String[] argNames; /** The argument values for the CAL function at the point where suspension was initiated. */ private final CalValue[] argValues; /** Descriptions of the types of the arguments to the CAL function. */ private final String[] argTypes; /** The stack trace at the point suspension was initiated. */ private final StackTraceElement[] stackTrace; private SuspensionState(String functionName, String[] argNames, String[] argTypes, CalValue[] argValues, StackTraceElement[] stackTrace) { this.functionName = functionName; this.argNames = argNames; this.argValues = argValues; this.argTypes = argTypes; this.stackTrace = stackTrace; } /** * @return The name of the CAL function which initiated suspension. */ public String getFunctionName() { return functionName; } /** * @return The argument names for the CAL function which initiated suspension. */ public String[] getArgNames() { return argNames.clone(); } /** * @return The argument values for the CAL function at the point where suspension was initiated. */ public CalValue[] getArgValues() { return argValues.clone(); } /** * @return Descriptions of the types of the arguments to the CAL function. */ public String[] getArgTypes () { return argTypes.clone(); } /** * @return The stack trace at the point suspension was initiated. */ public StackTraceElement[] getStackTrace() { return stackTrace.clone(); } } }