All Downloads are FREE. Search and download functionalities are using the official Maven repository.

il.ac.bgu.cs.bp.bpjs.model.BProgram Maven / Gradle / Ivy

package il.ac.bgu.cs.bp.bpjs.model;

import il.ac.bgu.cs.bp.bpjs.execution.jsproxy.BProgramJsProxy;
import il.ac.bgu.cs.bp.bpjs.model.eventselection.EventSelectionStrategy;
import il.ac.bgu.cs.bp.bpjs.model.eventselection.SimpleEventSelectionStrategy;

import java.util.*;
import java.util.concurrent.*;

import il.ac.bgu.cs.bp.bpjs.exceptions.BPjsCodeEvaluationException;
import il.ac.bgu.cs.bp.bpjs.exceptions.BPjsException;
import il.ac.bgu.cs.bp.bpjs.execution.tasks.FailedAssertionException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.Scriptable;
import java.util.concurrent.atomic.AtomicInteger;
import org.mozilla.javascript.EcmaError;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.WrappedException;

/**
 * Base class for BPrograms. Provides the context (Javascript scope, external 
 * event queue, etc.) bthreads interact with while running.
 * Concrete BProgram extend this class by implementing 
 * the {@link #setupProgramScope(org.mozilla.javascript.Scriptable)} method.
 * 
 * 

* For creating a BProgram that uses a single Javascript file available in the * classpath, see {@link SingleResourceBProgram}. For creating them from a * hard-coded string, see {@link StringBProgram}. * * @author michael */ public abstract class BProgram { // ------------- Static Members --------------- /** * "Poison pill" to insert to the external event queue. Used only to turn the * daemon mode off. */ static final BEvent NO_MORE_DAEMON = new BEvent("NO_MORE_DAEMON"); /** Counter for giving anonymous instances some semantic name. */ private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(); /** * A callback interface invoked when a b-thread is added to {@code this}. */ public interface BProgramCallback { void bthreadAdded(BProgram bp, BThreadSyncSnapshot theBThread); } // ------------- Instance Members --------------- private String name; /** * When {@code true}, the BProgram waits for an external event when no * internal ones are available. */ private boolean daemonMode; /** * Events are enqueued here by external threads */ private final BlockingQueue recentlyEnqueuedExternalEvents = new LinkedBlockingQueue<>(); /** * BThreads added between bsyncs are added here. */ private final BlockingQueue recentlyRegisteredBthreads = new LinkedBlockingDeque<>(); private volatile boolean started = false; protected Scriptable programScope; private EventSelectionStrategy eventSelectionStrategy; /** * Objects that client code wishes to put in scope before the scope is * initialized are collected here. */ protected Map initialScopeValues = new HashMap<>(); private Optional addBThreadCallback = Optional.empty(); private List appendedCode; private List prependedCode; /** * Constructs a BProgram with a default name, guaranteed to be unique within * a given run. */ public BProgram() { this("BProgram-" + INSTANCE_COUNTER.incrementAndGet()); } /** * Constructs a BProgram with a specific name. * @param aName name for the new BProgram. */ public BProgram(String aName) { name = aName; } /** * Creates a BProgram with a specific name and an event selection strategy. * @param aName Name for the program. * @param anEss Event selection strategy. */ public BProgram( String aName, EventSelectionStrategy anEss ) { name = aName; eventSelectionStrategy = anEss; } /** * Loads a Javascript resource (a file that's included in the .jar). * * @param pathInJar path of the resource, relative to the class. */ public void evaluateResource(String pathInJar) { try ( InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream(pathInJar) ) { if (resource == null) { throw new RuntimeException("Resource '" + pathInJar + "' not found."); } evaluate(resource, pathInJar); } catch (IOException ex) { throw new RuntimeException("Error reading resource: '" + pathInJar +"': " + ex.getMessage(), ex); } } /** * Adds more source code to be evaluated after {@link #setupProgramScope(org.mozilla.javascript.Scriptable)} * is called. This method allows to programatically add code, e.g. for adding standard environment, mocking non-modeled * parts for model-checking. * * @throws IllegalStateException if the code is appended after the bprogram started. * @param source */ public void appendSource( String source ) { if ( started ) { throw new IllegalStateException("Cannot append code after the program had started."); } else { if ( appendedCode == null ) { appendedCode = new ArrayList<>(); } appendedCode.add(source); } } /** * Adds more source code to be evaluated before {@link #setupProgramScope(org.mozilla.javascript.Scriptable)} * is called. This method allows to programatically add code, e.g. for adding standard environment, mocking non-modeled * parts for model-checking. * * @throws IllegalStateException if the code is appended after the bprogram started. * @param source */ public void prependSource( String source ) { if ( started ) { throw new IllegalStateException("Cannot append code after the program had started."); } else { if ( prependedCode == null ) { prependedCode = new ArrayList<>(); } prependedCode.add(source); } } /** * Reads and evaluates the code at the passed input stream. The stream is * read to its end, but is not closed. * * @param inStrm Input stream for reading the script to be evaluated. * @param scriptName for error reporting purposes. * @return Result of evaluating the code at {@code inStrm}. */ protected Object evaluate(InputStream inStrm, String scriptName) { InputStreamReader streamReader = new InputStreamReader(inStrm, StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(streamReader); StringBuilder sb = new StringBuilder(); String line; try { while ((line = br.readLine()) != null) { sb.append(line).append("\n"); } } catch (IOException e) { throw new RuntimeException("error while reading javascript from stream", e); } String script = sb.toString(); return evaluate(script, scriptName); } /** * Runs the passed code in the passed scope. * @param script Code to evaluate * @param scriptName For error reporting purposes. * @return Result of code evaluation. */ protected Object evaluate(String script, String scriptName) { try { return Context.getCurrentContext().evaluateString(programScope, script, scriptName, 1, null); } catch (EcmaError rerr) { if ( rerr.getErrorMessage().trim().equals("\"bsync\" is not defined.") ) { throw new BPjsCodeEvaluationException("'bsync' is only defined in BThreads. Did you forget to call 'bp.registerBThread()'?", rerr); } throw new BPjsCodeEvaluationException(rerr); } catch (WrappedException wrapped) { if ( wrapped.getCause() instanceof BPjsException ) { throw (BPjsException)wrapped.getCause(); } else { throw wrapped; } } catch (EvaluatorException evalExp) { throw new BPjsCodeEvaluationException(evalExp); } } /** * Registers a BThread into the program. If the program started, the BThread * will take part in the current bstep. * * @param bt the BThread to be registered. */ public void registerBThread(BThreadSyncSnapshot bt) { bt.setupScope(programScope); recentlyRegisteredBthreads.add(bt); addBThreadCallback.ifPresent( cb -> cb.bthreadAdded(this, bt)); } /** * Adds an event to {@code this}' external event queue. * * @param e The event to add. */ public void enqueueExternalEvent(BEvent e) { recentlyEnqueuedExternalEvents.add(e); } /** * Sets up the program scope and evaluates the program source. * * This method can only be called once per instance. * * @return a snapshot of the program, after source code was executed, and * before any registered b-threads have run. * @throws IllegalStateException for repeated calls. */ public BProgramSyncSnapshot setup() { if ( started ) { throw new IllegalStateException("Program already set up."); } Set bthreads = drainRecentlyRegisteredBthreads(); if ( eventSelectionStrategy==null ) { eventSelectionStrategy = new SimpleEventSelectionStrategy(); } FailedAssertion failedAssertion = null; try { Context cx = ContextFactory.getGlobal().enterContext(); cx.setOptimizationLevel(-1); // must use interpreter mode initProgramScope(cx); if ( prependedCode != null ) { prependedCode.forEach( s -> evaluate(s, "prependedCode") ); } setupProgramScope(programScope); bthreads.forEach(bt -> bt.setupScope(programScope)); if ( appendedCode != null ) { appendedCode.forEach( s -> evaluate(s, "appendedCode") ); } } catch ( FailedAssertionException fae ) { failedAssertion = new FailedAssertion(fae.getMessage(), "---init_code"); } finally { Context.exit(); } started = true; return new BProgramSyncSnapshot(this, bthreads, Collections.emptyList(), failedAssertion); } private void initProgramScope(Context cx) { // load and execute globalScopeInit.js ImporterTopLevel importer = new ImporterTopLevel(cx); programScope = cx.initStandardObjects(importer); BProgramJsProxy proxy = new BProgramJsProxy(this); programScope.put("bp", programScope, Context.javaToJS(proxy, programScope)); // evaluateResource("globalScopeInit.js");// <-- Currently not needed. Leaving in as we might need it soon. initialScopeValues.entrySet().forEach(e->putInGlobalScope(e.getKey(), e.getValue())); initialScopeValues=null; } /** * The BProgram should set up its scope here. Normally, this amounts to * loading the script with the BThreads. * * @param scope the scope to set up. */ protected abstract void setupProgramScope(Scriptable scope); /** * Blocks until an external event is added. Then, if that event * is not the "stop daemon mode" one, returns the event. Otherwise, * returns {@code null}. * @return The event, or {@code null} in case the daemon mode * is turned off during the wait. * @throws InterruptedException */ public BEvent takeExternalEvent() throws InterruptedException { BEvent next = recentlyEnqueuedExternalEvents.take(); if (next == NO_MORE_DAEMON) { daemonMode = false; return null; } else { return next; } } /** * Sets whether this program is a daemon or not. When daemon, program will * wait for external events even when there are no selectable internal events. * * In normal mode ({@code daemon==false}), when no events are available for * selection, the program terminates. * * @param newDaemonMode {@code true} to make the program a daemon, * {@code false} otherwise. */ public void setDaemonMode(boolean newDaemonMode) { if (daemonMode && !newDaemonMode) { daemonMode = false; recentlyEnqueuedExternalEvents.add(NO_MORE_DAEMON); } else { daemonMode = newDaemonMode; } } /** * Returns {@code true} iff the program is in daemon mode. When in this mode, * the program will not terminate when it has no event available for selection. * Rather, it will wait for an external event to be enqueued into its external * event queue. * * @return {@code true} if this BProgram is in daemon mode, * {@code false} otherwise. * @see #enqueueExternalEvent(il.ac.bgu.cs.bp.bpjs.events.BEvent) */ public boolean isDaemonMode() { return daemonMode; } /** * Returns the program's global scope. * @return the global scope of the program. */ public Scriptable getGlobalScope() { return programScope; } /** * Adds an object to the program's global scope. JS code can reference the * added object by {@code name}. * @param name The name under which {@code object} will be available to the JS code. * @param obj The object to be added to the program's scope. */ public void putInGlobalScope( String name, Object obj ) { if ( getGlobalScope() == null ) { initialScopeValues.put(name, obj); } else { try { Context.enter(); getGlobalScope().put(name, programScope, Context.javaToJS(obj, programScope)); } finally { Context.exit(); } } } /** * Gets the object pointer by the passed name in the global scope. * @param Class of the returned object. * @param name Name of the object in the JS heap. * @param clazz Class of the returned object * @return The object pointer by the passed name in the JS heap, converted to * the passed class. */ public Optional getFromGlobalScope( String name, Class clazz ) { if ( getGlobalScope().has(name, programScope) ) { return Optional.of( (T)Context.jsToJava(getGlobalScope().get(name, getGlobalScope()), clazz) ); } else { return Optional.empty(); } } /** * Sets the name of the program * @param name the new program's name */ public void setName(String name) { this.name = name; } /** * @return the program's name */ public String getName() { return name; } Set drainRecentlyRegisteredBthreads() { Set out = new HashSet<>(); recentlyRegisteredBthreads.drainTo(out); return out; } List drainEnqueuedExternalEvents() { List out = new ArrayList<>(recentlyEnqueuedExternalEvents.size()); recentlyEnqueuedExternalEvents.drainTo(out); return out; } public void setAddBThreadCallback(BProgramCallback anAddBThreadCallback) { addBThreadCallback = Optional.ofNullable(anAddBThreadCallback); } public EventSelectionStrategy getEventSelectionStrategy() { if ( eventSelectionStrategy == null ) { setEventSelectionStrategy( new SimpleEventSelectionStrategy() ); } return eventSelectionStrategy; } public void setEventSelectionStrategy(EventSelectionStrategy eventSelectionStrategy) { this.eventSelectionStrategy = eventSelectionStrategy; } @Override public String toString() { return "[BProgram " + getName() + "]"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy