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}