001/* An actor with several sub-workflow choices for execution. 
002 * 
003 * Copyright (c) 2012 The Regents of the University of California.
004 * All rights reserved.
005 *
006 * '$Author: crawl $'
007 * '$Date: 2015-08-24 18:05:00 +0000 (Mon, 24 Aug 2015) $' 
008 * '$Revision: 33621 $'
009 * 
010 * Permission is hereby granted, without written agreement and without
011 * license or royalty fees, to use, copy, modify, and distribute this
012 * software and its documentation for any purpose, provided that the above
013 * copyright notice and the following two paragraphs appear in all copies
014 * of this software.
015 *
016 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
017 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
018 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
019 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
020 * SUCH DAMAGE.
021 *
022 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
023 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
024 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
025 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
026 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
027 * ENHANCEMENTS, OR MODIFICATIONS.
028 *
029 */
030package org.kepler.ddp.actor;
031
032import java.io.File;
033import java.io.FileWriter;
034import java.io.IOException;
035import java.lang.reflect.Field;
036import java.util.Arrays;
037import java.util.HashMap;
038import java.util.HashSet;
039import java.util.Hashtable;
040import java.util.LinkedHashSet;
041import java.util.LinkedList;
042import java.util.List;
043import java.util.Map;
044import java.util.Set;
045import java.util.regex.Matcher;
046import java.util.regex.Pattern;
047
048import org.apache.commons.io.FileUtils;
049import org.kepler.build.modules.Module;
050import org.kepler.build.modules.ModuleTree;
051import org.kepler.ddp.gui.ExecutionChoiceEditorFactory;
052import org.kepler.ddp.gui.ExecutionChoiceEditorPane;
053import org.kepler.provenance.ProvenanceRecorder;
054import org.kepler.reporting.ReportingListener;
055
056import ptolemy.actor.IOPort;
057import ptolemy.actor.IOPortEvent;
058import ptolemy.actor.IOPortEventListener;
059import ptolemy.actor.TypedIOPort;
060import ptolemy.actor.gui.style.EditableChoiceStyle;
061import ptolemy.actor.lib.hoc.Case;
062import ptolemy.actor.lib.hoc.MultiCompositeActor;
063import ptolemy.actor.lib.hoc.MultiCompositePort;
064import ptolemy.actor.lib.hoc.Refinement;
065import ptolemy.data.BooleanToken;
066import ptolemy.data.StringToken;
067import ptolemy.data.Token;
068import ptolemy.data.expr.Parameter;
069import ptolemy.data.expr.StringParameter;
070import ptolemy.data.type.BaseType;
071import ptolemy.gui.ExtensionFilenameFilter;
072import ptolemy.kernel.ComponentEntity;
073import ptolemy.kernel.ComponentPort;
074import ptolemy.kernel.CompositeEntity;
075import ptolemy.kernel.Port;
076import ptolemy.kernel.Relation;
077import ptolemy.kernel.util.Attribute;
078import ptolemy.kernel.util.ChangeListener;
079import ptolemy.kernel.util.ChangeRequest;
080import ptolemy.kernel.util.IllegalActionException;
081import ptolemy.kernel.util.NameDuplicationException;
082import ptolemy.kernel.util.NamedObj;
083import ptolemy.kernel.util.Settable;
084import ptolemy.kernel.util.SingletonAttribute;
085import ptolemy.kernel.util.Workspace;
086import ptolemy.moml.MoMLChangeRequest;
087import ptolemy.moml.MoMLParser;
088import ptolemy.util.CancelException;
089import ptolemy.util.MessageHandler;
090import ptolemy.vergil.basic.KeplerDocumentationAttribute;
091
092/** An actor that supports multiple choices for execution. Each choice 
093 *  is a sub-workflow that can either be loaded from a set of templates,
094 *  or created by the user.
095 *  
096 *  Each file input port has an associated parameter. When an input token is read,
097 *  the associated parameter's value is set to the be token.
098 *  
099 *  Each file output also has an associated parameter. When the execution choice
100 *  finishes, the value of the parameter is written to the output port.
101 * 
102 *  @author Daniel Crawl
103 *  @version $Id: ExecutionChoice.java 33621 2015-08-24 18:05:00Z crawl $
104 *  
105 */
106public class ExecutionChoice extends Case implements ChangeListener, IOPortEventListener {
107
108    /** Create a new ExecutionChoice in a container with the
109     *  specified name.
110     */
111    public ExecutionChoice(CompositeEntity container, String name)
112            throws IllegalActionException, NameDuplicationException {
113        super(container, name);
114       _init();
115    }
116
117    /** Create a new ExecutionChoice in a workspace. */
118    public ExecutionChoice(Workspace workspace) throws IllegalActionException,
119            NameDuplicationException {
120        super(workspace);
121        _init();
122    }
123        
124    /** React to a change in an attribute. */
125    @Override
126    public void attributeChanged(Attribute attribute) throws IllegalActionException {
127        
128        if(attribute == checkOutputTimestamp) {
129            
130            _checkOutputTimestampVal = ((BooleanToken)checkOutputTimestamp.getToken()).booleanValue();
131            
132        } else if (attribute == control) {
133            String newValue = ((StringToken)control.getToken()).stringValue();
134            
135            // see if we've added the default choice
136            if(_default != null && !newValue.equals(DEFAULT_TEMPLATE_NAME)) {   
137
138                    // verify that the refinement exists
139                    boolean found = false;
140                    List<Refinement> refinements = entityList(Refinement.class);
141                    for(Refinement refinement : refinements) {
142                        if(refinement.getName().equals(newValue)) {
143                            found = true;
144                            break;
145                        }
146                    }
147                    if(!found) {
148                        throw new IllegalActionException(this, "Execution Choice '" + newValue + "' not found.");
149                    }
150            }
151        } else {
152            super.attributeChanged(attribute);
153        }
154    }
155    
156    /** React to the fact that the change has been successfully executed
157     *  by doing nothing.
158     *  @param change The change that has been executed.
159     */
160    @Override
161    public void changeExecuted(ChangeRequest change) {
162        // Nothing to do.
163    }
164
165    /** React to the fact that the change has failed by reporting it.
166     *  @param change The change that was attempted.
167     *  @param exception The exception that resulted.
168     */
169    @Override
170    public void changeFailed(ChangeRequest change, Exception exception) {
171        // Ignore if this is not the originator.
172        if ((change != null) && (change.getSource() != this)) {
173            return;
174        }
175
176        if ((change != null) && !change.isErrorReported()) {
177            change.setErrorReported(true);
178            MessageHandler.error("Rename failed: ", exception);
179            _changeRequestError = true;
180        }
181    }
182
183    /** Override the base class to ensure that the member fields
184     *  are initialized.
185     *  @param workspace The workspace for the new object.
186     *  @return A new ExecutionChoice.
187     *  @exception CloneNotSupportedException If any of the attributes
188     *   cannot be cloned.
189     */
190    @Override
191    public Object clone(Workspace workspace) throws CloneNotSupportedException {
192        ExecutionChoice newObject = (ExecutionChoice) super.clone(workspace);
193        newObject._changeRequestError = false;
194        newObject._checkOutputTimestampVal = true;
195        newObject._choiceStyle = null;
196        newObject._commandLineArguments = "$additionalOptions";
197        newObject._refinementCommandLines = new HashMap<Refinement, String>();
198        newObject._templateDir = null;
199        return newObject;
200    }
201    
202    /** Export an execution choice to a file.
203     *  @param saveFile the file to write the execution choice as a MoML XML. 
204     *  @param name the name of the execution choice to save.
205     */
206        public void exportExecutionChoice(File saveFile, String name) 
207                        throws IllegalActionException, NameDuplicationException {
208
209                final ComponentEntity<?> refinement = getEntity(name);
210                if(refinement == null) {
211                        throw new IllegalActionException(this, "Execution choice " +
212                                        name + " does not exist.");
213                }
214                
215                // create the container to be exported.
216                // it is a MultiCompositeActor because MultiCompositePorts cannot
217                // be contained by other actors.
218                final MultiCompositeActor toExport = new MultiCompositeActor(_workspace);
219                toExport.setName(name);
220                
221                try {
222                        
223                        // copy ports to the workflow
224                        for(Port port : (List<Port>)portList()) {
225                                if(port != control.getPort()) {
226                                        Port clonePort = (Port) port.clone(_workspace);
227                                        clonePort.setContainer(toExport);
228                                        
229                                        // clone the File parameters
230                                        if(getPortIOType(port) == IOType.File) {
231                                                Parameter parameter = (Parameter) getAttribute(port.getName());
232                                                Parameter cloneParameter = (Parameter) parameter.clone(_workspace);
233                                                cloneParameter.setContainer(toExport);
234                                        }
235                                }
236                        }
237                        
238                        // copy parameters to the workflow
239                        for(String parameterName : getParameterNames()) {
240                                Parameter parameter = (Parameter) getAttribute(parameterName);
241                                Parameter cloneParameter = (Parameter) parameter.clone(_workspace);
242                                cloneParameter.setContainer(toExport);
243                        }
244                        
245                        // copy the refinement to the workflow
246                        Refinement cloneRefinement = (Refinement) refinement.clone(_workspace);
247                        cloneRefinement.setContainer(toExport);
248                } catch(CloneNotSupportedException e) {
249                        throw new IllegalActionException(this, e, "Could not copy execution choice.");
250                }
251                
252                // finally write out the workflow
253        FileWriter writer = null;
254        try {
255            try {
256                writer = new FileWriter(saveFile);
257                toExport.exportMoML(writer);
258            } finally {
259                if(writer != null) {
260                    writer.close();
261                }
262            }
263        } catch(IOException e) {
264            throw new IllegalActionException(this, e, "Error exporting to " + saveFile);
265        }
266        
267        // delete the exported workflow from memory
268        toExport.setContainer(null);
269        }
270
271    @Override
272    public void fire() throws IllegalActionException {
273        
274        // if we're checking the last modified timestamps of output
275        // files, save the current last modified timestamps before we
276        // execute the template.
277        final Map<String,Long> lastModifiedTimes = new HashMap<String,Long>();
278        if(_checkOutputTimestampVal) {
279            for(String outputName : getOutputNames(false)) {
280                StringParameter parameter = (StringParameter) getAttribute(outputName);
281                String outputFileStr = parameter.stringValue();
282                File outputFile = new File(outputFileStr);
283                lastModifiedTimes.put(outputFile.getPath(), outputFile.lastModified());
284            }
285        }
286        
287        super.fire();
288    
289        if(_checkOutputTimestampVal) {
290            // verify that each output file's last modified timestamp
291            // has increased.
292            for(String outputName : getOutputNames(false)) {
293                StringParameter parameter = (StringParameter) getAttribute(outputName);
294                String outputFileStr = parameter.stringValue();
295                File outputFile = new File(outputFileStr);
296                // do not check if the output is a directory
297                if(!outputFile.isDirectory()) {
298                        long lastModifiedTimeBeforeExec = lastModifiedTimes.get(outputFile.getPath());
299        
300                        // see if the file was not updated
301                        if(lastModifiedTimeBeforeExec > 0 && 
302                                outputFile.lastModified() <= lastModifiedTimeBeforeExec) {
303                            throw new IllegalActionException(this, "Output " +
304                                    outputName + " (" + outputFile + ")\n" +
305                                    "does not appear to have been updated.");
306                        } // see if the file did not exist before and after we executed the refinement
307                        else if(outputFile.lastModified() == 0) {
308                            throw new IllegalActionException(this, "Output " +
309                                    outputName + " (" + outputFile + ")\n" +
310                                    " was not created.");
311                        }
312                }
313            }            
314        }
315
316    }
317    
318    /** Get the command line argument for a parameter. 
319     * @param name the name of the parameter
320     * @return the command line argument if one was set, otherwise null 
321     */
322    public String getArgument(String name) throws IllegalActionException {
323        return getArgument(this, name);
324    }
325    
326    /** Get the command line argument for a parameter in a specific container
327     * @param container the container 
328     * @param name the name of the parameter
329     * @return the command line argument if one was set, otherwise null 
330     */
331    public static String getArgument(NamedObj container, String name) throws IllegalActionException {       
332
333        Parameter argumentParameter = getArgumentParameter(container, name);
334        if(argumentParameter != null) {
335            return argumentParameter.getExpression();
336        }
337        return null;        
338    }
339
340    /** Get the parameter containing the command line argument for a parameter.
341     *  @param name the name of the parameter
342     *  @return the parameter containing the value of the argument
343     */
344    public StringParameter getArgumentParameter(String name) throws IllegalActionException {
345        return getArgumentParameter(this, name);
346    }
347
348    /** Get the parameter containing the command line argument for a parameter
349     *  in a specific container.
350     *  @param container the container
351     *  @param name the name of the parameter
352     *  @return the parameter containing the value of the argument
353     */
354    public static StringParameter getArgumentParameter(NamedObj container, String name)
355                throws IllegalActionException {
356    
357        Attribute attribute = container.getAttribute(name);
358        if(attribute == null) {
359            throw new IllegalActionException(container, "No component with name " + name);
360        }
361        
362        return (StringParameter) attribute.getAttribute(ARGUMENT_NAME);
363    }
364
365    /** Get a list of execution choice names. */
366    public List<String> getExecutionChoiceNames() {
367        
368        final List<Refinement> refinements = entityList(Refinement.class);
369        final String[] namesArray = new String[refinements.size()];
370        int i = 0;
371        for(Refinement refinement : refinements) {
372            namesArray[i++] = refinement.getName();
373        }
374        Arrays.sort(namesArray);
375        return Arrays.asList(namesArray);
376    }
377
378    /** Get a list of file input names. */
379    public List<String> getInputNames() throws IllegalActionException {
380        return getInputNames(false);
381    }
382    
383    /** Get a list of input port names. */
384    public List<String> getInputNames(boolean includeData) throws IllegalActionException {
385        return _getIONames(inputPortList(), includeData);
386    }
387
388    /** Get a list of file output names. */
389    public List<String> getOutputNames() throws IllegalActionException {
390        return getOutputNames(false);
391    }
392    
393    /** Get a list of output port names. */
394    public List<String> getOutputNames(boolean includeData) throws IllegalActionException {
395        return _getIONames(outputPortList(), includeData);
396    }    
397
398    /** Get a list of parameter names. The returned list does not include
399     *  parameters associated with inputs or outputs, fields of
400     *  ExecutionChoice, visibility set to none, or names starting with "_". 
401     */
402    public List<String> getParameterNames() throws IllegalActionException {
403        final List<String> inputs = getInputNames(false);
404        final List<String> outputs = getOutputNames(false);
405        
406        final List<String> fields = new LinkedList<String>();
407        for(Field field : getClass().getFields()) {
408            fields.add(field.getName());
409        }
410        
411        final List<String> parameterNames = new LinkedList<String>();
412        for(Parameter parameter : attributeList(Parameter.class)) {
413            String name = parameter.getName();
414            if (!inputs.contains(name) && !outputs.contains(name)
415                    && !fields.contains(name)
416                    && !name.startsWith("_")
417                    && parameter.getVisibility() != Settable.NONE) {
418                parameterNames.add(name);
419            }
420        }
421        
422        return parameterNames;
423    }
424
425    /** Get the parameter type for a parameter. */
426    public ParameterType getParameterType(String name) throws IllegalActionException {
427        return getParameterType(this, name);
428    }
429    
430    /** Get the parameter type for a parameter in a container within this actor. */
431    public static ParameterType getParameterType(NamedObj container, String name) throws IllegalActionException {
432        
433        Parameter parameter = (Parameter) container.getAttribute(name);
434        if(parameter == null) {
435                throw new IllegalActionException(container, "Parameter " + name +
436                                " in " + container.getName() + " not found.");
437        }
438        
439        if(parameter instanceof StringParameter) {
440                return ParameterType.String;
441        } else {
442                return ParameterType.Numeric;
443        }
444    }
445    
446    /** Get the IOType for a port. */
447    public static IOType getPortIOType(Port port) throws IllegalActionException {
448        Parameter typeParameter = (Parameter) port.getAttribute(IO_TYPE_NAME);
449        if(typeParameter == null) {
450            System.err.println("WARNING: missing IO type for " + port + ".");
451            System.err.println("Assuming type is File.");            
452            try {
453                _setPortIOTypeParameter(port, IOType.File);
454            } catch (NameDuplicationException e) {
455                throw new IllegalActionException(port, e, "Error setting Port IO type.");
456            }
457            return IOType.File;
458        }
459        
460        return  IOType.valueOf(typeParameter.getExpression());
461    }
462
463    /** Get the available template names. */
464    public Set<String> getTemplateNames() throws IllegalActionException {
465
466        String[] files = _templateDir.list(new ExtensionFilenameFilter("xml"));
467        String[] names = new String[files.length + 1];
468        // remove the extensions
469        for(int i = 0; i < files.length; i++) {
470            names[i] = files[i].substring(0, files[i].length() - 4);
471        }
472        // add choice for an empty refinement
473        names[files.length] = EMPTY_TEMPLATE_NAME;
474        Arrays.sort(names);
475        return new LinkedHashSet<String>(Arrays.asList(names));
476    }
477    
478    /** Returns true if there is an input with the given name. */
479    public boolean hasInput(String name) {
480        return _hasInputOutput(name, true);
481    }
482    
483    /** Returns true if there is an output with the given name. */
484    public boolean hasOutput(String name) {
485        return _hasInputOutput(name, false);
486    }
487        
488    /** Create a new execution choice from a template.
489     *  @param templateName the name of the template. The
490     *  template name must be contained in the set of names
491     *  return by getTemplateNames(). 
492     *  @param name the name to give the new refinement
493     *  @return The refinement created for the new execution choice.
494     */
495    public Refinement newExecutionChoice(String templateName, String refinementName)
496        throws IllegalActionException {
497
498        Refinement newRefinement = null;
499        if(templateName.equals(EMPTY_TEMPLATE_NAME)) {
500            try {
501                newRefinement = newRefinement(refinementName);
502
503                // add ports to the blank refinement
504                _updatePortsAndInsideLinks();
505                
506                updateExecutionChoices();
507
508                _addCommandLineParameter(newRefinement);
509
510            } catch (NameDuplicationException e) {
511                throw new IllegalActionException(this, e, "Error adding blank template.");
512            }
513        } else {
514        
515            String templateNameReplaced = templateName.replaceAll(" ", "");
516            
517            File templateFile = new File(_templateDir, templateNameReplaced + ".xml");
518            if(!templateFile.exists()) {
519                throw new IllegalActionException(this, "Could not find template " +
520                        templateName + ": " + templateFile);
521            }
522            
523            newRefinement = newExecutionChoice(templateFile, refinementName);
524        }
525        
526        return newRefinement;
527        
528    }
529    
530    /** Create a new execution choice from a file.
531     *  @param templateFile the containing the MoML of the execution choice.
532     *  @param name the name to give the new refinement.
533     *  @return The refinement created for the new execution choice.
534     */
535    public Refinement newExecutionChoice(File templateFile, String refinementName)
536                throws IllegalActionException {
537        
538        // see if a refinement with the same name already exists
539        Refinement existingRefinement = (Refinement) getEntity(refinementName);        
540        if(existingRefinement != null) {
541                // in some cases, the default local execution refinement is saved
542                // in the model. if the existing refinement is the default one, 
543                // rename it.
544                if(refinementName.equals(DEFAULT_TEMPLATE_NAME)) {
545                        try {
546                                        existingRefinement.setName("OLD " + DEFAULT_TEMPLATE_NAME);
547                                } catch (NameDuplicationException e) {
548                                        throw new IllegalActionException(this, e, "Unable to rename " +
549                                                        " old LocalExecution execution choice.");
550                                }
551                } else {
552                        throw new IllegalActionException(this, "An execution choice with the name " +
553                                refinementName + " has already been loaded.");
554                }
555        }
556            
557        //final List<Refinement> existingRefinements = entityList(Refinement.class);
558
559        Refinement newRefinement = null;
560
561        ExecutionChoice container = null;
562                try {
563                        container = new ExecutionChoice(_workspace);
564                        container.setName("imported container");
565                } catch (NameDuplicationException e) {
566                        throw new IllegalActionException(this, e, "Error create new MultiCompositeActor.");
567                }
568                
569                // add program and additionalOptions parameters to the container
570                // since parameters in the template may reference them
571                /*
572                try {
573                        new StringParameter(container, "program");
574                } catch (NameDuplicationException e) {
575                        throw new IllegalActionException(this, e, "Error creating program parameter.");
576                }
577                try {
578                        new StringParameter(container, "additionalOptions");
579                } catch (NameDuplicationException e) {
580                        throw new IllegalActionException(this, e, "Error creating additionalOptions parameter.");
581                }
582                */
583                
584                try {
585                        addDefaultInputsAndOutputs(container);
586                } catch (NameDuplicationException e) {
587                        throw new IllegalActionException(this, e, "Error adding defaults to container.");
588                }
589                
590                // call validateSettables() to validate program and additionalOptions
591                //container.validateSettables();
592                
593                // load the template file into the container
594        _loadExecutionChoice(templateFile, container);
595        
596        MultiCompositeActor exportedContainer;
597        
598        // see if the template was a refinement (old version)
599        if(container.entityList(MultiCompositeActor.class).isEmpty()) {
600                System.out.println("WARNING: template is older version that does " +
601                                "not contain ports and parameters.");  
602                
603                exportedContainer = container;
604        } else {
605                
606                exportedContainer = container.entityList(MultiCompositeActor.class).get(0);
607                
608                // create ports
609                for(Port port : (List<Port>)exportedContainer.portList()) {
610
611                        // see if we already have a port with this name
612                        final String portName = port.getName();
613                        if(getPort(portName) == null) {
614                                
615                                final IOType type = getPortIOType(port);
616                                
617                                // if it's a File, get the argument
618                                String argument = null;
619                                if(type == IOType.File) {
620                                        argument = getArgument(exportedContainer, portName);
621                                }
622                                
623                                try {
624                                        if(((IOPort) port).isInput()) {
625                                                newInput(portName, type, argument);
626                                        } else {
627                                                newOutput(portName, type, argument);
628                                        }
629                                } catch(NameDuplicationException e) {
630                                        throw new IllegalActionException(this, e, "Error creating port " + 
631                                                        portName + " from template.");
632                                }
633                                
634                                // if it's a File, set the parameter value
635                                String value = ((Parameter) exportedContainer.getAttribute(portName)).getExpression();
636                                ((Parameter) getAttribute(portName)).setExpression(value);
637                        }
638                }
639                
640                // create parameters
641                List<Parameter> parameterList = new LinkedList<Parameter>(exportedContainer.attributeList(Parameter.class));
642                for(Parameter parameter : parameterList) {
643                        final String parameterName = parameter.getName();
644                        // see if we already have a parameter with this name, or
645                        // the parameter was created when we added ports above
646                        if(getAttribute(parameterName) == null) {
647                                try {
648                                                parameter.setContainer(this);
649                                        } catch (NameDuplicationException e) {
650                                                throw new IllegalActionException(this, e, "Error adding port " +
651                                                                parameterName + " from template.");
652                                        }
653                        }
654                }
655        }
656        
657        // move the refinement
658        List<Refinement> refinements = exportedContainer.entityList(Refinement.class);
659        if(refinements.size() == 0) {
660                throw new IllegalActionException(this, "No execution choices found in template.");
661        } 
662        
663        newRefinement = refinements.get(0);
664        
665        if(refinements.size() > 1) {
666                System.out.println("WARNING: more than one executon choice found in template.");
667                System.out.println("Only " + newRefinement.getName() + " will be loaded.");
668        }
669        
670        try {           
671                // set the name before moving the refinement to this actor
672                newRefinement.setName(refinementName);
673                        newRefinement.setContainer(this);
674                } catch (NameDuplicationException e) {
675                        throw new IllegalActionException(this, e, "Error adding template into actor.");
676                }
677
678        try {
679                        container.setContainer(null);
680                } catch (NameDuplicationException e) {
681                        throw new IllegalActionException(this, e, "Error deleting template container.");
682                }
683            
684        
685        // the template is the new refinement
686        /*
687        final List<Refinement> currentRefinements = entityList(Refinement.class);
688        for(Refinement refinement : currentRefinements) {
689                if(!existingRefinements.contains(refinement)) {
690                        newRefinement = refinement;
691                        break;
692                }
693        }
694
695        if(newRefinement == null) {
696                throw new IllegalActionException(this, "Error loading refinement." +
697                                " Perhaps it is not a Refinement?");
698        }
699        */
700        
701        /*
702        if(!(newRefinement instanceof Refinement)) {
703            throw new IllegalActionException(this, "Template is not a Refinement.");
704        }
705        */
706        
707        // we no longer have to clone the refinement into our workspace since
708        // the template is loaded by incremental parsing.
709        /*
710        try {
711            newRefinement = (Refinement) namedObj.clone(workspace());
712        } catch (CloneNotSupportedException e) {
713            throw new IllegalActionException(this, e, "Cloning error.");
714        }
715        */
716                     
717        // remove provenance recorder and reporting listener
718        // if they are present
719        List<Attribute> toRemove = new LinkedList<Attribute>(
720                        newRefinement.attributeList(ProvenanceRecorder.class));
721        toRemove.addAll(newRefinement.attributeList(ReportingListener.class));
722        for(Attribute attribute : toRemove) {
723                try {
724                                attribute.setContainer(null);
725                        } catch (NameDuplicationException e) {
726                                throw new IllegalActionException(this, e,
727                                        "Error removing " + attribute.getName());
728                        }
729        }
730        
731        try {
732            newRefinement.setContainer(this);
733        } catch (NameDuplicationException e) {
734            throw new IllegalActionException(this, e, "Error adding template.");
735        }
736        
737        // NOTE: do not update _current, since that's done in CaseDirector.prefire().
738
739        if(_default == null) {
740            _default = newRefinement;
741        }
742
743        // the refinement loaded from the template and the containing
744        // MultiCompositeActor (this class) may have a different set
745        // of ports. we need to make sure they both have the same set
746        // of ports and each outside-inside pair are linked.
747        // FIXME is this still necessary?
748        _updatePortsAndInsideLinks();
749        
750        updateExecutionChoices();
751
752        _addCommandLineParameter(newRefinement);
753
754        // repaint the canvas to show new ports
755        MoMLChangeRequest change = 
756                new MoMLChangeRequest(this, this, "<group></group>");
757        change.setPersistent(false);
758        requestChange(change);
759
760        return newRefinement;
761    }
762           
763    private void _loadExecutionChoice(File templateFile, NamedObj container)
764                throws IllegalActionException {
765        
766        MoMLParser parser = new MoMLParser();
767        // NOTE: the template may reference parameters not defined in the
768        // template but defined in ExecutionChoice, e.g., outputPath or
769        // inputPath, so we set the parser's context to this actor to
770        // avoid exceptions when parsing the template.
771        parser.setContext(container);
772                
773        String templateStr = null;
774        try {
775                templateStr = FileUtils.readFileToString(templateFile);
776        } catch(IOException e) {
777                throw new IllegalActionException(this, e,
778                                "Error reading template file " + templateFile.getAbsolutePath());
779        }
780
781        try {
782                // use incremental parsing to load the template.
783                // this is necessary since all the MoMLParser.parse()
784                // methods return the top-level object, which is our
785                // top-level object since we called MoMLParser.setContext().
786                
787                // don't put in a <group></group> since the template MoML is
788                // a top-level object (contains <!DOCTYPE>
789                //StringBuilder moml = new StringBuilder("<group>");
790                //moml.append(templateStr);
791                //moml.append("</group>");
792                //parser.parse(moml.toString());
793                parser.parse(templateStr);
794        } catch (Exception e) {
795            throw new IllegalActionException(this, e, "Error parsing " + templateFile);
796        }
797                
798        }
799
800        /** Create a new file input. */
801    public void newInput(String name, IOType type) throws NameDuplicationException, IllegalActionException {
802        
803        newInput(name, type, null);
804    }
805
806    /** Create a new file input. */
807    public void newInput(String name, IOType type, String argument) throws NameDuplicationException, IllegalActionException {
808
809        _newInputOrOutput(name, type, argument, true);
810
811        // if the IO type is file, and the name is not the default
812        // input directory name, set the default value of the
813        // parameter to be the default input directory . the name
814        if(type == IOType.File && !name.equals(DEFAULT_INPUT_DIR_NAME)) {
815            Parameter parameter = (Parameter) getAttribute(name);
816            if(getAttribute(DEFAULT_INPUT_DIR_NAME) != null) {
817                parameter.setExpression("$" + DEFAULT_INPUT_DIR_NAME + File.separator +
818                                getName() + "." + name);
819            } else {
820                parameter.setExpression(getName() + "." + name);
821            }
822        }
823        
824        /* NOTE: PortParameter ports are not mirrored for MultiCompositeActors
825        PortParameter input = new PortParameter(this, name);
826        input.setStringMode(true);
827        input.setExpression(getName() + "." + name);
828        
829        TypedIOPort port = input.getPort();
830        port.setTypeEquals(BaseType.STRING);
831        new SingletonAttribute(port, "_showName");
832        */
833        
834    }
835    
836    /** Create a new file output. */
837    public void newOutput(String name, IOType type) throws IllegalActionException, NameDuplicationException {
838        
839        newOutput(name, type, null);
840        
841    }
842    
843    /** Create a new file output. */
844    public void newOutput(String name, IOType type, String argument) throws IllegalActionException, NameDuplicationException {
845    
846        _newInputOrOutput(name, type, argument, false);
847        
848        // if the IO type is file, and the name is not the default
849        // output directory name, set the default value of the
850        // parameter to be the default output directory . the name
851        if(type == IOType.File && !name.equals(DEFAULT_OUTPUT_DIR_NAME)) {
852            Parameter parameter = (Parameter) getAttribute(name);
853                if(getAttribute(DEFAULT_OUTPUT_DIR_NAME) != null) {
854                        parameter.setExpression("$" + DEFAULT_OUTPUT_DIR_NAME + File.separator +
855                                getName() + "." + name);
856                } else {
857                        parameter.setExpression(getName() + "." + name);
858                }
859        }
860
861    }
862    
863    /** Add a new parameter.
864     * @param name the parameter name
865     * @param argument the command line argument for this parameter. this can be null. 
866     */
867    public void newParameter(String name, String argument)
868            throws IllegalActionException, NameDuplicationException {
869        newParameter(name, argument, null);
870    }
871    
872    /** Add a new parameter.
873     * @param name the parameter name
874     * @param argument the command line argument for this parameter. this can be null. 
875     * @param value the default value for the paramete. this can be null.
876     */
877    public void newParameter(String name, String argument, String value)
878            throws IllegalActionException, NameDuplicationException {
879
880        newParameter(name, argument, value, null, ParameterType.String);
881    }
882    
883    /** Add a new parameter.
884    * @param name the parameter name
885    * @param argument the command line argument for this parameter. this can be null. 
886    * @param value the default value for the paramete. this can be null.
887    * @param choice the refinement in which to add the parameter. if null or
888    * set to ALL_CHOICES_NAME, the parameter is added to this actor.
889    */
890    public void newParameter(String name, String argument, String value, String choice,
891            ParameterType type) throws IllegalActionException, NameDuplicationException {
892
893        ComponentEntity<?> refinement = null;
894        if(choice != null && !choice.equals(ALL_CHOICES_NAME)) {
895            refinement = getEntity(choice);
896        }
897
898        NamedObj container;
899        if(refinement == null) {
900            container = this;
901        } else {
902            container = refinement;
903        }
904        
905        Parameter parameter;
906        if(type == ParameterType.Numeric) {
907            parameter = new Parameter(container, name);
908        } else {
909            parameter = new StringParameter(container, name);
910        }
911        
912        if(argument != null && !argument.isEmpty()) {
913            setArgument(parameter, argument);
914        }
915        
916        if(value != null) {
917            parameter.setExpression(value);
918        }
919        
920        
921        if(refinement == null) {
922            _addToCommandLines(name);
923        }
924        
925    }
926    
927    /** Create a new execution choice. Override the parent class to
928     *  add the execution choice name to the parameter list.
929     */
930    /*
931    @Override
932    public Refinement newRefinement(String name) throws IllegalActionException, NameDuplicationException {
933        Refinement refinement = super.newRefinement(name);
934        if(!name.equals("default")) {
935            control.addChoice(name);
936        }
937        return refinement;
938    }
939    */
940    
941    /** Receive a port event. If the port is an input port and has an
942     *  associated parameter, set the value of the parameter to be
943     *  the token read by the port.
944     */
945    @Override
946    public void portEvent(IOPortEvent event) throws IllegalActionException {
947
948        //System.out.println(event);
949        if(event.getEventType() == IOPortEvent.GET_END) {
950            //System.out.println("got read: " + event);
951            IOPort port = event.getPort();
952            StringParameter parameter = (StringParameter) getAttribute(port.getName());
953            if(parameter != null) {
954                parameter.setToken(event.getToken());
955                //System.out.println("set parameter " + parameter);
956            }
957        }
958    }
959
960    /** List to port events for File input ports so that when a token is read, we
961     * can set the corresponding parameter. Also, make sure output Data file ports
962     * that are connected outside are connected in the current refinement.
963     * 
964     * @see #portEvent()
965     */
966    @Override
967    public void preinitialize() throws IllegalActionException {
968
969        super.preinitialize();
970        
971        addDefaultExecutionChoice();
972
973        for(Object object : portList()) {
974            final TypedIOPort port = (TypedIOPort) object;
975
976            // see if the IOType is file
977            if(port != control.getPort() && getPortIOType(port) == IOType.File) {
978                
979                // for input ports, listen to port events so we can set
980                // the corresponding parameter
981                if(port.isInput()) {
982                    port.addIOPortEventListener(this);
983                }
984                
985                // set the type to string
986                port.setTypeEquals(BaseType.STRING);
987            }
988            
989            // make sure output data ports that are connected outside, are connected
990            // inside the current refinement
991            if(port.isOutput() && port.numberOfSinks() > 0) {
992                final IOType type = getPortIOType(port);
993                if(type == IOType.Data) {
994                    
995                    // make sure it's connected inside
996                    String refinementName = ((StringToken)control.getToken()).stringValue();
997                    Refinement refinement = (Refinement) getEntity(refinementName);
998                    if(refinement == null) {
999                        throw new IllegalActionException(this, "Execution choice not found: " + refinementName);
1000                    }
1001                    
1002                    IOPort refinementPort = (IOPort) refinement.getPort(port.getName());
1003                    if(!refinementPort.isInsideConnected()) {
1004                        throw new IllegalActionException(this, "Output Data port " + port.getName() +
1005                            " is not connected inside the execution choice " + refinementName); 
1006                    }
1007                }
1008            }
1009        }
1010        
1011        
1012        // create directories for File outputs
1013        for(String outputName : getOutputNames(false)) {
1014                
1015                // create directories for outputs with specific names
1016                if(outputName.endsWith("Dir") ||
1017                                outputName.endsWith("_dir") ||
1018                                outputName.endsWith("_Dir")) {                          
1019                        Parameter outputParameter = (Parameter) getAttribute(outputName);
1020                        if(outputParameter != null) {
1021                                Token token = outputParameter.getToken();
1022                                if(token != null) {
1023                                        String outputString;
1024                                if(token instanceof StringToken) {
1025                                        outputString = ((StringToken)token).stringValue();
1026                                } else {
1027                                        outputString = token.toString();
1028                                }
1029                                
1030                                // make sure the value is not empty
1031                                if(!outputString.trim().isEmpty()) {
1032                                    File outputPathDir = new File(outputString);
1033                                    if(!outputPathDir.exists() && !outputPathDir.mkdirs()) {
1034                                        throw new IllegalActionException(this, "Could not create " +
1035                                                " directory: " + outputPathDir);
1036                                    }
1037                                }
1038                                }
1039                        }
1040                }
1041        }
1042                
1043    }
1044        
1045    /** Remove an execution choice. */
1046    public void removeExecutionChoice(String name) throws IllegalActionException, NameDuplicationException {
1047        
1048        ComponentEntity<?> refinement = getEntity(name);
1049        if(refinement == null) {
1050            throw new IllegalActionException(this, "Could not find execution choice " + name);
1051        }
1052        refinement.setContainer(null);  
1053        
1054        // update the choices in the control parameter
1055        updateExecutionChoices();
1056    }
1057    
1058    /** Remove a file input. */ 
1059    public void removeInput(String name) throws Exception {
1060        _removeInputOrOutput(name, true);
1061    }
1062
1063    /** Remove a file output. */
1064    public void removeOutput(String name) throws Exception {
1065        _removeInputOrOutput(name, false);
1066    }
1067    
1068    /** Remove a parameter. */
1069    public void removeParameter(String name) throws Exception {
1070        
1071        Attribute attribute = getAttribute(name);
1072        if(attribute == null) {
1073            throw new IllegalActionException(this, "No parameter called " + name);
1074        }
1075
1076        // remove first from command line since we know the argument, if any
1077        _removeFromCommandLines(name);
1078        
1079        // remove from any parameters that are referencing it
1080        _removeFromIOParameters(name);
1081        
1082        attribute.setContainer(null);
1083        
1084        _removeInDocumentation(name);
1085    }
1086
1087    /** Rename an execution choice. */
1088    public void renameExecutionChoice(String oldName, String newName) {
1089
1090        //System.out.println("rename choice " + oldName + " to " + newName);       
1091        _rename("entity", oldName, newName, null, this);
1092    }
1093
1094    /** Rename an input. */
1095    public void renameInput(String oldName, String newName) {
1096
1097        _renameInputOrOutput(oldName, newName, true);
1098    }
1099
1100    /** Rename a parameter. */
1101    public boolean renameParameter(String oldName, String newName) {
1102        return renameParameter(oldName, newName, this);
1103    }
1104    
1105    /** Rename a parameter in the specified container. */
1106    public boolean renameParameter(String oldName, String newName, NamedObj container) {
1107
1108        //System.out.println("rename parameter " + oldName + " to " + newName);   
1109
1110        boolean retval = false;
1111        
1112        if(container == this) {
1113            // save the command lines and clear them since rename is broken
1114            _saveAndClearCommandLines();
1115        }
1116        
1117        // do the rename
1118        boolean renamed = _rename("property", oldName, newName, null, container);
1119        
1120        if(container == this) {
1121                // see if the renamed failed
1122                if(!renamed) {
1123                        // restore the command lines to their original values
1124                        _restoreCommandLines();
1125                } else {
1126                    // update the command lines with the parameter renamed
1127                    _renameInCommandLines(oldName, newName);
1128                    
1129                    // update the documentation
1130                    try {
1131                                        _renameInDocumentation(oldName, newName);
1132                                } catch (Exception e) {
1133                                        try {
1134                                                MessageHandler.warning("Unable to update documentation.", e);
1135                                        } catch (CancelException e1) {
1136                                                // ignore
1137                                        }
1138                                }
1139                    
1140                    retval = true;
1141                }
1142        }
1143        
1144        return retval;
1145    }
1146
1147    /** Rename an output. */
1148    public void renameOutput(String oldName, String newName) {
1149        _renameInputOrOutput(oldName, newName, false);
1150    }
1151
1152    /** Set the command line argument for a parameter. */
1153    public static void setArgument(Parameter parameter, String argument)
1154            throws IllegalActionException, NameDuplicationException {
1155        StringParameter argumentParameter = (StringParameter) parameter.getAttribute(ARGUMENT_NAME);
1156        if(argumentParameter == null) {
1157            argumentParameter = new StringParameter(parameter, ARGUMENT_NAME);
1158        }
1159        argumentParameter.setExpression(argument);        
1160    }
1161
1162    /** Set the ParameterType for a parameter contained by this actor. */
1163    public void setParameterType(String name, ParameterType type) 
1164                throws IllegalActionException, NameDuplicationException {
1165        setParameterType(this, name, type);
1166    }
1167    
1168    /** Set the ParameterType for a parameter in contained in a sub-workflow. */
1169    public static void setParameterType(NamedObj container, String name, ParameterType type) 
1170                throws IllegalActionException, NameDuplicationException {
1171        
1172        Parameter parameter = (Parameter) container.getAttribute(name);
1173        if(parameter == null) {
1174                throw new IllegalActionException(container, "Parameter " + name +
1175                                " in " + container.getName() + " not found.");
1176        }
1177        
1178        // save the current value and argument
1179        final String value = parameter.getExpression();
1180        final String argument = getArgument(container, name);
1181        
1182        // FIXME this can bring up the warning about dependencies
1183        parameter.setContainer(null);
1184        if(type == ParameterType.String) {
1185                parameter = new StringParameter(container, name);
1186        } else {
1187                parameter = new Parameter(container, name);
1188        }
1189        
1190        // restore the current value and argument
1191        // XXX what happens if value is a string and the type is numeric?
1192        parameter.setExpression(value);
1193        if(argument != null) {
1194                setArgument(parameter, argument);
1195        }
1196                
1197    }
1198    
1199    /** Set the PortType for a port. */
1200    public void setPortIOType(TypedIOPort port, IOType type) 
1201                throws IllegalActionException, NameDuplicationException {
1202        
1203        final String portName = port.getName();
1204        
1205        _setPortIOTypeParameter(port, type);
1206        
1207        if(type == IOType.File) {
1208                
1209            port.setTypeEquals(BaseType.STRING);
1210
1211            // create the associated parameter
1212            /*StringParameter newParameter =*/ new StringParameter(this, portName);
1213            
1214            _addToCommandLines(portName);
1215            
1216        } else { // type == IOType.Data
1217                
1218                port.setTypeEquals(BaseType.UNKNOWN);
1219
1220                // remove the parameter from the command lines.
1221                // NOTE: this is done before removing the parameter since it
1222                // accessed the parameter argument.
1223            _removeFromCommandLines(portName);
1224
1225            // remove the name if referenced in any other parameter
1226            _removeFromIOParameters(portName);
1227            
1228                // remove the associated parameter and any argument
1229                StringParameter associatedParameter = (StringParameter) getAttribute(portName);
1230                
1231                // FIXME this can display the warning dialog about dependencies
1232                associatedParameter.setContainer(null);
1233                
1234        }
1235
1236        _updatePortsAndInsideLinks();
1237    }
1238    
1239    /** Update the execution choices in the control parameter. */
1240    public void updateExecutionChoices() throws IllegalActionException {
1241
1242        List<String> choices = new LinkedList<String>();
1243        
1244        List<StringParameter> existingChoices = _choiceStyle.attributeList(StringParameter.class);
1245        for(StringParameter parameter : existingChoices) {
1246            if(parameter.getName().startsWith("choice")) {
1247                String name = parameter.getExpression();
1248                // make sure refinement exists
1249                if(getEntity(name) == null) {
1250                    try {
1251                        // refinement no longer exists so remove the choice
1252                        parameter.setContainer(null);
1253                    } catch (NameDuplicationException e) {
1254                        throw new IllegalActionException(this, e, "Error removing choice " + name);
1255                    }
1256                } else {
1257                    choices.add(name);
1258                }
1259            }
1260        }
1261        
1262        for(Refinement refinement : entityList(Refinement.class)) {
1263            if(!choices.contains(refinement.getDisplayName())) {
1264                // add a new parameter contained by the choice style so that the
1265                // choice is saved to MoML.
1266                Parameter choiceParameter;
1267                try {
1268                    choiceParameter = new StringParameter(_choiceStyle, _choiceStyle.uniqueName("choice"));
1269                    choiceParameter.setExpression(refinement.getDisplayName());
1270                } catch (NameDuplicationException e) {
1271                    throw new IllegalActionException(this, e, "Error adding choice.");
1272                }
1273                choices.add(refinement.getDisplayName());
1274            }
1275        }
1276
1277        // see if current choice was removed
1278        if(!choices.isEmpty() &&
1279                        !choices.contains(((StringToken)control.getToken()).stringValue())) {
1280            // set the control choice to the default
1281            control.setExpression(choices.get(0));
1282        }
1283    }
1284
1285    /** Perform cleanup. Stop listening to input ports. */
1286    @Override
1287    public void wrapup() throws IllegalActionException {
1288        
1289        super.wrapup();
1290        
1291        for(Object object : inputPortList()) {
1292            IOPort port = (IOPort) object;
1293            if(getAttribute(port.getName()) != null) {
1294                port.removeIOPortEventListener(this);
1295            }
1296        }
1297        
1298    }
1299    
1300
1301    /** The command line program to execute. */
1302    public StringParameter program;
1303    
1304    /** Additional command line options. */
1305    public StringParameter additionalOptions;
1306        
1307    /** If true, verify the last modification timestamp for each
1308     *  output file has increased after execution. If the timestamp
1309     *  has not increased, throw an error.
1310     */
1311    public Parameter checkOutputTimestamp;
1312    
1313    /** The name of the default template. */
1314    public final static String DEFAULT_TEMPLATE_NAME = "LocalExecution";
1315
1316    /** The name of the default input directory. */
1317    public final static String DEFAULT_INPUT_DIR_NAME = "inputDir";
1318
1319    /** The name of the default output directory. */
1320    public final static String DEFAULT_OUTPUT_DIR_NAME = "outputDir";
1321
1322    /** The name of the default input file. */
1323    public final static String DEFAULT_INPUT_FILE_NAME = "inputFile";
1324
1325    /** The name of the default output file. */
1326    public final static String DEFAULT_OUTPUT_FILE_NAME = "outputFile";
1327    
1328    /** String constant to denote all execution choices. **/
1329    public final static String ALL_CHOICES_NAME = "All Choices";
1330    
1331    /** The name of the (optional) attribute contained in parameters.
1332     *  The value of this attribute is the command-line argument for
1333     *  the parameter, e.g., "-e".
1334     */
1335    public final static String ARGUMENT_NAME = "Argument";
1336    
1337    /** The name of the command line parameter in each refinement. */
1338    public final static String COMMAND_LINE_NAME = "commandLine";
1339
1340    /** The types of input/outputs. */
1341    public enum IOType {
1342        File,
1343        Data;        
1344    };
1345    
1346    /** The types of parameters. */
1347    public enum ParameterType {
1348        Numeric,
1349        String;
1350    }
1351    
1352    ///////////////////////////////////////////////////////////////////
1353    ////                       protected methods                   ////
1354
1355    /** Create a director. This base class creates an instance of CaseDirector.
1356     *  @return The created director.
1357     *  @exception IllegalActionException If the director cannot be created.
1358     *  @exception NameDuplicationException If there is already an
1359     *  attribute with the name "_director".
1360     */
1361    @Override
1362    protected ExecutionChoiceDirector _createDirector() throws IllegalActionException,
1363            NameDuplicationException {
1364        return new ExecutionChoiceDirector(this, "_director");
1365    }
1366
1367    /** Set the refinement to execute. */
1368    protected void _setCurrentRefinement(Refinement refinement) {
1369        _current = refinement;
1370    }
1371
1372    ///////////////////////////////////////////////////////////////////
1373    ////                         private method                    ////
1374
1375    /** Add a input/output/parameter to the command lines of each refinement. */
1376    private void _addToCommandLines(String name)
1377            throws IllegalActionException, NameDuplicationException {
1378
1379        // construct the string to add
1380        String toAdd = _makeCommandLineArgument(name);
1381        
1382        // see if we should add to beginning or end
1383        String argument = getArgument(name);
1384        boolean append = (argument != null && argument.equals(">"));
1385
1386        // update each refinement
1387        for(Refinement refinement : entityList(Refinement.class)) {
1388            Parameter commandLineParameter = (Parameter) refinement.getAttribute(COMMAND_LINE_NAME);
1389            if(commandLineParameter == null) {
1390                commandLineParameter = new StringParameter(refinement, COMMAND_LINE_NAME);
1391                //commandLineParameter.setLazy(true);
1392            }
1393            String expression = commandLineParameter.getExpression();
1394            if(append) {
1395                commandLineParameter.setExpression(expression + " " + toAdd);                
1396            } else {
1397                // add to the beginning of the command line, but after $program
1398                int index = expression.indexOf("$program");
1399                
1400                // make sure $program was found; otherwise do not add
1401                if(index > -1) {
1402                        
1403                        // if expression is only "$program", add a space and the argument to add
1404                        if(expression.equals("$program")) {
1405                                commandLineParameter.setExpression("$program " + toAdd + " ");
1406                        } else {                        
1407                            StringBuilder value = new StringBuilder(expression);
1408                            value.insert(index + "$program".length() + 1, toAdd + " ");
1409                            commandLineParameter.setExpression(value.toString());
1410                        }
1411                }
1412            }
1413            
1414            //System.out.println("add: new command line for " + refinement.getName() + ":" +
1415                //commandLineParameter.getExpression());
1416        }
1417
1418        // update the default command line
1419        if(append) {
1420            _commandLineArguments += " " + toAdd;
1421        } else {
1422            _commandLineArguments = toAdd + " " + _commandLineArguments;
1423        }
1424    }
1425
1426    /** Add the commandLine parameter to a refinement if it is not there. */
1427    private void _addCommandLineParameter(Refinement refinement) throws IllegalActionException {
1428        
1429        // see if the commandLine parameter exists in this refinement
1430        Parameter commandLineParameter = (Parameter) refinement.getAttribute(COMMAND_LINE_NAME);
1431        if(commandLineParameter == null) {
1432            try {
1433                commandLineParameter = new StringParameter(refinement, COMMAND_LINE_NAME);
1434                //commandLineParameter.setLazy(true);
1435            } catch (NameDuplicationException e) {
1436                throw new IllegalActionException(this, e, "Error adding commandLine parameter.");
1437            }
1438
1439            // set the default value
1440            commandLineParameter.setExpression("$program " + _commandLineArguments);
1441        }
1442    }
1443
1444    /** Get a list of input/output names.
1445     *  @param ports the input or output ports from which to get names.
1446     *  @param includeData if true, include Data ports in the list of names.
1447     *  otherwise, just return a list of File ports.
1448     */
1449    private List<String> _getIONames(List<?> ports, boolean includeData) throws IllegalActionException {
1450        final List<String> retval = new LinkedList<String>();
1451        for(Object object : ports) {
1452            if(object != control.getPort()) {
1453                final TypedIOPort port = (TypedIOPort) object;
1454                if(includeData || getPortIOType(port) == IOType.File) {
1455                    retval.add(port.getDisplayName());
1456                }
1457                /*
1458                if(getAttribute(port.getDisplayName()) != null) {
1459                    retval.add(port.getDisplayName());
1460                }
1461                */
1462            }
1463        }
1464        return retval;
1465    }
1466
1467    /** Returns true if there is an input/output with the given name.
1468     *  @param name The name to check.
1469     *  @param input If true, check inputs, otherwise check outputs.
1470     *  @return True if the input/output exists. 
1471     */
1472     private boolean _hasInputOutput(String name, boolean input) {
1473        TypedIOPort port = (TypedIOPort) getPort(name);
1474        if(port != null) {
1475                if(input) {
1476                        return port.isInput();
1477                } else {
1478                        return port.isOutput();
1479                }
1480        }
1481        return false;
1482    }
1483
1484    /** Initialize parameters and load the default template. */
1485    private void _init() throws NameDuplicationException, IllegalActionException {
1486        
1487        setClassName("org.kepler.ddp.actor.ExecutionChoice");
1488
1489        // remove the default refinement created by the parent class
1490        _default.setContainer(null);
1491        _default = null;
1492
1493        // hide the control port
1494        control.setStringMode(true);
1495        new SingletonAttribute(control.getPort(), "_hide");
1496        control.setDisplayName("Choice");
1497        control.setExpression(DEFAULT_TEMPLATE_NAME);
1498        control.removeAllChoices();
1499               
1500        program = new StringParameter(this, "program");
1501        program.setExpression("ls");
1502        
1503        additionalOptions = new StringParameter(this, "additionalOptions");
1504        
1505        new ExecutionChoiceEditorFactory(this, "_editor");
1506        new ExecutionChoiceEditorPane.Factory(this, "_editorPane");
1507        
1508        _choiceStyle = new EditableChoiceStyle(control, "style");
1509        
1510        // parse the template
1511        ModuleTree tree = ModuleTree.instance();
1512        Module module = tree.getModuleByStemName("ddp-common");
1513        if(module == null) {
1514            throw new IllegalActionException(this, "Could not find ddp-common module in suite.");
1515        }
1516                
1517        String templateDirPathStr = module.getResourcesDir().getAbsolutePath() +
1518                File.separator + "templates" + File.separator +
1519                "ExecutionChoice";
1520        _templateDir = new File(templateDirPathStr);
1521        
1522        checkOutputTimestamp = new Parameter(this, "checkOutputTimestamp");
1523        checkOutputTimestamp.setTypeEquals(BaseType.BOOLEAN);
1524        checkOutputTimestamp.setToken(BooleanToken.TRUE);
1525    }
1526    
1527    public void addDefaults() throws IllegalActionException, NameDuplicationException {        
1528                    
1529        addDefaultInputsAndOutputs();
1530        addDefaultExecutionChoice();
1531    }
1532    
1533    /** Add default execution choice if none are present. */
1534    public void addDefaultExecutionChoice() throws IllegalActionException {
1535        
1536        // see if there are any execution choices
1537        if(getExecutionChoiceNames().isEmpty()) {
1538            // load the default
1539            newExecutionChoice(DEFAULT_TEMPLATE_NAME, DEFAULT_TEMPLATE_NAME);   
1540        }
1541
1542    }
1543    
1544    /** Add default inputs and outputs if none are present. 
1545     *  @return true if inputs/outputs were created. 
1546     */
1547    public boolean addDefaultInputsAndOutputs() throws NameDuplicationException, IllegalActionException {
1548        return addDefaultInputsAndOutputs(this);
1549    }
1550    
1551    /** Add default inputs and outputs if non are present for an ExecutionChoice.
1552     *  @return true if inputs/outputs were created.
1553     */
1554    public static boolean addDefaultInputsAndOutputs(ExecutionChoice choice) 
1555                throws IllegalActionException, NameDuplicationException {
1556        
1557        // see if there are any inputs or outputs
1558        if(choice.getInputNames(true).isEmpty() &&
1559                        choice.getOutputNames(true).isEmpty()) {
1560            
1561            System.out.println("Adding default inputs, outputs for " + choice.getName());
1562            
1563            choice.newInput(DEFAULT_INPUT_FILE_NAME, IOType.File, "-i");
1564
1565            choice.newOutput(DEFAULT_OUTPUT_FILE_NAME, IOType.File, ">");
1566
1567            choice.newInput(DEFAULT_INPUT_DIR_NAME, IOType.File, "-i");
1568
1569            choice.newOutput(DEFAULT_OUTPUT_DIR_NAME, IOType.File, ">");
1570
1571            // repaint the canvas to show new ports
1572            MoMLChangeRequest change = 
1573                    new MoMLChangeRequest(choice, choice, "<group></group>");
1574            change.setPersistent(false);
1575            choice.requestChange(change);
1576            
1577            return true;
1578        }
1579        
1580        return false;
1581
1582    }
1583
1584    /** Get the command line string for an input/output/parameter. */
1585    private String _makeCommandLineArgument(String name) throws IllegalActionException {
1586        
1587        // construct the string to add to the command lines
1588        StringBuilder str = new StringBuilder();
1589        
1590        // see if there's a argument
1591        String argument = getArgument(name);
1592        if(argument != null) {
1593            str.append("$(");
1594            str.append(name);
1595            str.append("::");
1596            str.append(ARGUMENT_NAME);
1597            str.append(") ");
1598        }
1599        
1600        // add the name
1601        str.append("$");
1602        str.append(name); 
1603        return str.toString();
1604    }
1605
1606    /** Create a new port and parameter for a new input or output.
1607     * @param name the name of the input/output
1608     * @param type the IOType of the input/output 
1609     * @param argument the command line argument for the new input
1610     * or output. this is only valid for IOType.File inputs/outputs,
1611     * and can be null.
1612     * @param isInput if true, the port is an input port. if false, the
1613     * ports is an output port.
1614     */
1615    private void _newInputOrOutput(String name, IOType type, String argument, boolean isInput)
1616            throws IllegalActionException, NameDuplicationException {
1617        
1618        if(type != IOType.File && argument != null && !argument.isEmpty()) {
1619            throw new IllegalActionException(this, "Command-line arguments can only be " +
1620                    "specified for File types.");
1621        }
1622
1623        TypedIOPort port = (TypedIOPort) newPort(name);
1624        new SingletonAttribute(port, "_showName");
1625        
1626        // set the port to be input or output
1627        // NOTE: this must be done before _showOrHideInsidePort() is called
1628        // (in _updatePortsAndInsideLinks()) since it hides outputs ports.
1629        if(isInput) {
1630            port.setInput(true);
1631        } else {
1632            port.setOutput(true);
1633        }
1634
1635        _setPortIOTypeParameter(port, type);
1636        
1637        if(type == IOType.File) {
1638            port.setTypeEquals(BaseType.STRING);
1639
1640            // create the associated parameter
1641            StringParameter newOutputParameter = new StringParameter(this, name);
1642
1643            if(argument != null && !argument.isEmpty()) {
1644                setArgument(newOutputParameter, argument);
1645            }
1646            
1647            _addToCommandLines(name);
1648        }
1649
1650        _updatePortsAndInsideLinks();
1651    }
1652
1653    /** Remove a input/output/parameter from the refinement command lines.*/
1654    private void _removeFromCommandLines(String name)
1655            throws IllegalActionException, NameDuplicationException {
1656
1657        // construct the string to remove
1658        String toRemove = _makeCommandLineArgument(name);
1659        
1660        for(Refinement refinement : entityList(Refinement.class)) {
1661            Parameter commandLineParameter = (Parameter) refinement.getAttribute(COMMAND_LINE_NAME);
1662            if(commandLineParameter == null) {
1663                commandLineParameter = new StringParameter(refinement, COMMAND_LINE_NAME);
1664                //commandLineParameter.setLazy(true);
1665            }
1666            String expression = commandLineParameter.getExpression();
1667            int index = expression.indexOf(toRemove);
1668            if(index > -1) {
1669                String value = expression.substring(0, index) +
1670                        expression.substring(index + toRemove.length());
1671                commandLineParameter.setExpression(value.trim());
1672                commandLineParameter.validate();
1673            }
1674            
1675            //System.out.println("remove: new command line for " + refinement.getName() + ":" +
1676                //commandLineParameter.getExpression());
1677
1678        }
1679        
1680        // remove form the default command line
1681        int index = _commandLineArguments.indexOf(toRemove);
1682        if(index > -1) {
1683            String value = _commandLineArguments.substring(0, index) +
1684                    _commandLineArguments.substring(index + toRemove.length());
1685            _commandLineArguments = value.trim();
1686        }
1687    }
1688    
1689    /** Remove a name that is referenced in any input/output parameters. */
1690    private void _removeFromIOParameters(String name) throws IllegalActionException {
1691        
1692        String toRemove = "$" + name;
1693        
1694        List<String> toCheck = new LinkedList<String>();
1695        toCheck.addAll(getInputNames(false));
1696        toCheck.addAll(getOutputNames(false));
1697        
1698        for(String toCheckName : toCheck) {
1699            boolean changed = false;
1700            Parameter parameter = (Parameter) getAttribute(toCheckName);
1701            String expression = parameter.getExpression();
1702            int index = expression.indexOf(toRemove);
1703            while(index > -1) {
1704                changed = true;
1705                expression = expression.substring(0, index) +
1706                        expression.substring(index + toRemove.length());
1707                index = expression.indexOf(toRemove);
1708            }
1709            if(changed) {
1710                parameter.setExpression(expression.trim());
1711                parameter.validate();
1712            }
1713        }   
1714    }
1715    
1716    /** Remove a port or parameter from the documentation. */
1717    private void _removeInDocumentation(String name) throws Exception {
1718        
1719        List<KeplerDocumentationAttribute> docList = attributeList(KeplerDocumentationAttribute.class);
1720        for(KeplerDocumentationAttribute docAttribute : docList) {
1721                
1722                // see if the hash tables have been initialized
1723                Hashtable<?,?> portHash = docAttribute.getPortHash();
1724                if(portHash.isEmpty()) {
1725                        docAttribute.createInstanceFromExisting(docAttribute);
1726                }
1727
1728                        docAttribute.removePort(name);
1729                        docAttribute.removeProperty(name);
1730        }
1731    }
1732    
1733    /** Remove a file input/output. */
1734    private void _removeInputOrOutput(String name, boolean isInput) throws Exception {
1735        
1736        Port port = getPort(name);
1737        if(port == null) {
1738            throw new IllegalActionException(this, "Could not find " +
1739                        (isInput? "intput" : "output") + " port " + name);
1740        }
1741        
1742        final IOType type = getPortIOType(port);
1743        
1744        port.setContainer(null);
1745
1746        if(type == IOType.File) {
1747            removeParameter(name);
1748        }
1749        
1750        _removeInDocumentation(name);
1751    }
1752
1753    /** Rename a port and parameter in the documentation. */
1754    private void _renameInDocumentation(String oldName, String newName) throws Exception {
1755        
1756        List<KeplerDocumentationAttribute> docList = attributeList(KeplerDocumentationAttribute.class);
1757        for(KeplerDocumentationAttribute docAttribute : docList) {
1758                
1759                // see if the hash tables have been initialized
1760                Hashtable<?,?> portHash = docAttribute.getPortHash();
1761                if(portHash.isEmpty()) {
1762                        docAttribute.createInstanceFromExisting(docAttribute);
1763                }
1764                
1765                String portDocStr = docAttribute.getPort(oldName);
1766                if(portDocStr != null) {
1767                        docAttribute.removePort(oldName);
1768                        docAttribute.addPort(newName, portDocStr);
1769                }
1770                
1771                String parameterDocStr = docAttribute.getProperty(oldName);
1772                if(parameterDocStr != null) {
1773                        docAttribute.removeProperty(oldName);
1774                        docAttribute.addProperty(newName, parameterDocStr);
1775                }
1776        }
1777    }
1778    
1779    /** Rename a parameter in each of the refinement command lines. */
1780    private void _renameInCommandLines(String oldName, String newName) {
1781        
1782        for(Refinement refinement : entityList(Refinement.class)) {
1783            Parameter commandLineParameter = (Parameter) refinement.getAttribute(COMMAND_LINE_NAME);
1784            // see if the command line exists in this refinement
1785            if(commandLineParameter != null) {
1786                
1787                // get the expression saved before the rename
1788                String expression = _refinementCommandLines.get(refinement);
1789                
1790                // do the rename in the expression
1791                String value = expression.replaceAll(Pattern.quote("$" + oldName),
1792                    Matcher.quoteReplacement("$" + newName));
1793                
1794                // rename the parameter name in references to the argument, e.g.,:
1795                // $(inputFile::argument)
1796                value = value.replaceAll(Pattern.quote("$(" + oldName + "::"),
1797                        Matcher.quoteReplacement("$(" + newName + "::"));
1798                
1799                // set the new value in the parameter
1800                commandLineParameter.setExpression(value);
1801                
1802                //System.out.println("rename: new command line for " + refinement.getName() + ":" +
1803                    //commandLineParameter.getExpression());
1804
1805            }
1806        }
1807        
1808        // update the default command line
1809        String value = _commandLineArguments.replaceAll(Pattern.quote("$" + oldName),
1810                Matcher.quoteReplacement("$" + newName));
1811        value = value.replaceAll(Pattern.quote("$(" + oldName + "::"),
1812                Matcher.quoteReplacement("$(" + newName + "::"));
1813        _commandLineArguments = value;
1814        
1815    }
1816
1817    /** Rename a component.
1818     * @param type the type of component to rename: property, port, or entity.
1819     * @param oldName the old name
1820     * @param newName the new name
1821     * @param newDisplayName the new display name. can be null
1822     */
1823    private boolean _rename(String type, String oldName, String newName,
1824            String newDisplayName, NamedObj container) {
1825
1826        // parameters:
1827        //<property name="Parameter"><rename name="name2"/></property>
1828        
1829        // ports:
1830        // <port name="a"><rename name="b"/></port>
1831
1832        // refinements:
1833        // <entity name="Ramp"><rename name="r2"/></entity>
1834
1835        StringBuilder moml = new StringBuilder("<");
1836        moml.append(type);
1837        moml.append(" name=\"");
1838        moml.append(oldName);
1839        moml.append("\"><rename name=\"");
1840        moml.append(newName);
1841        moml.append("\"/>");
1842        if(newDisplayName != null) {
1843            moml.append("<display name=\"");
1844            moml.append(newDisplayName);
1845            moml.append("\"/>");
1846        }
1847        moml.append("</");
1848        moml.append(type);
1849        moml.append(">");
1850        
1851        MoMLChangeRequest request = new MoMLChangeRequest(this, container, moml.toString());
1852        request.addChangeListener(this);
1853        _changeRequestError = false;
1854        requestChange(request);
1855        executeChangeRequests();
1856        return !_changeRequestError;
1857        
1858    }
1859    
1860    /** Rename an input or output.
1861     *  
1862     *  @param oldName the old name
1863     *  @param newName the new name
1864     *  @param isInput if true, oldName refers to an input. 
1865     *  if false, oldName refers to an output.
1866     */
1867    private void _renameInputOrOutput(String oldName, String newName, boolean isInput) {
1868
1869        //System.out.println("rename input/output " + oldName + " to " + newName);
1870        boolean renamedParameter = renameParameter(oldName, newName);
1871        
1872        if(renamedParameter) {
1873                
1874                // set the display name if we're renaming an output
1875                String newDisplayName = null;
1876                if(!isInput) {
1877                        newDisplayName = newName;
1878                }
1879                
1880                boolean renamedPort = _rename("port", oldName, newName, newDisplayName, this);
1881                
1882                // if we failed to rename the port, rename the parameter back
1883                // to the original name
1884                if(!renamedPort) {
1885                        renameParameter(newName, oldName);
1886                }
1887        }
1888    }
1889
1890    /** Restore the command line without changing them. */
1891    private void _restoreCommandLines() {
1892        
1893        for(Refinement refinement : entityList(Refinement.class)) {
1894            Parameter commandLineParameter = (Parameter) refinement.getAttribute(COMMAND_LINE_NAME);
1895            // see if the command line exists in this refinement
1896            if(commandLineParameter != null) {
1897                
1898                // get the expression saved before the rename
1899                String expression = _refinementCommandLines.get(refinement);
1900                                
1901                commandLineParameter.setExpression(expression);                
1902            }
1903        }        
1904    }
1905    
1906    /** Save command line parameters in each refinement and clear them. This must be done
1907     *  before a rename, since something is currently broken in MoMLParser for renames. 
1908     */
1909    private void _saveAndClearCommandLines() {
1910
1911        _refinementCommandLines.clear();
1912        
1913        for(Refinement refinement : entityList(Refinement.class)) {
1914            Parameter commandLineParameter = (Parameter) refinement.getAttribute(COMMAND_LINE_NAME);
1915            // see if the command line exists in this refinement
1916            if(commandLineParameter != null) {
1917                
1918                // save the expression
1919                String expression = commandLineParameter.getExpression();
1920                _refinementCommandLines.put(refinement, expression);
1921                
1922                // clear the expression
1923                commandLineParameter.setExpression(" ");
1924            }
1925        }
1926    }
1927    
1928    /** Set the IOType parameter for a port. */
1929    private static void _setPortIOTypeParameter(Port port, IOType type) 
1930                throws IllegalActionException, NameDuplicationException {
1931        
1932        Parameter typeParameter = (Parameter) port.getAttribute(IO_TYPE_NAME);
1933        if(typeParameter == null) {
1934            typeParameter = new StringParameter(port, IO_TYPE_NAME);
1935        }
1936        typeParameter.setExpression(type.toString());
1937    }
1938
1939    /** Show or hide the mirrored ports connected to a port contained
1940     *  by this actor based on the IOType.
1941     */
1942    private void _showOrHideInsidePort(MultiCompositePort port) 
1943        throws NameDuplicationException, IllegalActionException {
1944        
1945        // only show or hide output ports
1946        if(port.isOutput()) {
1947            
1948            // get the type
1949            IOType type = getPortIOType(port);
1950            
1951            // hide the inside port
1952            
1953            // NOTE: do not use insideSourcePortList() since it does not return transparent ports.
1954            //List<IOPort> insidePorts = port.insideSourcePortList();
1955            List<IOPort> insidePorts = port.insidePortList();
1956            
1957            //System.out.println("checking inside ports for " + port.getName() + ":");
1958            
1959            for(IOPort insidePort : insidePorts) {
1960                
1961                //System.out.println("    " + insidePort.getFullName());
1962                
1963                switch(type) {
1964                case File:
1965                    new SingletonAttribute(insidePort, "_hideInside");
1966                    //System.out.println("        hiding inside file port " + port.getName());
1967                    break;
1968                case Data:
1969                    Attribute attribute = insidePort.getAttribute("_hideInside");
1970                    if(attribute != null) {
1971                        attribute.setContainer(null);
1972                    }
1973                    //System.out.println("        showing inside data port " + port.getName());
1974                    break;
1975                }
1976            }
1977        }
1978    }
1979
1980    /** Make sure that each port in this actor has a corresponding port in 
1981     *  each refinement and that they are linked together.
1982     */
1983    private void _updatePortsAndInsideLinks() throws IllegalActionException {
1984            
1985        for(Refinement refinement : entityList(Refinement.class)) {
1986        
1987            // add ports present in the container, but not in the refinement
1988            Set<Port> portsToMirror = new HashSet<Port>();
1989            for(Object containerPortObject : portList()) {
1990                if(containerPortObject != control.getPort()) {
1991                    final Port containerPort = (Port) containerPortObject;
1992                    final String portName = containerPort.getName();
1993                    
1994                    // see if the refinement has the port
1995                    final Port refinementPort = refinement.getPort(portName);
1996                    if(refinementPort == null) {
1997                        //System.out.println("mirroring port found in container " + containerPort);
1998                        portsToMirror.add(containerPort);
1999                    } else {
2000    
2001                        // see if the container and refinement ports are linked
2002                        final String relationName = portName + "Relation";
2003                        Relation relation = getRelation(relationName);
2004                        if (relation == null) {
2005                            try {
2006                                relation = newRelation(relationName);
2007                                //System.out.println("creating relation " + relationName);
2008                            } catch (NameDuplicationException e) {
2009                                throw new IllegalActionException(this, e, "Error linking port " + portName);
2010                            }
2011                        }
2012                            
2013                        if(!((ComponentPort) containerPort).isInsideLinked(relation)) {
2014                            containerPort.link(relation);
2015                            //System.out.println("linking " + relationName + " to " + containerPort);
2016                        }
2017                        if(!refinementPort.isLinked(relation)) {
2018                            refinementPort.link(relation);
2019                            //System.out.println("linking " + relationName + " to " + refinementPort);
2020                        }
2021                    }
2022                }
2023            }
2024            
2025            try {
2026                MultiCompositeActor.mirrorContainerPortsInRefinement(refinement, portsToMirror);
2027            } catch (NameDuplicationException e) {
2028                throw new IllegalActionException(this, e, "Error adding ports to choice.");
2029            }
2030                
2031            // add ports present in the refinement, but not in the container, unless
2032            // they are the default input/output ports.
2033            portsToMirror.clear();
2034            portsToMirror.addAll(refinement.portList());
2035            for(Port port : portsToMirror) {
2036                final String portName = port.getName();
2037                if(port != control.getPort() && getPort(portName) == null &&
2038                                !portName.equals(DEFAULT_INPUT_FILE_NAME) &&
2039                                !portName.equals(DEFAULT_INPUT_DIR_NAME) &&
2040                                !portName.equals(DEFAULT_OUTPUT_FILE_NAME) &&
2041                                !portName.equals(DEFAULT_OUTPUT_DIR_NAME)) {
2042                    try {
2043                        //System.out.println("mirroring port found in template " + port);
2044                        IOPort containerPort = (IOPort) newPort(portName);
2045                        if(((IOPort) port).isInput()) {
2046                            containerPort.setInput(true);
2047                        } else {
2048                            containerPort.setOutput(true);
2049                        }
2050                        new SingletonAttribute(port, "_showName");
2051                    } catch (NameDuplicationException e) {
2052                        throw new IllegalActionException(this, e, "Error adding ports to container.");
2053                    }
2054                }
2055            }
2056        }
2057        
2058        // show or hide all the inside ports
2059        for(Object port : portList()) {
2060            try {
2061                if(port != control.getPort()) {
2062                    _showOrHideInsidePort((MultiCompositePort) port);
2063                }
2064            } catch (NameDuplicationException e) {
2065                throw new IllegalActionException(this, e, "Error show/hiding inside port.");
2066            }
2067        }
2068
2069    }
2070
2071    
2072    /** If true, make sure last modification time for each output file
2073     *  increases after executing the template.
2074     */
2075    private boolean _checkOutputTimestampVal = true;
2076    
2077    /** Directory containing templates. */
2078    private File _templateDir;
2079    
2080    private EditableChoiceStyle _choiceStyle;
2081
2082    /** The default command line used for new refinements. */
2083    private String _commandLineArguments = "$additionalOptions";
2084    
2085    /** The template name used to create an empty refinement. */
2086    private final static String EMPTY_TEMPLATE_NAME = "Blank";
2087    
2088    /** The name of the attribute contained in input/output File ports.
2089     *  The value of this attribute is the IOType.
2090     */
2091    private final static String IO_TYPE_NAME = "_ioType";
2092    
2093    /** A map to save all the refinement command lines before a rename occurs. */
2094    private Map<Refinement, String> _refinementCommandLines = new HashMap<Refinement, String>();
2095
2096    /** If true, an error occurred during a change request originating in this actor. */
2097    private boolean _changeRequestError = false;
2098
2099}