//
// Copyright (C) 2008 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.util.script;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.util.StateExtensionClient;
import gov.nasa.jpf.util.StateExtensionListener;
import gov.nasa.jpf.vm.ChoiceGenerator;
/**
* class representing a statemachine environment that produces SCEventGenerators
* from scripts
*/
@SuppressWarnings("hiding")
public abstract class ScriptEnvironment<CG extends ChoiceGenerator<?>>
implements StateExtensionClient<ScriptEnvironment<CG>.ActiveSnapshot> {
static final String DEFAULT = "default";
//--- just a helper tuple
static class ActiveSequence implements Cloneable {
String stateName;
Section section;
SequenceInterpreter intrp;
public ActiveSequence (String stateName, Section section, SequenceInterpreter intrp) {
this.stateName = stateName;
this.section = section;
this.intrp = intrp;
}
public Object clone() {
try {
ActiveSequence as = (ActiveSequence) super.clone();
as.intrp = (SequenceInterpreter) intrp.clone();
return as;
} catch (CloneNotSupportedException nonsense) {
return null; // we are a Cloneable, so we don't get here
}
}
public boolean isDone() {
return intrp.isDone();
}
}
//--- our state extension - we need this mostly for cloning (deep copy)
class ActiveSnapshot implements Cloneable {
ActiveSequence[] actives;
ActiveSnapshot () {
actives = new ActiveSequence[0];
}
ActiveSnapshot (ActiveSequence[] as) {
actives = as;
}
public ActiveSequence get (String stateName) {
for (ActiveSequence as : actives) {
if (as.stateName.equals(stateName)) {
return as;
}
}
return null;
}
@SuppressWarnings("unchecked")
public Object clone() {
try {
ActiveSnapshot ss = (ActiveSnapshot)super.clone();
for (int i=0; i<actives.length; i++) {
ActiveSequence as = actives[i];
ss.actives[i] = (ActiveSequence)as.clone();
}
return ss;
} catch (CloneNotSupportedException nonsense) {
return null; // we are a Cloneable, so we don't get here
}
}
ActiveSnapshot advance (String[] activeStates, BitSet isReEntered) {
ActiveSequence[] newActives = new ActiveSequence[activeStates.length];
//--- carry over the persisting entries
for (int i=0; i<activeStates.length; i++) {
String sn = activeStates[i];
for (ActiveSequence as : actives) {
if (as.stateName.equals(sn) ) {
// we could use isReEntered to determine if we want to restart sequences
// <2do> how do we factor this out as policy?
newActives[i] = (ActiveSequence)as.clone();
}
}
}
//--- add the new ones
int skipped = 0;
nextActive:
for (int i=0; i<activeStates.length; i++) {
if (newActives[i] == null) {
// get the script section
Section sec = getSection(activeStates[i]);
if (sec != null) {
// check if that section is already processed by another active state, in which case we skip
for (int j=0; j<newActives.length; j++) {
if (newActives[j] != null && newActives[j].section == sec) {
skipped++;
continue nextActive;
}
}
// check if it was processed by a prev state (superstate section by a
// common parent of a new and an old state - this is the common case
for (int j=0; j<actives.length; j++) {
// <2do> how do we handle state re-entering?
if (actives[j].section == sec) {
ActiveSequence as = new ActiveSequence(activeStates[i], sec, actives[j].intrp);
newActives[i] = as;
continue nextActive;
}
}
// it's a new one
ActiveSequence as = new ActiveSequence(activeStates[i], sec,
new SequenceInterpreter(sec));
newActives[i] = as;
} else { // sec == null : we didn't find any sequence for this state
skipped++;
}
}
}
//--- compress if we skipped any active states
if (skipped > 0) {
int n = activeStates.length - skipped;
ActiveSequence[] na = new ActiveSequence[n];
for (int i=0, j=0; j<n; i++) {
if (newActives[i] != null) {
na[j++] = newActives[i];
}
}
newActives = na;
}
return new ActiveSnapshot(newActives);
}
}
//--- start of ScriptEnvronment
String scriptName;
Reader scriptReader;
Script script;
ActiveSnapshot cur;
HashMap<String,Section> sections = new HashMap<String,Section>();
Section defaultSection;
//--- initialization
public ScriptEnvironment (String fname) throws FileNotFoundException {
this( fname, new FileReader(fname));
}
public ScriptEnvironment (String name, Reader r) {
this.scriptName = name;
this.scriptReader = r;
}
public void parseScript () throws ESParser.Exception {
ESParser parser= new ESParser(scriptName, scriptReader);
script = parser.parse();
initSections();
cur = new ActiveSnapshot();
}
void initSections() {
Section defSec = new Section(script, DEFAULT);
for (ScriptElement e : script) {
if (e instanceof Section) {
Section sec = (Section)e;
List<String> secIds = sec.getIds();
if (secIds.size() > 0) {
for (String id : secIds) {
sections.put(id, (Section)sec.clone()); // clone to guarantee different identities
}
} else {
sections.put(secIds.get(0), sec);
}
} else { // add copy to default sequence
defSec.add(e.clone());
}
}
if (defSec.getNumberOfChildren() > 0) {
defaultSection = defSec;
}
}
Section getSection (String id) {
Section sec = null;
while (id != null) {
sec = sections.get(id);
if (sec != null) {
return sec;
}
int idx = id.lastIndexOf('.');
if (idx > 0) {
id = id.substring(0, idx); // ?? do we really want this recursive? that's policy
} else {
id = null;
}
}
return defaultSection;
}
void addExpandedEvent(ArrayList<Event> events, Event se) {
for (Event e : se.expand()) {
if (!events.contains(e)) {
events.add(e);
}
}
}
static final String[] ACTIVE_DEFAULT = { DEFAULT };
public CG getNext (String id) {
return getNext(id, ACTIVE_DEFAULT, null);
}
public CG getNext (String id, String[] activeStates) {
return getNext(id, activeStates, null);
}
// this is our main purpose in life, but there is some policy in here
public CG getNext (String id, String[] activeStates, BitSet isReEntered) {
cur = cur.advance(activeStates, isReEntered);
ArrayList<Event> events = new ArrayList<Event>(cur.actives.length);
for (ActiveSequence as : cur.actives) {
while (true) {
ScriptElement se = as.intrp.getNext();
if (se != null) {
if (se instanceof Event) {
addExpandedEvent(events, (Event)se);
break;
} else if (se instanceof Alternative) {
for (ScriptElement ase : (Alternative)se) {
if (ase instanceof Event) {
addExpandedEvent(events, (Event)ase);
}
}
break;
} else {
// get next event
}
} else {
break; // process next active sequence
}
}
}
return createCGFromEvents(id, events);
}
protected abstract CG createCGFromEvents(String id, List<Event> events);
//--- StateExtension interface
public ActiveSnapshot getStateExtension() {
return cur;
}
public void restore(ActiveSnapshot stateExtension) {
cur = stateExtension;
}
public void registerListener(JPF jpf) {
StateExtensionListener<ActiveSnapshot> sel = new StateExtensionListener<>(this);
jpf.addSearchListener(sel);
}
}