/*
* Copyright 2010 Jan Schmidt-Reinisch
*
* SoundComp - a sound processing library
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; in version 2.1
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package de.maramuse.soundcomp.parser;
import java.text.MessageFormat;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import de.maramuse.soundcomp.math.Variable;
import de.maramuse.soundcomp.math.ident;
import de.maramuse.soundcomp.parser.SCParser.ParserVal;
import de.maramuse.soundcomp.parser.math.FormulaElement;
import de.maramuse.soundcomp.parser.math.FormulaElement1;
import de.maramuse.soundcomp.parser.math.FormulaElement2;
import de.maramuse.soundcomp.parser.math.FormulaElement3;
import de.maramuse.soundcomp.process.ConstStream;
import de.maramuse.soundcomp.process.NamedSource;
import de.maramuse.soundcomp.process.ProcessElement;
import de.maramuse.soundcomp.process.SampleAdvancer;
import de.maramuse.soundcomp.process.SourceStore;
import de.maramuse.soundcomp.process.StandardParameters;
import de.maramuse.soundcomp.process.StandardParameters.Parameter;
import de.maramuse.soundcomp.process.Stateful;
import de.maramuse.soundcomp.process.TypeMismatchException;
import de.maramuse.soundcomp.process.UnknownConnectionException;
import de.maramuse.soundcomp.process.ValueType;
import de.maramuse.soundcomp.util.NativeObjects;
import de.maramuse.soundcomp.util.ReadOnlyMap;
import de.maramuse.soundcomp.util.ReadOnlyMapImpl;
/**
* this symbol class represents a process definition it is special in that it represents a ProcessElement by itself (to
* simplify creation of the ProcessElement which otherwise would require massive duplication) This special treatment
* means that we will need extra care for memory allocations. The instances that represent real calculated processes
* will have a native object created for, that must be freed in the end. The instances that only live as parse tree
* objects would not need this. They have it anyway, so it must be deleted at some point. (TODO)
*
* A process has a substructure of:
*
* - inputs that serve as sources of data
* - inner process elements (which may be processes themselves, but that is irrelevant at this level) which serve as sources of as well as sinks for data.
* - variables (which are just intermediates which serve to avoid duplicate calculation when the same term is needed multiple times).
* variables, like process elements, are sources and sinks in one.
* - outputs that serve as sinks for data
*
* When creating a process from parse data, or cloning it, all inner connections must be created.
*/
public class Process extends ParserVal implements ProcessElement, Stateful, ParserContext {
// start of members for the parser part. each is a list of same type objects
// the name of this process
ParserVal name;
// a list of input definitions for this process. currently just a name, later on also an input type
ParserVal inputs;
// a list of output definitions for this process. a name, and a formula that calculates the value
ParserVal outputs;
// a list of subprocesses. each element either is a process definition, or a process reference
// that refers to a process that must be defined elsewhere. the former is more an uncommon abbreviation,
// the latter should be the more common case. Each referred process must be defined or forward declared
// prior to referral use, as on referral, connection data must already be available.
//
// Initial implementation will only allow process references here, not real subprocess definitions.
// This is more a convenience issue - everything that the internal subprocesses allow can also be achieved
// by referring to external processes.
ParserVal subprocesses;
// a list of variables. similar to outputs, but visible to the interior instead of to the exterior.
// a variable is a kind of stateless subprocess definition
ParserVal variables=null;
// end of members for the parser part. each is a list of same type objects
// start of members for the control logic and signal handling part.
// a process template. unlike simpler process elements, this is not taken as a reproduction template for
// createTemplate() due to the increased complexity. instead, each instance is recreated from scratch.
// the initial template will still be calculated first as this is an ideal point for consistency checks.
// all "copied instances" will not produce their own template, they will refer to the original, should they
// ever need it. this member is probably redundant, but since we have to build the template anyway, why not
// make it available. one never knows...
ProcessElement template=null;
// this map of subordinated data sources helps in consistency checks (duplicate name checks).
ReadOnlyMapImpl<String, NamedSource> subs=null;
// a map of subordinated process definitions.
ReadOnlyMapImpl<String, Process> subprocessMap=new ReadOnlyMapImpl<String, Process>();
ReadOnlyMapImpl<String, ProcessRef> subrefMap=new ReadOnlyMapImpl<String, ProcessRef>();
// the sub-processes of this process do not register themselves with the AdvancerRegistry.
// this process itself has to take care of advancing its subs. this may ease life cycle management
// for the subs (they don't have to be known externally, their existence is hidden from the outside).
Set<SampleAdvancer> advancableSubs=new TreeSet<SampleAdvancer>();
// members for running the ProcessElement this process represents
private long nativeSpace;
private String instanceName;
private String abstractName;
private ReadOnlyMapImpl<Integer, ValueType> sourceTypes=new ReadOnlyMapImpl<Integer, ValueType>();
private ReadOnlyMapImpl<Integer, ValueType> destinationTypes=new ReadOnlyMapImpl<Integer, ValueType>();
private ReadOnlyMapImpl<Integer, SourceStore> sourceMap=new ReadOnlyMapImpl<Integer, SourceStore>();
private ReadOnlyMapImpl<String, Parameter> namedInputs=new ReadOnlyMapImpl<String, Parameter>();
private ReadOnlyMapImpl<String, Parameter> namedOutputs=new ReadOnlyMapImpl<String, Parameter>();
private ReadOnlyMapImpl<Integer, SourceStore> destinationMap=new ReadOnlyMapImpl<Integer, SourceStore>();
// members for the control logic
private Map<String, ProcessElement> containedElements=new TreeMap<String, ProcessElement>();
private Map<String, Variable> containedVariables=new TreeMap<String, Variable>();
private static long copyCount=0;
/**
* The constructor that is used when the Parser builds the initial version of this Process(-Element) as a template for
* the voice generation
*
* @param s
* the name of the element
*/
Process(String s) {
super(SCParser.PROCESS, s);
NativeObjects.registerNativeObject(this);
}
/**
* The constructor that is used when the Parser builds instances of derived classes (e.g. global processes that have
* special properties). Note that the native object then should be handled in the derived constructors.
*
* @param s
* the name of the element
*/
Process(int type, String s) {
super(type, s);
}
/**
* The constructor that is used when such a Process is created during runtime
*/
Process() {
super(SCParser.PROCESS, "~process");
NativeObjects.registerNativeObject(this);
}
/**
* The constructor that would be called if such an object is to be created from C++. This is not expected to happen
* since in C++ the necessary information for filling in a custom process is not available. Nevertheless, this
* constructor should be available so an accidental attempt to do so does not trigger a C++ Exception for the missing
* constructor.
*
* @param ignoreme
*/
Process(boolean ignoreme) {
super(SCParser.ILLEGAL, "unexpected c++ created process");
}
/**
* Set the ProcessElement name
*
* @param name
*/
void setName(ParserVal name) {
this.name=name;
}
/**
* Set the inputs (Parser oriented)
*
* @param inputs
*/
void setInputs(ParserVal inputs) {
this.inputs=inputs;
}
/**
* Set the variables (Parser oriented)
*
* @param variables
*/
void setVariables(ParserVal variables) {
this.variables=variables;
}
/**
* Set the outputs (Parser oriented)
*
* @param outputs
*/
void setOutputs(ParserVal outputs) {
this.outputs=outputs;
}
void setTemplate(ProcessElement template) {
this.template=template;
}
/**
* Create a ProcessElement representing the Process data generated by the parser.
*
* @return a ProcessElement representing the Process data generated by the parser.
* @throws UnknownConnectionException
* if an outer source is not found
* @throws TypeMismatchException
* if an outer source provides unsuitable data
*/
ProcessElement createTemplate() throws UnknownConnectionException, TypeMismatchException {
/*
* Master plan: first, gather all necessary information from the parse tree that is represented by appended objects.
* a process may contain of: - subprocesses - variables - inputs - outputs
*
* All of these should be kept in treemaps by name.
*
* Inputs are implicit UnnamedSources that nothing has to be done for within the Process. The same applies for
* variable readings. so they should be kept in one map together. Subprocesses' outputs are NamedSources that can
* simply be used. they could be kept in a separate map. For each entry, it is essential to look first whether the
* name is already given and to report any conflict and terminate compilation. What really has to be taken care of,
* are outputs, variable writings and subprocesses' inputs. In all these cases, generally a formula is being
* evaluated that generates the value. This formula is a kind of "nameless and stateless subprocess" that in itself
* is an UnnamedSource. Variables are calculated by formulas, and formulas may contain variables. For this reason,
* care has to be taken that there are no loops. Before it is evaluated, there must be a check whether this name is
* already under evaluation. Then, while calculating the variable, its name has to be memorized for this check.
* Since the loop may go over an arbitrary number of variables, the "name under evaluation" store must fit to
* contain an unlimited number of variable names. Should the test fail (i.e. an attempt to calculate a variable
* while it is already being calculated is detected), compilation must throw an error (and additionally set the
* variable to NaN).
*/
/* a set that contains all internal names for duplicate check */
Set<String> usedNames=new TreeSet<String>();
/* in the future subs and oldObjs shall serve to find illegal stateless loops */
subs=new ReadOnlyMapImpl<String, NamedSource>();
// Map<String, Object> oldObjs=new TreeMap<String, Object>();
int nameCount=0;
// make all interface inputs known to the external world, and to the internal accounting
int inputCount=0;
if(inputs!=null)
for(ParserVal val:inputs.inner){
if(usedNames.contains(val.getText()))
throw new IllegalArgumentException("Process "+name+" contains repeated parameter name "
+val.getText());
// TODO check for the right input type. We need a grammar definition for this!
destinationTypes.put(nameCount, ValueType.STREAM);
usedNames.add(val.getText());
namedInputs.put(val.getText(), new Parameter(val.getText(), inputCount++));
nameCount++;
}
// make all variables known to the rest of the process, and to the internal accounting
if(variables!=null)
for(ParserVal val:variables.inner){
if(usedNames.contains(val.getText()))
throw new IllegalArgumentException("Process "+name+" contains repeated variable name "
+val.getText());
Variable v=new Variable();
v.setAbstractName(val.getText());
containedVariables.put(val.getText(), v);
subs.put(val.getText(), v);
usedNames.add(val.getText());
SourceStore sst=getAsSource(val.inner.get(0));
v.setSource(0, sst.source, sst.sourceIndex);
}
/*
* make all internal elements known to the internal accounting these may be process references and in the future
* also inner process definitions
*/
if(subprocesses!=null)
for(ParserVal val:subprocesses.inner){
NamedSource el;
if(usedNames.contains(val.getText()))
throw new IllegalArgumentException("Process "+name+" contains repeated element name "
+val.getText());
if(val instanceof ProcessRef){
ProcessRef r=((ProcessRef)val);
// TODO find the corresponding process, if it is not a primitive. Should be done during parsing?
if(r.process==null){
if(r.localName==null)
throw new IllegalStateException("no process set for anonymous reference");
throw new IllegalStateException("no process set for reference "+r.localName);
}
el=r.process.createTemplate();
usedNames.add(r.localName);
subprocessMap.put(val.getText(), (Process)el);
subrefMap.put(val.getText(), r);
subs.put(val.getText(), el);
break; // do not insert it into subprocess map
}
// TODO: allow inner process definitions
throw new IllegalArgumentException("Element "+val.getText()+" contained in element "
+getText()
+" is not of suitable type (must be process reference) in "+filename+", line "+line);
}
// TODO: now that we know all sub elements, iterate once again over them, and connect
// their inputs. All connection counterparts, except for formulas, must be available now.
// For formulas, anonymous subprocesses must be created on the fly and
// get their inputs directly fed.
for(String ename:subs.keySet()){
// iterate over all subprocesses and subprocess references and connect their inputs
NamedSource val=subs.get(ename);
if(val instanceof ProcessRef){
ProcessRef r=(ProcessRef)val;
Process p=subprocessMap.get(r.localName);
if(r.getInputAssignments()!=null)
for(InputAssignment i:r.getInputAssignments().getAssignments()){
// now for each input of the subprocess, check if there is an assignment
if(i.getInputName()==null||i.getInputName().length()==0)
continue;
// first, get the destination index to which to connect to
Parameter dest=p.namedInputs.get(i.getInputName());
// then for each assignment, check if there is an input, and set the connection
if(i.getFormula()==null){
// this input of the sub element stays open, so ignore it
}else{
SourceStore sst=getAsSource(i.getFormula());
p.setSource(dest.i, sst.source, sst.sourceIndex);
}
}
}else if(val instanceof Process){
throw new IllegalArgumentException("Found process "+val.getAbstractName()+" where a processreference was expected - nested process definitions not yet implemented");
}else
// don't expect that here.
throw new IllegalArgumentException("Found symbol "+val.getAbstractName()+" where a processreference was expected");
}
// connect all variables to their value sources
if(variables!=null)
for(ParserVal val:variables.inner){
Variable v=containedVariables.get(val.getText());
SourceStore sst=getAsSource(val.inner.get(0));
v.setSource(0, sst.source, sst.sourceIndex);
}
// after all internal elements are connected, fix the connections of the outputs.
// here also, create anonymous subprocesses if applicable.
for(ParserVal val:outputs.inner){
if(usedNames.contains(val.getText()))
throw new IllegalArgumentException("Process "+name+" contains repeated output name "
+val.getText());
namedOutputs.put(val.getText(), new Parameter(val.getText(), nameCount));
// TODO will we ever have process definitions with outputs other than STREAM?
sourceTypes.put(nameCount, ValueType.STREAM);
sourceMap.put(nameCount, getAsSource(val));
usedNames.add(val.getText());
nameCount++;
}
@SuppressWarnings("unused")
Process process=new Process();
return null;
}
/**
* Convert the subordinated ParserVal into the corresponding SourceStore linked to ProcessElements
* The whole substructure of ProcessElements will be created.
*
* All Element and ConnectionPoint references will be resolved so that the resulting SourceStore
* can be queried for values after the first advanceState().
*
* This method and createTemplateFormula call each other in two-layer recursion to build a complete
* tree in case of a formula.
*
* @param val
* the ParserVal representing a formula to get a corresponding data source for
* @return the SourceStore that can be queried values for the ParserVal.
* @throws UnknownConnectionException
* if an outer source is not found
* @throws TypeMismatchException
* if an outer source provides unsuitable data
*/
private SourceStore getAsSource(ParserVal val) throws UnknownConnectionException,
TypeMismatchException {
if(val instanceof FormulaElement){
// nested formula
return new SourceStore(createTemplateFormula((FormulaElement)val), StandardParameters.OUT.i);
}
if(val instanceof ConnectionPoint){
// named process element output, look in tables
ConnectionPoint cp=(ConnectionPoint)val;
if(subs.containsKey(cp.getElementName())){
NamedSource src=subs.get(cp.getElementName());
Parameter param=src.outputsByName().get(cp.getConnectionName());
if(param==null)
throw new IllegalArgumentException(MessageFormat.format(
"Element {0} does not have output {1} in {2}", cp.getElementName(),
cp.getConnectionName(), cp.getLocationText()));
return new SourceStore(src, param.i);
}
throw new UnknownConnectionException("Element "+cp.getElementName()+" in "+name.getText()
+" not known in "
+val.filename+" line "+val.line);
}
if(val instanceof NamelessSource){
// a data source that has a stream whose name need not be given. NamelessSources by contract always
// are NamedSources that connect to OUT
// NamedSource src=subs.get(val);
// return new SourceStore(src, StandardParameters.OUT.i);
}
if(val instanceof Element){
// a single name appearing in a formula must refer to an interface input or a process variable
String elementName=val.getText();
Parameter param=inputsByName().get(elementName);
if(param!=null){
return getInputProxy(elementName);
}
Variable v=containedVariables.get(elementName);
if(v != null){
return new SourceStore(v, StandardParameters.OUT.i);
}
throw new UnknownConnectionException("Input "+elementName+" in "+name.getText()
+" not known in "
+val.filename+" line "+val.line);
}
if(val instanceof Connection){
// TODO how to handle?
Connection c=(Connection)val;
@SuppressWarnings("unused")
ConnectionPoint cp=c.source;
throw new UnknownConnectionException("Connection "+c.getEndpoint()+" in "+name.getText()
+" not known in "
+val.filename+" line "+val.line);
}
if(val instanceof NamelessSource && val instanceof TemplateProvider){
return new SourceStore(((TemplateProvider)val).getTemplate().clone(), StandardParameters.OUT.i);
}
if(val instanceof ConnectionPoint){
// can this legally happen?
ConnectionPoint cp=(ConnectionPoint)val;
throw new UnknownConnectionException("ConnectionPoint "+
cp.getElementName()+"."+cp.getConnectionName()+" in "+name.getText()
+" of unknown type in "
+val.filename+" line "+val.line);
}
throw new UnknownConnectionException("Element "+val.getText()+" in "+name.getText()
+" of unknown type in "
+val.filename+" line "+val.line);
}
/**
* Create a ProcessElement including the sub-elements that together represent a formula. Structure creation is stopped
* at the formula boundary.
*
* This method and getAsSource call each other in two-layer recursion to build a complete tree.
*
* @param val
* the FormulaElement-type ParserVal representing the top level operation
* @return the whole ProcessElement tree representing the formula
* @throws UnknownConnectionException
* if an outer source is not found
* @throws TypeMismatchException
* if an outer source provides unsuitable data
*/
private ProcessElement createTemplateFormula(FormulaElement val)
throws UnknownConnectionException,
TypeMismatchException {
ProcessElement el;
// FormulaElements always are TemplateProvider-s
el=((TemplateProvider)val).getTemplate().clone();
// FormulaElements always are ParserVal-s
el.setAbstractName(((ParserVal)val).getText());
if(val instanceof FormulaElement3){
ParserVal v3=((FormulaElement3)val).getInput3Val();
SourceStore el3=getAsSource(v3);
el.setSource(StandardParameters.IN.i, el3.source, el3.sourceIndex);
}
if(val instanceof FormulaElement2){
ParserVal v2=((FormulaElement2)val).getInput2Val();
SourceStore el2=getAsSource(v2);
el.setSource(StandardParameters.IN.i, el2.source, el2.sourceIndex);
}
if(val instanceof FormulaElement1){
ParserVal v1=((FormulaElement1)val).getInput1Val();
SourceStore el1=getAsSource(v1);
el.setSource(StandardParameters.IN.i, el1.source, el1.sourceIndex);
}
return el;
}
ProcessElement getTemplate() throws UnknownConnectionException, TypeMismatchException {
if(template==null)
template=createTemplate();
return template;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.NamedSource#getSourceTypes()
*/
@Override
public ReadOnlyMap<Integer, ValueType> getSourceTypes() {
return sourceTypes;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.NamedSource#getValue(int)
*/
@Override
public double getValue(int index) {
return sourceMap.get(index).getValue();
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.SampleAdvancer#advanceState()
*/
@Override
public void advanceState() {
// first, mark all variables dirty.
// whenever a variable is queried, it is only calculated when it is dirty, and then marked clean,
// so it is never calculated twice in one cycle.
if(containedVariables!=null)
for(Variable v:containedVariables.values()){
v.markDirty();
}
for(SampleAdvancer advancer:advancableSubs)
advancer.advanceState();
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.SampleAdvancer#advanceOutput()
*/
@Override
public void advanceOutput() {
for(SampleAdvancer advancer:advancableSubs)
advancer.advanceOutput();
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.SampleAdvancer#setAbstractName(java.lang.String)
*/
@Override
public void setAbstractName(String abstractName) {
this.abstractName=abstractName;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.SampleAdvancer#getAbstractName()
*/
@Override
public String getAbstractName() {
return abstractName;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.SampleAdvancer#setInstanceName(java.lang.String)
*/
@Override
public void setInstanceName(String instanceName) {
this.instanceName=instanceName;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.SampleAdvancer#getInstanceName()
*/
@Override
public String getInstanceName() {
return instanceName;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.NativeStub#getNativeSpace()
*/
@Override
public long getNativeSpace() {
return nativeSpace;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.ProcessElement#getDestinationTypes()
*/
@Override
public ReadOnlyMap<Integer, ValueType> getDestinationTypes() {
return destinationTypes;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.ProcessElement#setSource(int, de.maramuse.soundcomp.process.NamedSource, int)
*/
@Override
public void setSource(int connectionIndex, NamedSource source, int sourceIndex)
throws UnknownConnectionException, TypeMismatchException {
ValueType sourceType=source.getSourceTypes().get(sourceIndex);
ValueType destType=destinationTypes.get(connectionIndex);
if(destType==null)
throw new UnknownConnectionException("Could not determine type of "+name+" input "
+connectionIndex);
if(sourceType==null)
throw new UnknownConnectionException("Could not determine type of "+source.getAbstractName()
+" output "+sourceIndex);
if(!sourceType.equals(destType))
throw new UnknownConnectionException("Connection end types "+name+"/"+connectionIndex+" and "
+source.getAbstractName()+"/"+sourceIndex+" do not match");
destinationMap.put(connectionIndex, new SourceStore(source, sourceIndex));
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.ProcessElement#getSourceMap()
*/
@Override
public ReadOnlyMap<Integer, SourceStore> getSourceMap() {
return sourceMap;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.ProcessElement#outputsByName()
*/
@Override
public ReadOnlyMap<String, Parameter> outputsByName() {
return namedOutputs;
}
/*
* (non-Javadoc)
*
* @see de.maramuse.soundcomp.process.ProcessElement#inputsByName()
*/
@Override
public ReadOnlyMap<String, Parameter> inputsByName() {
return namedInputs;
}
public Map<String, ProcessElement> getContainedElements() {
return containedElements;
}
/**
* clone() just creates an "empty" process. The actual content is
* provided in copyStructure()
*/
@Override
public Process clone() {
return new Process();
}
// SourceStore deepCopyFormula(ParserVal val) {
// SourceStore src=null;
// NamedSource nsc=null;
// if(val instanceof Number){
// nsc=new ConstStream(val.asDouble());
// src=new SourceStore(nsc, StandardParameters.OUT.i);
// }else if(val instanceof TemplateProvider){
// nsc=((TemplateProvider)val).getTemplate();
// }
// return src;
// }
/**
* A SourceStore that is used within the InputProxy to allow delayed retrieval of
* the actual source
*/
private class InputStore extends SourceStore {
InputStore(){
super(null,0);
}
}
/**
* A proxy class that connects internal data destinations to outside sources.
* We need a proxy because when the internal connections are set up, outside sources are yet unknown.
* Hierarchical processes definitions are interpreted from inside to outside, so when an inner process
* definition is handled, no outside structures are available to connect to.
* These proxies are the central places that later get their inputs connected on first value request.
*/
private class InputProxy extends ident {
private String inputName;
private InputStore store=new InputStore();
/**
* Constructor for an input proxy
*
* @param inputIndex
* the index that the relevant input (data destination) will be seen with from outside
*/
InputProxy(String inputName) {
this.inputName=inputName;
}
/**
* Gets a value from the proxied connection. The passed in index is irrelevant as each proxy exactly serves one
* stream destination
*
* The current implementation does not support dis-/reconnection of inputs
* as the data source is cached upon first use. The control logic must ensure
* that the inputs of a process are connected before it gets the first
* advanceState() (which leads to calling getValue below)
* If there is a need to change this, dis-/reconnecting should look up
* the relevant input and adjust the proxies.
*/
@Override
public double getValue(int index) {
if(store.source==null){
Parameter param=namedInputs.get(inputName);
// TODO when it is possible to define default input values for processes,
// take care of that here instead of taking Double.NaN.
if(param==null){
store.source=ConstStream.c(Double.NaN);
return Double.NaN;
}
SourceStore _store=sourceMap.get(param.i);
store.sourceIndex=_store.sourceIndex;
store.source=_store.source;
}
return store.getValue();
}
}
public Process setElements(ParserVal list) {
this.subprocesses=list;
return this;
}
// /////////////////////////////////////////////////////////////////////////////////////
// methods for the control logic, mostly for copying the structure to create
// concrete instances from the parsed template
/**
* create a complete, working copy of this process template. In case of a structure, this is done by recreating it
* from the parse tree elements.
*
* @return a complete, working copy of this process template.
* @throws Exception
*/
public Process copyStructure() throws Exception {
Process dest=clone();
dest.destinationTypes=destinationTypes;
dest.sourceTypes=sourceTypes;
dest.namedOutputs=namedOutputs;
dest.namedInputs=namedInputs;
dest.inputs=inputs;
dest.outputs=outputs;
dest.subprocesses=subprocesses;
dest.createTemplate();
if(name!=null){
dest.abstractName=name.getText();
dest.instanceName=name.getText()+copyCount++;
}else{
dest.abstractName="anonymous process";
dest.instanceName="anonymous process"+copyCount++;
}
return dest;
}
public ReadOnlyMap<String, Parameter> getInputs(){
return namedInputs;
}
/**
* retrieve a SourceStore for an input given by textual name
* @param inputName the name of the input that a SourceStore is requested for
* @return a SourceStore that can be used to retrieve the input
*/
public SourceStore getInputProxy(String inputName){
// TODO: we could cache these proxies in case the same input
// is used multiple times - it makes no sense duplicating the proxy then
return new InputProxy(inputName).store;
}
/**
* helper class for debugging purposes
* TODO make this class private and comment out the debug test case
* when processes can successfully be created from the parser.
* this will cut off direct external access to the process' internal structures.
* @return a new empty process.
*/
public static class DebugProxy {
Process process;
public DebugProxy(Process process){
this.process=process;
}
public void setInputs(ParserVal inputs){
process.setInputs(inputs);
}
public void setOutputs(ParserVal outputs){
process.setOutputs(outputs);
}
public static Process create(){
return new Process();
}
}
@Override
public ProcessRef getProcessRefByName(String name1) {
return subrefMap.get(name1);
}
@Override
public boolean containsElement(String name1) {
return hasVariable(name1)||hasInput(name1)||hasProcessDef(name1);
}
@Override
public boolean hasVariable(String name1) {
for(ParserVal val:variables.inner){
if(val.text.equals(name1))return true;
}
return false;
}
@Override
public boolean hasInput(String name1) {
for(ParserVal val:inputs.inner){
if(val.text.equals(name1))return true;
}
return false;
}
@Override
public boolean hasProcessDef(String name1) {
for(ProcessRef val:subrefMap.values()){
if(val.localName.equals(name1))return true;
}
return false;
}
}