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

il.ac.bgu.cs.bp.bpjs.execution.jsproxy.BProgramJsProxy Maven / Gradle / Ivy

Go to download

Provides runtime and analysis for behavioral programs written in JavaScript. It can run stand-alone (from the commmandline) or be embedded in larger JVM-based systems.

The newest version!
package il.ac.bgu.cs.bp.bpjs.execution.jsproxy;

import il.ac.bgu.cs.bp.bpjs.exceptions.BPjsRuntimeException;
import il.ac.bgu.cs.bp.bpjs.execution.tasks.FailedAssertionException;
import il.ac.bgu.cs.bp.bpjs.internal.ScriptableUtils;
import il.ac.bgu.cs.bp.bpjs.model.*;
import il.ac.bgu.cs.bp.bpjs.model.eventsets.EventSet;
import il.ac.bgu.cs.bp.bpjs.model.eventsets.EventSets;
import il.ac.bgu.cs.bp.bpjs.model.eventsets.JsEventSet;
import java.util.List;
import org.mozilla.javascript.*;

import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

/**
 * An object representing the {@link BProgram} context for JavaScript code.
 * Methods in this object allow JavaScript code to register new BThreads,
 * create events,write messages to the log etc.
 * 

* Methods in the class are available to JavaScript code via the {@code bp} * object, like so: * *


 * bp.log.info("I'm a log message!");
 * var myEvent = bp.Event("My Event");
 * bp.registerBThread(...);
 * 
* * @author michael */ public class BProgramJsProxy extends SyncStatementBuilder implements java.io.Serializable { /////////////////////////////// // A (Java) thread-local mechanism to allow the Java code calling the BP // code to communicate with the Java code called from the BP code. private static class BThreadData { final BThreadSyncSnapshot snapshot; final MapProxy storeModifications; public BThreadData(BThreadSyncSnapshot snapshot, MapProxy storeModifications) { this.snapshot = snapshot; this.storeModifications = storeModifications; } } private static final ThreadLocal CURRENT_BTHREAD = new ThreadLocal<>(); public static void setCurrentBThread(BProgramSyncSnapshot bpss, BThreadSyncSnapshot btss) { CURRENT_BTHREAD.set(new BThreadData(btss, new MapProxy(bpss.getDataStore()))); } public static void clearCurrentBThread() { CURRENT_BTHREAD.remove(); } public static MapProxy getCurrentChanges() { BThreadData bThreadData = CURRENT_BTHREAD.get(); return (bThreadData != null) ? bThreadData.storeModifications : null; } // /thread-local /////////////////////////////// /** * State of a b-thread, captures during a bp.sync. */ public static class CapturedBThreadState { public final SyncStatement syncStmt; public final MapProxy modifications; public CapturedBThreadState(SyncStatement syncStmt, MapProxy modifications) { this.syncStmt = syncStmt; this.modifications = modifications; } } private final BProgram bProg; private final AtomicInteger autoAddCounter = new AtomicInteger(0); public final BpLog log = new BpLog(); /** * Deprecated - use eventSets.all */ @Deprecated(forRemoval = true) public final EventSet all = EventSets.all; /** * Deprecated - use eventSets.none */ @Deprecated(forRemoval = true) public final EventSet none = EventSets.none; public final EventSetsJsProxy eventSets = new EventSetsJsProxy(); /** * Facility for creating random numbers. BPjs code should not use JavaScript's * random facility, as it won't play well with model checking. */ public RandomProxy random = new RandomProxy(); public BProgramJsProxy(BProgram aBProgram) { bProg = aBProgram; } /** * Event constructor, called from JavaScript, hence the funny * capitalization. * * @param name name of the event * @return an event with the passed name. */ public BEvent Event(String name) { return new BEvent(name); } /** * Event constructor, called from JavaScript, hence the funny * capitalization. * * @param name name of the event * @param jsData Additional data for the object. * @return an event with the passed name. */ public BEvent Event(String name, Object jsData) { return new BEvent(name, jsData); } public JsEventSet EventSet(String name, Object predicateObj) { if (predicateObj instanceof Function) { return new JsEventSet(name, (Function) predicateObj); } else { throw new BPjsRuntimeException("An event set predicate has to be a function."); } } public EventSet allExcept(EventSet es) { return EventSets.not(es); } /** * Called from JS to add BThreads with data. * * @param name Name of the registered BThread (useful for debugging). * @param data Data object for the b-thread. * @param func Script entry point of the BThread. * @see #registerBThread(org.mozilla.javascript.Function) */ public void registerBThread(String name, Object data, Function func) { bProg.registerBThread(new BThreadSyncSnapshot(name, func, null, null, null, data, null)); } /** * Called from JS to add BThreads running function as their runnable code. * * @param name Name of the registered BThread (useful for debugging). * @param func Script entry point of the BThread. * @see #registerBThread(org.mozilla.javascript.Function) */ public void registerBThread(String name, Function func) { bProg.registerBThread(new BThreadSyncSnapshot(name, func, bProg)); } /** * Registers a BThread and gives it a unique name. Use when you don't care * about the added BThread's name. * * @param func the BThread to add. * @see #registerBThread(java.lang.String, org.mozilla.javascript.Function) */ public void registerBThread(Function func) { registerBThread("autoadded-" + autoAddCounter.incrementAndGet(), func); } /** * If {@code value} is {@code false}, puts the entire program in an invalid * state. This, in turn, would terminate it when it's being run, or discover * a specification violation when it's being verified. *

* note: I'd rather call it {@code assert} too, but that's a Java keyword, which complicates stuff. * * @param value The value of the assertion. When {@code false}, the program is declared in invalid state. * @param message Textual information about what caused the violation. * @throws FailedAssertionException if {@code value} is false. */ public void ASSERT(boolean value, String message) throws FailedAssertionException { if (!value) { throw new FailedAssertionException(message); } } public void fork() throws ContinuationPending { ContinuationPending capturedContinuation = Context.getCurrentContext().captureContinuation(); capturedContinuation.setApplicationState(new ForkStatement(capturedContinuation.getContinuation())); throw capturedContinuation; } public void setInterruptHandler(Object aPossibleHandler) { CURRENT_BTHREAD.get().snapshot.setInterruptHandler( (aPossibleHandler instanceof Function) ? (Function) aPossibleHandler : null); } //////////////////////// // sync ("bp.sync") related code @Override public void sync(NativeObject jsRWB, Object data) { synchronizationPoint(jsRWB, null, data); } @Override public SyncStatementBuilder hot(boolean isHot) { SyncStatementBuilderImpl sub = new SyncStatementBuilderImpl(this); sub.setHotness(isHot); return sub; } /** * Where the actual Behavioral Programming synchronization point is done. * * @param jsRWB The JavaScript object {@code {request:... waitFor:...}} * @param hot {@code True} if this should be a "hot" synchronization point. * @param data Optional extra data the synchronizing b-thread may want to add. */ @Override void synchronizationPoint(NativeObject jsRWB, Boolean hot, Object data) { if ( CURRENT_BTHREAD.get() == null ) { throw new BPjsRuntimeException("Calling bp.sync outside of a b-thread is forbidden"); } Map jRWB = (Map) Context.jsToJava(jsRWB, Map.class); SyncStatement stmt = SyncStatement.make(); if (hot != null) { stmt = stmt.hot(hot); } Object req = jRWB.get("request"); if (req != null) { try { if (req instanceof NativeArray) { NativeArray arr = (NativeArray) req; final List requestedEvents = arr.getIndexIds().stream() .map(i -> (BEvent) arr.get(i)) .collect(toList()); if ( requestedEvents.contains(null) ) { throw new RuntimeException("Cannot request a null event. B-thread name: "+ CURRENT_BTHREAD.get().snapshot.getName()); } stmt = stmt.request(requestedEvents); } else { stmt = stmt.request((BEvent) req); } } catch (ClassCastException cce) { throw new BPjsRuntimeException("A non-event object requested in a sync statement. " + "Offending object:'" + ScriptableUtils.stringify(req) + "' in BThread " + CURRENT_BTHREAD.get().snapshot.getName()); } } EventSet waitForSet = convertToEventSet(jRWB.get("waitFor")); EventSet blockSet = convertToEventSet(jRWB.get("block")); EventSet interruptSet = convertToEventSet(jRWB.get("interrupt")); stmt = stmt.waitFor(waitForSet) .block(blockSet) .interrupt(interruptSet) .data(data); captureBThreadState(stmt); } private EventSet convertToEventSet(Object jsObject) { if (jsObject == null) return EventSets.none; // This covers event sets AND events. if (jsObject instanceof EventSet) { return (EventSet) jsObject; } else if (jsObject instanceof NativeArray) { NativeArray arr = (NativeArray) jsObject; if (arr.isEmpty()) return EventSets.none; if (Stream.of(arr.getIds()).anyMatch(id -> arr.get(id) == null)) { throw new RuntimeException("EventSet Array contains null sets. B-thread name: " + CURRENT_BTHREAD.get().snapshot.getName()); } if (arr.getLength() == 1) return (EventSet) arr.get(0); return EventSets.anyOf( arr.getIndexIds().stream() .map(i -> (EventSet) arr.get(i)) .collect(toSet())); } else { final String errorMessage = "Cannot convert " + jsObject + " of class " + jsObject.getClass() + " to an event set"; throw new BPjsRuntimeException(errorMessage); } } private void captureBThreadState(SyncStatement stmt) throws ContinuationPending { ContinuationPending capturedContinuation = Context.getCurrentContext().captureContinuation(); BThreadData curBThreadData = CURRENT_BTHREAD.get(); capturedContinuation.setApplicationState(new CapturedBThreadState(stmt, curBThreadData != null ? curBThreadData.storeModifications : null)); throw capturedContinuation; } // /sync ///////////////////////// /** * Push a new event to the external event queue. * * @param evt The event to be pushed. * @return the event being pushed. */ public BEvent enqueueExternalEvent(BEvent evt) { bProg.enqueueExternalEvent(evt); return evt; } /** * Sets whether the BProgram will wait for external events when there's * no internal event to choose. * * @param newDaemonMode {@code true} for making {@code this} a daemon; * {@code false} otherwise. */ public void setWaitForExternalEvents(boolean newDaemonMode) { bProg.setWaitForExternalEvents(newDaemonMode); } public boolean isWaitForExternalEvents() { return bProg.isWaitForExternalEvents(); } /** * @return Returns the current time in milliseconds since 1/1/1970. */ public long getTime() { return System.currentTimeMillis(); } public BThreadDataProxy getThread() { return new BThreadDataProxy(CURRENT_BTHREAD.get().snapshot); } public MapProxy getStore() { BThreadData bThreadExecutionContext = CURRENT_BTHREAD.get(); return (bThreadExecutionContext != null) ? bThreadExecutionContext.storeModifications : new DirectMapProxy<>(bProg.getStore()); } /** * Gets the name of the Java thread executing this b-thread at the moment. Useful for * debugging Java runtime issues. * * @return the name of the Java thread executing this b-thread at the moment. */ public String getJavaThreadName() { return Thread.currentThread().getName(); } @Override public int hashCode() { return 42; } /** * Proxies contain no state of their own, and provide a gateway to the Java * environment the BProgram runs in. When comparing sync snapshots, we can * encounter JS proxies when traversing the JS heap and stack. To prevent leaking * the equality evaluation outside of the JavaScript, we do not check the * equality of the {@link BProgram} {@code this} is a proxy of. Rather, we * just check that the other object is a proxy too. * * @param obj The object we compare to. * @return {@code true} iff the other object is a {@code BProgramJsProxy}. */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; return getClass() == obj.getClass(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy