001/* Base class for Ptolemy configurations. 002 003 Copyright (c) 2000-2018 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 */ 027package ptolemy.actor.gui; 028 029import java.io.File; 030import java.lang.reflect.Constructor; 031import java.lang.reflect.Field; 032import java.lang.reflect.Modifier; 033import java.net.URI; 034import java.net.URL; 035import java.util.HashSet; 036import java.util.Iterator; 037import java.util.LinkedList; 038import java.util.List; 039import java.util.Set; 040 041import ptolemy.actor.ApplicationConfigurer; 042import ptolemy.actor.InstanceOpener; 043import ptolemy.actor.PubSubPort; 044import ptolemy.actor.TypedAtomicActor; 045import ptolemy.data.ArrayToken; 046import ptolemy.data.BooleanToken; 047import ptolemy.data.StringToken; 048import ptolemy.data.Token; 049import ptolemy.data.expr.Parameter; 050import ptolemy.data.expr.StringParameter; 051import ptolemy.data.type.BaseType; 052import ptolemy.graph.Inequality; 053import ptolemy.graph.InequalityTerm; 054import ptolemy.kernel.ComponentEntity; 055import ptolemy.kernel.CompositeEntity; 056import ptolemy.kernel.Entity; 057import ptolemy.kernel.InstantiableNamedObj; 058import ptolemy.kernel.Port; 059import ptolemy.kernel.attributes.URIAttribute; 060import ptolemy.kernel.util.Attribute; 061import ptolemy.kernel.util.IllegalActionException; 062import ptolemy.kernel.util.InternalErrorException; 063import ptolemy.kernel.util.NameDuplicationException; 064import ptolemy.kernel.util.NamedObj; 065import ptolemy.kernel.util.Settable; 066import ptolemy.kernel.util.Workspace; 067import ptolemy.moml.MoMLFilter; 068import ptolemy.moml.MoMLParser; 069import ptolemy.moml.filter.RemoveClasses; 070import ptolemy.moml.filter.RemoveGraphicalClasses; 071import ptolemy.util.ClassUtilities; 072import ptolemy.util.MessageHandler; 073import ptolemy.util.StringUtilities; 074 075/////////////////////////////////////////////////////////////////// 076//// Configuration 077 078/** 079 The configuration of an application that uses Ptolemy II classes. 080 An instance of this class is in charge of the user interface, 081 and coordinates multiple views of multiple models. One of its 082 functions, for example, is to manage the opening of new models, 083 ensuring that an appropriate view is used. It also makes sure that 084 if a model is opened that is already open, then existing views are 085 shown rather than creating new views. 086 <p> 087 The applications <i>vergil</i> and <i>moml</i> (at least) use 088 configurations defined in MoML files, typically located in 089 ptII/ptolemy/configs. The <i>moml</i> application takes as 090 command line arguments a list of MoML files, the first of which 091 is expected to define an instance of Configuration and its contents. 092 That configuration is then used to open subsequent MoML files on the 093 command line, and to manage the user interface. 094 <p> 095 Rather than performing all these functions itself, this class 096 is a container for a model directory, effigy factories, and tableau 097 factories that actually realize these functions. An application 098 is configured by populating an instance of this class with 099 a suitable set of these other classes. A minimal configuration 100 defined in MoML is shown below: 101 <pre> 102 <?xml version="1.0" standalone="no"?> 103 <!DOCTYPE entity PUBLIC "-//UC Berkeley//DTD MoML 1//EN" 104 "http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd"> 105 <entity name="configuration" class="ptolemy.actor.gui.Configuration"> 106 <doc>Configuration to run but not edit Ptolemy II models</doc> 107 <entity name="directory" class="ptolemy.actor.gui.ModelDirectory"/> 108 <entity name="effigyFactory" class="ptolemy.actor.gui.PtolemyEffigy$Factory"/> 109 <property name="tableauFactory" class="ptolemy.actor.gui.RunTableau$Factory"/> 110 </entity> 111 </pre> 112 <p> 113 It must contain, at a minimum, an instance of ModelDirectory, named 114 "directory", and an instance of EffigyFactory, named "effigyFactory". 115 The openModel() method delegates to the effigy factory the opening of a model. 116 It may also contain an instance of TextEditorTableauFactory, named "tableauFactory". 117 A tableau is a visual representation of the model in a top-level window. 118 The above minimal configuration can be used to run Ptolemy II models 119 by opening a run panel only. 120 <p> 121 When the directory becomes empty (all models have been closed), 122 it removes itself from the configuration. When this happens, the 123 configuration calls System.exit() to exit the application. 124 125 <p>To access the configuration from a random place, if you have a 126 NamedObj <code>foo</code>, then you can call: 127 <pre> 128 Effigy effigy = Configuration.findEffigy(foo.toplevel()); 129 Configuration configuration = effigy.toplevel(); 130 </pre> 131 132 133 @author Steve Neuendorffer and Edward A. Lee 134 @version $Id$ 135 @since Ptolemy II 1.0 136 @Pt.ProposedRating Green (eal) 137 @Pt.AcceptedRating Yellow (celaine) 138 @see EffigyFactory 139 @see ModelDirectory 140 @see Tableau 141 @see TextEditorTableau 142 */ 143public class Configuration extends CompositeEntity 144 implements ApplicationConfigurer, InstanceOpener { 145 /** Construct an instance in the specified workspace with an empty 146 * string as a name. You can then change the name with setName(). 147 * If the workspace argument is null, then use the default workspace. 148 * Add the instance to the workspace directory. 149 * Increment the version number of the workspace. 150 * Note that there is no constructor that takes a container 151 * as an argument; a Configuration is always 152 * a top-level entity (this is enforced by the setContainer() 153 * method). 154 * @param workspace The workspace that will list the entity. 155 * @exception IllegalActionException If the container is incompatible 156 * with this entity. 157 * @exception NameDuplicationException If the name coincides with 158 * an entity already in the container. 159 */ 160 public Configuration(Workspace workspace) 161 throws IllegalActionException, NameDuplicationException { 162 super(workspace); 163 _configurations.add(this); 164 classesToRemove = new Parameter(this, "_classesToRemove", 165 new ArrayToken(BaseType.STRING)); 166 //classesToRemove.setTypeEquals(new ArrayType(BaseType.STRING)); 167 removeGraphicalClasses = new Parameter(this, "_removeGraphicalClasses", 168 BooleanToken.FALSE); 169 removeGraphicalClasses.setTypeEquals(BaseType.BOOLEAN); 170 } 171 172 /////////////////////////////////////////////////////////////////// 173 //// public variables //// 174 175 /** A Parameter that is an array of Strings where each element 176 * names a class to be removed. The initial default value is 177 * an array with an empty element. 178 * <p> Kepler uses this parameter to remove certain classes: 179 * <pre> 180 * <property name="_classesToRemove" class="ptolemy.data.expr.Parameter" 181 * value="{"ptolemy.codegen.kernel.StaticSchedulingCodeGenerator","ptolemy.codegen.c.kernel.CCodeGenerator"}"> 182 * <doc>An array of Strings, where each element names a class 183 * to removed by the MoMLFilter.</doc> 184 * >/property> 185 * </pre> 186 */ 187 public Parameter classesToRemove; 188 189 /** A Parameter that if set to true adds {@link 190 * ptolemy.moml.filter.RemoveGraphicalClasses} to the list of 191 * MoMLFilters. Use this to run non-graphical classes. Note that 192 * setting this parameter and using MoMLApplication is not likely 193 * to work as MoMLApplication sets the look and feel which invokes 194 * the graphical system. The initial value is a boolean with the 195 * value false, indicating that RemoveGraphicalClasses should not 196 * be added to the filter list. 197 */ 198 public Parameter removeGraphicalClasses; 199 200 /////////////////////////////////////////////////////////////////// 201 //// public methods //// 202 203 /** React to a change in an attribute. 204 * @param attribute The attribute that changed. 205 * @exception IllegalActionException If the change is not acceptable 206 * to this container (not thrown in this base class). 207 */ 208 @Override 209 public void attributeChanged(Attribute attribute) 210 throws IllegalActionException { 211 if (attribute == classesToRemove) { 212 // FIXME: There is code duplication here, but 213 // combining the code caused problems because getting 214 // calling getToken() on the other attribute resulted in 215 // attributeChanged() being called for the other token 216 // which caused rentry problems. Basically, we ended 217 // up with two RemoveGraphicalClasses filters in the list. 218 219 // Find the RemoveClasses element, if any. 220 RemoveClasses removeClassesFilter = null; 221 List momlFilters = MoMLParser.getMoMLFilters(); 222 if (momlFilters == null) { 223 momlFilters = new LinkedList(); 224 } else { 225 Iterator filters = momlFilters.iterator(); 226 while (filters.hasNext()) { 227 MoMLFilter filter = (MoMLFilter) filters.next(); 228 if (filter instanceof RemoveClasses) { 229 removeClassesFilter = (RemoveClasses) filter; 230 break; 231 } 232 } 233 } 234 235 // Get the token 236 ArrayToken classesToRemoveToken = (ArrayToken) classesToRemove 237 .getToken(); 238 if (removeClassesFilter == null) { 239 // We did not find a RemoveGraphicalClasses, so create one. 240 removeClassesFilter = new RemoveClasses(); 241 momlFilters.add(removeClassesFilter); 242 } 243 244 // We always clear 245 RemoveClasses.clear(); 246 247 // _classesToRemove is an array of Strings where each element 248 // names a class to be added to the MoMLFilter for removal. 249 for (int i = 0; i < classesToRemoveToken.length(); i++) { 250 String classNameToRemove = ((StringToken) classesToRemoveToken 251 .getElement(i)).stringValue(); 252 removeClassesFilter.put(classNameToRemove, null); 253 } 254 255 MoMLParser.setMoMLFilters(momlFilters); 256 } else if (attribute == removeGraphicalClasses) { 257 // Find the RemoveGraphicalClasses element, if any 258 RemoveGraphicalClasses removeGraphicalClassesFilter = null; 259 List momlFilters = MoMLParser.getMoMLFilters(); 260 if (momlFilters == null) { 261 momlFilters = new LinkedList(); 262 } else { 263 Iterator filters = momlFilters.iterator(); 264 while (filters.hasNext()) { 265 MoMLFilter filter = (MoMLFilter) filters.next(); 266 if (filter instanceof RemoveGraphicalClasses) { 267 removeGraphicalClassesFilter = (RemoveGraphicalClasses) filter; 268 break; 269 } 270 } 271 } 272 // Get the token 273 BooleanToken removeGraphicalClassesToken = (BooleanToken) removeGraphicalClasses 274 .getToken(); 275 if (removeGraphicalClassesToken.booleanValue()) { 276 if (removeGraphicalClassesFilter == null) { 277 // We did not find a RemoveGraphicalClasses, so create one. 278 removeGraphicalClassesFilter = new RemoveGraphicalClasses(); 279 momlFilters.add(removeGraphicalClassesFilter); 280 } 281 } else { 282 if (removeGraphicalClassesFilter != null) { 283 momlFilters.remove(removeGraphicalClassesFilter); 284 } 285 } 286 MoMLParser.setMoMLFilters(momlFilters); 287 } 288 super.attributeChanged(attribute); 289 } 290 291 /** Check the configuration for common style problems. 292 * @return HTML describing the problems 293 * @exception Exception If there is a problem cloning the configuration. 294 */ 295 public String check() throws Exception { 296 StringBuffer results = new StringBuffer(); 297 Configuration cloneConfiguration = (Configuration) clone( 298 new Workspace("clonedCheckWorkspace")); 299 300 // Check TypedAtomicActors and Attributes 301 Iterator containedObjects = deepNamedObjList().iterator(); 302 while (containedObjects.hasNext()) { 303 NamedObj containedObject = (NamedObj) containedObjects.next(); 304 // System.out.println("Configuration.check: containedObject: " + containedObject); 305 // Check the clone fields on AtomicActors and Attributes. 306 // Note that Director extends Attribute, so we get the 307 // Directors as well 308 if (containedObject instanceof TypedAtomicActor 309 || containedObject instanceof Attribute) { 310 try { 311 results.append(checkCloneFields(containedObject)); 312 } catch (Throwable throwable) { 313 throw new InternalErrorException(containedObject, null, 314 throwable, 315 "The check for " + "clone methods properly setting " 316 + "the fields failed."); 317 } 318 } 319 } 320 321 for (CompositeEntity composite : deepCompositeEntityList()) { 322 CompositeEntity clonedComposite = (CompositeEntity) composite 323 .clone(new Workspace("clonedCompositeWorkspace")); 324 Iterator attributes = composite.attributeList().iterator(); 325 while (attributes.hasNext()) { 326 Attribute attribute = (Attribute) attributes.next(); 327 // System.out.println("Configuration.check: attribute: " + attribute); 328 if (!attribute.getClass().isMemberClass()) { 329 // If an attribute is an inner class, it makes 330 // no sense to clone to a different workspace because 331 // the outer class will remain in the old workspace. 332 // For example, IterateOverArray has an inner class 333 // (IterateDirector) that is an attribute. 334 try { 335 results.append(checkCloneFields(attribute)); 336 } catch (Throwable throwable) { 337 throw new InternalErrorException(attribute, null, 338 throwable, 339 "The check for " 340 + "clone methods properly setting " 341 + "the fields failed."); 342 } 343 } else { 344 // Clone the composite and check that any inner 345 // classes are properly cloned. 346 String name = attribute.getName(); 347 Attribute clonedAttribute = clonedComposite 348 .getAttribute(name); 349 if (clonedAttribute == null) { 350 results.append("Could not find \"" + name + "\" in " 351 + composite.getFullName() + "\n" 352 + clonedComposite.description()); 353 } else { 354 if (attribute.workspace() 355 .equals(clonedAttribute.workspace())) { 356 results.append("\nIn attribute " 357 + attribute.getFullName() 358 + ", the workspace is the same in the master and " 359 + " the clone?"); 360 } 361 // Check that the Workspace of the outer class is different. 362 Class attributeClass = attribute.getClass(); 363 364 if (!Modifier.isStatic(attributeClass.getModifiers())) { 365 // Get the this$0 field, which refers to the outer class. 366 Field thisZeroField = null; 367 try { 368 thisZeroField = attributeClass 369 .getDeclaredField("this$0"); 370 } catch (Throwable throwable) { 371 results.append("Class " 372 + attributeClass.getName() 373 + " does not have a this$0 field?\n"); 374 } 375 if (thisZeroField != null) { 376 thisZeroField.setAccessible(true); 377 378 Object outer = thisZeroField.get(attribute); 379 if (Class 380 .forName("ptolemy.kernel.util.NamedObj") 381 .isAssignableFrom(outer.getClass())) { 382 NamedObj outerNamedObj = (NamedObj) outer; 383 NamedObj clonedOuterNamedObj = (NamedObj) thisZeroField 384 .get(clonedAttribute); 385 if (outerNamedObj.workspace().equals( 386 clonedOuterNamedObj.workspace())) { 387 results.append( 388 "\nAn inner class instance has the same workspace in the outer " 389 + "class in both the original and the cloned attribute." 390 + "\n Attribute: " 391 + attribute 392 .getFullName() 393 + "\n Cloned attribute: " 394 + clonedAttribute 395 .getFullName() 396 + "\n Outer Workspace: " 397 + outerNamedObj 398 .workspace() 399 + "\n ClonedOuter Workspace: " 400 + clonedOuterNamedObj 401 .workspace() 402 + "\n Outer Object: " 403 + outerNamedObj 404 .getFullName() 405 + "\n Cloned Outer Object: " 406 + clonedOuterNamedObj 407 .getFullName()); 408 } 409 } 410 } 411 } 412 } 413 } 414 } 415 clonedComposite.setContainer(null); 416 } 417 418 // Check atomic actors for clone problems related to types 419 List entityList = allAtomicEntityList(); 420 Iterator entities = entityList.iterator(); 421 long startTime = (new java.util.Date()).getTime(); 422 while (entities.hasNext()) { 423 Object entity = entities.next(); 424 System.out.println("Configuration.check: entity: " + entity + " " + ptolemy.actor.Manager.timeAndMemory(startTime)); 425 System.out.print("#"); 426 if (entity instanceof TypedAtomicActor) { 427 // Check atomic actors for clone problems 428 try { 429 results.append(checkCloneFields((TypedAtomicActor) entity)); 430 } catch (Throwable throwable) { 431 throw new InternalErrorException((TypedAtomicActor) entity, 432 null, throwable, 433 "The check for " + "clone methods properly setting " 434 + "the fields failed."); 435 } 436 TypedAtomicActor actor = (TypedAtomicActor) entity; 437 String fullName = actor.getName(this); 438 TypedAtomicActor clone = (TypedAtomicActor) cloneConfiguration 439 .getEntity(fullName); 440 if (clone == null) { 441 results.append("Actor " + fullName + " was not cloned!"); 442 } else { 443 444 // First, check type constraints 445 Set<Inequality> constraints = actor.typeConstraints(); 446 Set<Inequality> cloneConstraints = clone.typeConstraints(); 447 448 // Make sure the clone has the same number of constraints. 449 if (constraints.size() != cloneConstraints.size()) { 450 results.append(actor.getFullName() + " has " 451 + constraints.size() + " constraints that " 452 + "differ from " + cloneConstraints.size() 453 + " constraints its clone has.\n" 454 + " Constraints: \nMaster Constraints:\n"); 455 Iterator constraintIterator = constraints.iterator(); 456 while (constraintIterator.hasNext()) { 457 Inequality constraint = (Inequality) constraintIterator 458 .next(); 459 results.append(constraint.toString() + "\n"); 460 } 461 results.append("Clone constraints:\n"); 462 Iterator cloneConstraintIterator = cloneConstraints 463 .iterator(); 464 while (cloneConstraintIterator.hasNext()) { 465 Inequality constraint = (Inequality) cloneConstraintIterator 466 .next(); 467 results.append(constraint.toString() + "\n"); 468 } 469 470 } 471 472 // Make sure the constraints are the same. 473 // First, iterate through the constraints of the master 474 // and create a HashSet of string descriptions. 475 // This is likely to work since the problem is usually 476 // the the clone is missing constraints. 477 HashSet<String> constraintsDescription = new HashSet<String>(); 478 try { 479 Iterator constraintIterator = constraints.iterator(); 480 while (constraintIterator.hasNext()) { 481 Inequality constraint = (Inequality) constraintIterator 482 .next(); 483 constraintsDescription.add(constraint.toString()); 484 } 485 } catch (Throwable throwable) { 486 throw new IllegalActionException(actor, throwable, 487 "Failed to iterate through constraints."); 488 } 489 // Make sure that each constraint in the clone is present 490 // in the master 491 Iterator cloneConstraintIterator = cloneConstraints 492 .iterator(); 493 while (cloneConstraintIterator.hasNext()) { 494 Inequality constraint = (Inequality) cloneConstraintIterator 495 .next(); 496 if (!constraintsDescription 497 .contains(constraint.toString())) { 498 results.append( 499 "Master object of " + actor.getFullName() 500 + " is missing constraint:\n" 501 + constraint.toString() + ".\n"); 502 } 503 } 504 505 // Now do the same for the clone. 506 HashSet<String> cloneConstraintsDescription = new HashSet<String>(); 507 try { 508 Iterator constraintIterator = cloneConstraints 509 .iterator(); 510 while (constraintIterator.hasNext()) { 511 Inequality constraint = (Inequality) constraintIterator 512 .next(); 513 cloneConstraintsDescription 514 .add(constraint.toString()); 515 } 516 } catch (Throwable throwable) { 517 throw new IllegalActionException(actor, throwable, 518 "Failed to iterate through constraints."); 519 } 520 // Make sure that each constraint in the clone is present 521 // in the master 522 Iterator constraintIterator = constraints.iterator(); 523 while (constraintIterator.hasNext()) { 524 Inequality constraint = (Inequality) constraintIterator 525 .next(); 526 if (!cloneConstraintsDescription 527 .contains(constraint.toString())) { 528 results.append("Clone of " + actor.getFullName() 529 + " is missing constraint:\n" 530 + constraint.toString() + ".\n"); 531 } 532 } 533 534 // Check that the type constraint is between ports 535 // and Parameters of the same object. 536 537 cloneConstraintIterator = cloneConstraints.iterator(); 538 while (cloneConstraintIterator.hasNext()) { 539 Inequality constraint = (Inequality) cloneConstraintIterator 540 .next(); 541 InequalityTerm greaterTerm = constraint 542 .getGreaterTerm(); 543 InequalityTerm lesserTerm = constraint.getLesserTerm(); 544 545 Object greaterAssociatedObject = greaterTerm 546 .getAssociatedObject(); 547 Object lesserAssociatedObject = lesserTerm 548 .getAssociatedObject(); 549 if (greaterAssociatedObject instanceof NamedObj 550 && lesserAssociatedObject instanceof NamedObj) { 551 // FIXME: what about non-NamedObjs? 552 NamedObj greaterNamedObj = (NamedObj) greaterAssociatedObject; 553 NamedObj lesserNamedObj = (NamedObj) lesserAssociatedObject; 554 greaterNamedObj.getContainer().getClass().getName(); 555 if (lesserNamedObj != null && greaterNamedObj 556 .getContainer() != lesserNamedObj 557 .getContainer() 558 // actor.lib.qm.CompositeQM was causing false 559 // positives because the contstraints had different 560 // containers, but were contained within the Composite. 561 && greaterNamedObj.getContainer() 562 .getContainer() != lesserNamedObj 563 .getContainer() 564 // PubSubPort that contains an 565 // initialTokens that is used to 566 // set the type. 567 && !(lesserNamedObj 568 .getContainer() instanceof PubSubPort)) { 569 results.append(clone.getFullName() 570 + " has type constraints with " 571 + "associated objects that don't have " 572 + "the same container:\n" 573 + greaterNamedObj.getFullName() 574 + " has a container:\n" 575 + greaterNamedObj.getContainer() + "\n" 576 + lesserNamedObj.getFullName() 577 + " has a container:\n" 578 + lesserNamedObj.getContainer() 579 + "\nThe constraint was: " + constraint 580 + "\n" 581 + "This can occur if the clone(Workspace) " 582 + "method is not present or does not set " 583 + "the constraints like the constructor " 584 + "does or if a Parameter or Port is not " 585 + "declared public.\n"); 586 } 587 } 588 } 589 } 590 } 591 } 592 // Free up space. 593 cloneConfiguration.setContainer(null); 594 return results.toString(); 595 } 596 597 /** Check that clone(Workspace) method properly sets the fields. 598 * In a cloned Director, Attribute or Actor, all 599 * private fields should either point to null or to 600 * distinct objects. 601 * @param namedObj The NamedObj, usually a Director, Attribute 602 * or actor to be checked. 603 * @return A string containing an error message if there is a problem, 604 * otherwise return the empty string. 605 * @exception CloneNotSupportedException If namedObj does not support 606 * clone(Workspace). 607 * @exception IllegalAccessException If there is a problem getting 608 * a field. 609 * @exception ClassNotFoundException If a class cannot be found. 610 */ 611 public static String checkCloneFields(NamedObj namedObj) 612 throws CloneNotSupportedException, IllegalAccessException, 613 IllegalActionException, NameDuplicationException, 614 ClassNotFoundException { 615 NamedObj namedObjClone = (NamedObj) namedObj.clone(new Workspace()); 616 StringBuffer results = new StringBuffer(); 617 Class namedObjClass = namedObj.getClass(); 618 619 // We check only the public, protected and private fields 620 // declared in this class, but not inherited fields. 621 Field[] namedObjFields = namedObjClass.getDeclaredFields(); 622 for (Field field : namedObjFields) { 623 results.append(_checkCloneField(namedObj, namedObjClone, field)); 624 } 625 626 // Get the fields of the parent classes 627 Class clazz = namedObjClass; 628 while (clazz != NamedObj.class && clazz != null) { 629 clazz = clazz.getSuperclass(); 630 namedObjFields = clazz.getDeclaredFields(); 631 for (Field field : namedObjFields) { 632 field.setAccessible(true); 633 results.append( 634 _checkCloneField(namedObj, namedObjClone, field)); 635 } 636 } 637 638 if (namedObjClone instanceof Attribute) { 639 ((Attribute) namedObjClone).setContainer(null); 640 } else if (namedObjClone instanceof ComponentEntity) { 641 ((ComponentEntity) namedObjClone).setContainer(null); 642 } else if (namedObjClone instanceof Port) { 643 ((Port) namedObjClone).setContainer(null); 644 } 645 646 return results.toString(); 647 } 648 649 /** Close all the tableaux. 650 * @exception IllegalActionException If thrown while accessing the Configuration 651 * or while closing the tableaux. 652 */ 653 public static void closeAllTableaux() throws IllegalActionException { 654 // Based on code by Chad Berkley. 655 Configuration configuration = Configuration.configurations().iterator() 656 .next(); 657 // Get the directory from the configuration. 658 ModelDirectory directory = configuration.getDirectory(); 659 if (directory == null) { 660 return; 661 } 662 663 Iterator effigies = directory.entityList(Effigy.class).iterator(); 664 // Go through the directory and close each effigy. 665 while (effigies.hasNext()) { 666 Effigy effigy = (Effigy) effigies.next(); 667 if (!effigy.closeTableaux()) { 668 return; 669 } 670 } 671 } 672 673 /** Return a list of all the configurations that have been created. 674 * Note that if this method is called before a configuration 675 * is created, then it will return an empty linked list. 676 * @return A list of configurations, where each element of the list 677 * is of type Configuration. 678 */ 679 public static List<Configuration> configurations() { 680 return _configurations; 681 } 682 683 /** Create the first tableau for the given effigy, using the 684 * tableau factory. This is called after an effigy is first opened, 685 * or when a new effigy is created. If the method fails 686 * to create a tableau, then it removes the effigy from the directory. 687 * This prevents us from having lingering effigies that have no 688 * user interface. 689 * @return A tableau for the specified effigy, or null if none 690 * can be opened. 691 * @param effigy The effigy for which to create a tableau. 692 */ 693 public Tableau createPrimaryTableau(final Effigy effigy) { 694 // NOTE: It used to be that the body of this method was 695 // actually executed later, in the event thread, so that it can 696 // safely interact with the user interface. 697 // However, this does not appear to be necessary, and it 698 // makes it impossible to return the tableau. 699 // So we no longer do this. 700 // If the object referenced by the effigy contains 701 // an attribute that is an instance of TextEditorTableauFactory, 702 // then use that factory to create the tableau. 703 // Otherwise, use the first factory encountered in the 704 // configuration that agrees to represent this effigy. 705 TableauFactory factory = null; 706 if (effigy instanceof PtolemyEffigy) { 707 NamedObj model = ((PtolemyEffigy) effigy).getModel(); 708 709 if (model != null) { 710 Iterator factories = model.attributeList(TableauFactory.class) 711 .iterator(); 712 713 // If there are more than one of these, use the first 714 // one that agrees to open the model. 715 while (factories.hasNext() && factory == null) { 716 factory = (TableauFactory) factories.next(); 717 718 try { 719 Tableau tableau = factory.createTableau(effigy); 720 721 if (tableau != null) { 722 // The first tableau is a master if the container 723 // of the containing effigy is the model directory. 724 // Used to do this: 725 // if (effigy.getContainer() instanceof ModelDirectory) { 726 if (effigy.masterEffigy() == effigy) { 727 tableau.setMaster(true); 728 } 729 730 tableau.setEditable(effigy.isModifiable()); 731 tableau.show(); 732 return tableau; 733 } 734 } catch (Exception ex) { 735 // Ignore so we keep trying. 736 // NOTE: Uncomment this line to detect bugs when 737 // you try to open a model and you get a text editor. 738 // ex.printStackTrace(); 739 factory = null; 740 } 741 } 742 } 743 } 744 745 // Defer to the configuration. 746 // Create a tableau if there is a tableau factory. 747 factory = (TableauFactory) getAttribute("tableauFactory"); 748 749 if (factory != null) { 750 // If this fails, we do not want the effigy to linger 751 try { 752 Tableau tableau = factory.createTableau(effigy); 753 754 if (tableau == null) { 755 throw new Exception("Tableau factory returns null."); 756 } 757 758 // The first tableau is a master if the container 759 // of the containing effigy is the model directory. 760 if (effigy.getContainer() instanceof ModelDirectory) { 761 tableau.setMaster(true); 762 } 763 764 tableau.setEditable(effigy.isModifiable()); 765 tableau.show(); 766 return tableau; 767 } catch (Exception ex) { 768 // NOTE: Uncomment this line to detect bugs when 769 // you try to open a model and you get a text editor. 770 // ex.printStackTrace(); 771 // Remove the effigy. We were unable to open a tableau for it. 772 773 // If we have a link to a missing .htm file, we want to 774 // avoid popping up two MessageHandlers. 775 boolean calledMessageHandler = false; 776 try { 777 if (effigy == null) { 778 // Coverity Scan 1352685 warned that effigy can be null. 779 throw new InternalErrorException( 780 "createPrimaryTableau() " 781 + "called with a null Effigy?"); 782 } else if (effigy 783 .getContainer() instanceof ModelDirectory) { 784 // This is the master. 785 // Calling setContainer() = null will exit, so 786 // we display the error message here. 787 // 788 // We will get to here if 789 // diva.graph.AbstractGraphController.rerender() 790 // throws an NullPointerException when starting 791 // vergil. 792 if (effigy instanceof PtolemyEffigy) { 793 if (((PtolemyEffigy) effigy).getModel() != null) { 794 MessageHandler.error("Failed to open " 795 + ((PtolemyEffigy) effigy).getModel() 796 .getFullName(), 797 ex); 798 } else { 799 MessageHandler.error("Failed to open " + effigy, 800 ex); 801 } 802 calledMessageHandler = true; 803 } else { 804 // Opening a link to a non-existent .htm file 805 // might get us to here because the effigy is 806 // not a PtolemyEffigy. 807 // 808 // Note that because we call MessageHandler here, 809 // this means that putting a try/catch around 810 // configuration.openModel() does not do much good 811 // if a .htm file is not found because the 812 // MessageHandler pops up before we return 813 // from the exception. 814 // In addition, we cannot catch HeadlessExceptions. 815 816 MessageHandler.error( 817 "Failed to open " 818 + effigy.identifier.getExpression(), 819 ex); 820 calledMessageHandler = true; 821 } 822 } 823 824 // This eventually calls Configuration._removeEntity() 825 // which calls System.exit(); 826 effigy.setContainer(null); 827 } catch (Throwable throwable) { 828 calledMessageHandler = false; 829 throw new InternalErrorException(this, throwable, null); 830 } 831 832 // As a last resort, attempt to open source code 833 // associated with the object. 834 if (effigy instanceof PtolemyEffigy) { 835 NamedObj object = ((PtolemyEffigy) effigy).getModel(); 836 837 // Source code is found by name. 838 String filename = StringUtilities 839 .objectToSourceFileName(object); 840 841 try { 842 URL toRead = getClass().getClassLoader() 843 .getResource(filename); 844 845 // If filename was not found in the classpath, then search 846 // each element in the classpath for a directory named 847 // src and then search for filename. This is needed 848 // by Eclipse for Kepler. See also TextEffigy.newTextEffigy() 849 if (toRead == null) { 850 toRead = ClassUtilities.sourceResource(filename); 851 // System.out.println("Configuration: sourceResource " 852 // + filename + " " + toRead); 853 } 854 855 if (toRead != null) { 856 return openModel(null, toRead, 857 toRead.toExternalForm()); 858 } else { 859 MessageHandler.error( 860 "Cannot find a tableau or the source code for " 861 + object.getFullName()); 862 } 863 } catch (Exception exception) { 864 MessageHandler 865 .error("Failed to open the source code for " 866 + object.getFullName(), exception); 867 } 868 } 869 870 // Note that we can't rethrow the exception here 871 // because removing the effigy may result in 872 // the application exiting. 873 if (!calledMessageHandler) { 874 MessageHandler.error("Failed to open tableau for " 875 + effigy.identifier.getExpression(), ex); 876 } 877 } 878 } 879 return null; 880 } 881 882 /** Find an effigy for the specified model by searching all the 883 * configurations that have been created. Although typically there is 884 * only one, in principle there may be more than one. This can be used 885 * to find a configuration, which is typically the result of calling 886 * toplevel() on the effigy. 887 * @param model The model for which to find an effigy. 888 * @return An effigy, or null if none can be found. 889 */ 890 public static Effigy findEffigy(NamedObj model) { 891 for (Configuration configuration : _configurations) { 892 Effigy effigy = configuration.getEffigy(model); 893 894 if (effigy != null) { 895 return effigy; 896 } 897 } 898 899 return null; 900 } 901 902 /** Instantiate the class named by a StringParameter in the configuration. 903 * @param parameterName The name of the StringParameter in the configuration. 904 * @param constructorParameterTypes An array of parameter types, null if there 905 * are no parameters 906 * @param constructorParameterClass An array of objects to pass to the constructor. 907 * @return an instance of the class named by parameterName, or 908 * null if the configuration does not contain a parameter by that 909 * name. 910 * @exception ClassNotFoundException If the class named by 911 * parameterName cannot be found. 912 * @exception IllegalAccessException If the constructor is not accessible. 913 * @exception IllegalActionException If thrown while reading the 914 * parameter named by parameterName. 915 * @exception InstantiationException If the object cannot be instantiated 916 * @exception java.lang.reflect.InvocationTargetException If 917 * there is problem invoking the constructor. 918 * @exception NoSuchMethodException If there is no constructor with the type. 919 */ 920 public Object getStringParameterAsClass(String parameterName, 921 Class[] constructorParameterTypes, 922 Object[] constructorParameterClass) 923 throws ClassNotFoundException, IllegalAccessException, 924 IllegalActionException, InstantiationException, 925 java.lang.reflect.InvocationTargetException, NoSuchMethodException { 926 // Deal with the PDF Action first. 927 StringParameter classNameParameter = (StringParameter) getAttribute( 928 parameterName, StringParameter.class); 929 930 if (classNameParameter != null) { 931 String className = classNameParameter.stringValue(); 932 Class clazz = Class.forName(className); 933 Constructor constructor = clazz 934 .getDeclaredConstructor(constructorParameterTypes); 935 return constructor.newInstance(constructorParameterClass); 936 } 937 return null; 938 } 939 940 /** Get the model directory. 941 * @return The model directory, or null if there isn't one. 942 */ 943 public ModelDirectory getDirectory() { 944 Entity directory = getEntity(_DIRECTORY_NAME); 945 946 if (directory instanceof ModelDirectory) { 947 return (ModelDirectory) directory; 948 } 949 950 return null; 951 } 952 953 /** Get the effigy for the specified Ptolemy model. 954 * This searches all instances of PtolemyEffigy deeply contained by 955 * the directory, and returns the first one it encounters 956 * that is an effigy for the specified model. 957 * @param model The Ptolemy model. 958 * @return The effigy for the model, or null if none is found. 959 */ 960 public PtolemyEffigy getEffigy(NamedObj model) { 961 Entity directory = getEntity(_DIRECTORY_NAME); 962 963 if (directory instanceof ModelDirectory) { 964 return _findEffigyForModel((ModelDirectory) directory, model); 965 } else { 966 return null; 967 } 968 } 969 970 /** Open the specified instance. 971 * A derived class looks for the instance, and if the instance already has 972 * open tableaux, then put those in the foreground 973 * Otherwise, create a new tableau and if 974 * necessary, a new effigy. Unless there is a more natural container 975 * for the effigy (e.g. it is a hierarchical model), then if a new 976 * effigy is created, it is put into the directory of the configuration. 977 * Any new tableau created will be contained by that effigy. 978 * 979 * @param entity The entity to open. 980 * @exception IllegalActionException If constructing an effigy or tableau 981 * fails. 982 * @exception NameDuplicationException If a name conflict occurs (this 983 * should not be thrown). 984 */ 985 @Override 986 public void openAnInstance(NamedObj entity) 987 throws IllegalActionException, NameDuplicationException { 988 // This could be called openInstance(), but we don't want to 989 // have a dependency to Tableau in ModalController and ModalRefinement. 990 991 // Note that we ignore the return value here. 992 openInstance(entity); 993 } 994 995 /** Open the specified instance. If the instance already has 996 * open tableaux, then put those in the foreground and 997 * return the first one. Otherwise, create a new tableau and if 998 * necessary, a new effigy. Unless there is a more natural container 999 * for the effigy (e.g. it is a hierarchical model), then if a new 1000 * effigy is created, it is put into the directory of the configuration. 1001 * Any new tableau created will be contained by that effigy. 1002 * @param entity The entity to open. 1003 * @return The tableau that is created, or the first one found, 1004 * or null if none is created or found. 1005 * @exception IllegalActionException If constructing an effigy or tableau 1006 * fails. 1007 * @exception NameDuplicationException If a name conflict occurs (this 1008 * should not be thrown). 1009 */ 1010 public Tableau openInstance(NamedObj entity) 1011 throws IllegalActionException, NameDuplicationException { 1012 return openInstance(entity, null); 1013 } 1014 1015 /** Open the specified instance. If the instance already has 1016 * open tableaux, then put those in the foreground and 1017 * return the first one. Otherwise, create a new tableau and, 1018 * if necessary, a new effigy. Unless there is a more natural 1019 * place for the effigy (e.g. it is a hierarchical model), then if a new 1020 * effigy is created, it is put into the <i>container</i> argument, 1021 * or if that is null, into the directory of the configuration. 1022 * Any new tableau created will be contained by that effigy. 1023 * @param entity The model. 1024 * @param container The container for any new effigy. 1025 * @return The tableau that is created, or the first one found, 1026 * or null if none is created or found. 1027 * @exception IllegalActionException If constructing an effigy or tableau 1028 * fails. 1029 * @exception NameDuplicationException If a name conflict occurs (this 1030 * should not be thrown). 1031 */ 1032 public Tableau openInstance(NamedObj entity, CompositeEntity container) 1033 throws IllegalActionException, NameDuplicationException { 1034 return _openModel(entity, container); 1035 } 1036 1037 /** Open the specified URL. 1038 * If a model with the specified identifier is present in the directory, 1039 * then find all the tableaux of that model and make them 1040 * visible; otherwise, read a model from the specified URL <i>in</i> 1041 * and create a default tableau for the model and add the tableau 1042 * to this directory. 1043 * @param base The base for relative file references, or null if 1044 * there are no relative file references. 1045 * @param in The input URL. 1046 * @param identifier The identifier that uniquely identifies the model. 1047 * @return The tableau that is created, or null if none. 1048 * @exception Exception If the URL cannot be read. 1049 */ 1050 public Tableau openModel(URL base, URL in, String identifier) 1051 throws Exception { 1052 return openModel(base, in, identifier, null); 1053 } 1054 1055 /** Open the specified URL using the specified effigy factory. 1056 * If a model with the specified identifier is present in the directory, 1057 * then find all the tableaux of that model and make them 1058 * visible; otherwise, read a model from the specified URL <i>in</i> 1059 * and create a default tableau for the model and add the tableau 1060 * to this directory. 1061 * @param base The base for relative file references, or null if 1062 * there are no relative file references. 1063 * @param in The input URL. 1064 * @param identifier The identifier that uniquely identifies the model. 1065 * @param factory The effigy factory to use. 1066 * @return The tableau that is created, or null if none. 1067 * @exception Exception If the URL cannot be read. 1068 */ 1069 public Tableau openModel(URL base, URL in, String identifier, 1070 EffigyFactory factory) throws Exception { 1071 ModelDirectory directory = (ModelDirectory) getEntity(_DIRECTORY_NAME); 1072 1073 if (directory == null) { 1074 throw new InternalErrorException("No model directory!"); 1075 } 1076 1077 // Check to see whether the model is already open. 1078 Effigy effigy = directory.getEffigy(identifier); 1079 1080 if (effigy == null) { 1081 // No previous effigy exists that is identified by this URL. 1082 // Find an effigy factory to read it. 1083 if (factory == null) { 1084 // Check to see whether the URL includes the special 1085 // target "#in_browser", and if so, use a BrowserEffigy factory. 1086 // NOTE: This used to be handled only by HTMLViewer, but 1087 // this limited where the #in_browser notation could be used. 1088 // The following is adapted from HTMLViewer. 1089 1090 // NOTE: It would be nice to use target="_browser" or some 1091 // such, but this doesn't work. Targets aren't 1092 // seen unless the link is inside a frame, 1093 // regrettably. An alternative might be to 1094 // use the "userInfo" part of the URL, 1095 // defined at http://www.ncsa.uiuc.edu/demoweb/url-primer.html 1096 boolean useBrowser = false; 1097 String ref = in.getRef(); 1098 if (ref != null) { 1099 useBrowser = ref.equals("in_browser"); 1100 } 1101 String protocol = in.getProtocol(); 1102 if (protocol != null) { 1103 // Suggested mailto: extension from Paul Lieverse. 1104 // Unfortunately, it doesn't work for me on a Mac. 1105 // Leaving it here in case it works for someone else. 1106 useBrowser |= protocol.equals("mailto"); 1107 } 1108 if (useBrowser) { 1109 if (BrowserEffigy.staticFactory == null) { 1110 // The following will set BrowserEffigy.staticFactory. 1111 new BrowserEffigy.Factory(this, "browserEffigyFactory"); 1112 } 1113 factory = BrowserEffigy.staticFactory; 1114 } else { 1115 // Not a browser or mailto URL. 1116 // Get an effigy factory from this configuration. 1117 factory = (EffigyFactory) getEntity("effigyFactory"); 1118 } 1119 } 1120 1121 if (factory == null) { 1122 throw new InternalErrorException( 1123 "No effigy factories in the configuration!"); 1124 } 1125 1126 effigy = factory.createEffigy(directory, base, in); 1127 1128 if (effigy == null) { 1129 MessageHandler.error( 1130 "Unsupported file type or connection not available: " 1131 + in.toExternalForm()); 1132 return null; 1133 } 1134 1135 if (effigy.identifier.getExpression().compareTo("Unnamed") == 0) { 1136 // If the value identifier field of the effigy we just 1137 // created is "Unnamed", then set it to the value of 1138 // the identifier parameter. 1139 // 1140 // HSIFEffigyFactory sets effiigy.identifier because it 1141 // converts the file we specified from HSIF to MoML and then 1142 // opens up a file other than the one we specified. 1143 effigy.identifier.setExpression(identifier); 1144 } 1145 1146 // Check the URL to see whether it is a file, 1147 // and if so, whether it is writable. 1148 if (in != null && in.getProtocol().equals("file")) { 1149 String filename = in.getFile(); 1150 File file = new File(filename); 1151 1152 try { 1153 if (!file.canWrite()) { 1154 // FIXME: we need a better way to check if 1155 // a URL is writable. 1156 1157 // Sigh. If the filename has spaces in it, 1158 // then the URL will have %20s. However, 1159 // the file does not have %20s. 1160 // See 1161 // https://chess.eecs.berkeley.edu/bugzilla/show_bug.cgi?id=153 1162 filename = StringUtilities.substitute(filename, "%20", 1163 " "); 1164 file = new File(filename); 1165 if (!file.canWrite()) { 1166 effigy.setModifiable(false); 1167 } 1168 } 1169 } catch (java.security.AccessControlException accessControl) { 1170 // If we are running in a sandbox, then canWrite() 1171 // may throw an AccessControlException. 1172 effigy.setModifiable(false); 1173 } 1174 } else { 1175 effigy.setModifiable(false); 1176 } 1177 1178 return createPrimaryTableau(effigy); 1179 } else { 1180 // Model already exists. 1181 return effigy.showTableaux(); 1182 } 1183 } 1184 1185 /** Open the specified Ptolemy II model. If a model already has 1186 * open tableaux, then put those in the foreground and 1187 * return the first one. Otherwise, create a new tableau and if 1188 * necessary, a new effigy. Unless there is a more natural container 1189 * for the effigy (e.g. it is a hierarchical model), then if a new 1190 * effigy is created, it is put into the directory of the configuration. 1191 * Any new tableau created will be contained by that effigy. 1192 * @param entity The model. 1193 * @return The tableau that is created, or the first one found, 1194 * or null if none is created or found. 1195 * @exception IllegalActionException If constructing an effigy or tableau 1196 * fails. 1197 * @exception NameDuplicationException If a name conflict occurs (this 1198 * should not be thrown). 1199 */ 1200 public Tableau openModel(NamedObj entity) 1201 throws IllegalActionException, NameDuplicationException { 1202 NamedObj container = entity.getContainer(); 1203 Effigy containerEffigy = getEffigy(container); 1204 return openModel(entity, containerEffigy); 1205 } 1206 1207 /** Open the specified Ptolemy II model. If a model already has 1208 * open tableaux, then put those in the foreground and 1209 * return the first one. Otherwise, create a new tableau and, 1210 * if necessary, a new effigy. Unless there is a more natural 1211 * place for the effigy (e.g. it is a hierarchical model), then if a new 1212 * effigy is created, it is put into the <i>container</i> argument, 1213 * or if that is null, into the directory of the configuration. 1214 * Any new tableau created will be contained by that effigy. 1215 * @param entity The model. 1216 * @param container The container for any new effigy. 1217 * @return The tableau that is created, or the first one found, 1218 * or null if none is created or found. 1219 * @exception IllegalActionException If constructing an effigy or tableau 1220 * fails. 1221 * @exception NameDuplicationException If a name conflict occurs (this 1222 * should not be thrown). 1223 */ 1224 public Tableau openModel(NamedObj entity, CompositeEntity container) 1225 throws IllegalActionException, NameDuplicationException { 1226 // If the entity defers its MoML definition to another, 1227 // then open that other, unless this is a class extending another, 1228 // and also unless this is an object that contains a TableauFactory. 1229 // I.e., by default, when you open an instance of a class, what 1230 // is opened is the class definition, not the instance, unless 1231 // the instance contains a TableauFactory, in which case, we defer 1232 // to that TableauFactory. 1233 InstantiableNamedObj deferredTo = null; 1234 boolean isClass = false; 1235 1236 if (entity instanceof InstantiableNamedObj) { 1237 deferredTo = (InstantiableNamedObj) ((InstantiableNamedObj) entity) 1238 .getParent(); 1239 isClass = ((InstantiableNamedObj) entity).isClassDefinition(); 1240 } 1241 1242 if (deferredTo != null && !isClass) { 1243 entity = deferredTo; 1244 } 1245 1246 return _openModel(entity, container); 1247 } 1248 1249 /** If the argument is not null, then throw an exception. 1250 * This ensures that the object is always at the top level of 1251 * a hierarchy. 1252 * @param container The proposed container. If the proposed 1253 * container is null, then super.setContainer(null) is invoked, 1254 * which may free up memory. 1255 * @exception IllegalActionException If the argument is not null. 1256 * @exception NameDuplicationException If thrown by a parent class. 1257 */ 1258 @Override 1259 public void setContainer(CompositeEntity container) 1260 throws IllegalActionException, NameDuplicationException { 1261 if (container != null) { 1262 throw new IllegalActionException(this, 1263 "Configuration can only be at the top level " 1264 + "of a hierarchy."); 1265 } 1266 super.setContainer(null); 1267 } 1268 1269 /** Find all instances of Tableau deeply contained in the directory 1270 * and call show() on them. If there is no directory, then do nothing. 1271 */ 1272 public void showAll() { 1273 final ModelDirectory directory = (ModelDirectory) getEntity( 1274 _DIRECTORY_NAME); 1275 1276 if (directory == null) { 1277 return; 1278 } 1279 1280 _showTableaux(directory); 1281 } 1282 1283 /////////////////////////////////////////////////////////////////// 1284 //// public variables //// 1285 1286 /** The name of the model directory. */ 1287 static public final String _DIRECTORY_NAME = "directory"; 1288 1289 /////////////////////////////////////////////////////////////////// 1290 //// package protected methods //// 1291 1292 /** Remove the configuration from the list of configurations. 1293 * @param configuration The configuration to be removed. 1294 */ 1295 void removeConfiguration(Configuration configuration) { 1296 _configurations.remove(configuration); 1297 } 1298 1299 /////////////////////////////////////////////////////////////////// 1300 //// protected methods //// 1301 1302 /** Remove the specified entity; if that entity is the model directory, 1303 * then exit the application. This method should not be called 1304 * directly. Call the setContainer() method of the entity instead with 1305 * a null argument. 1306 * The entity is assumed to be contained by this composite (otherwise, 1307 * nothing happens). This does not alter the entity in any way. 1308 * This method is <i>not</i> synchronized on the workspace, so the 1309 * caller should be. 1310 * @param entity The entity to remove. 1311 */ 1312 @Override 1313 protected void _removeEntity(ComponentEntity entity) { 1314 super._removeEntity(entity); 1315 if (entity.getName().equals(_DIRECTORY_NAME)) { 1316 // If the ptolemy.ptII.doNotExit property or the 1317 // ptolemy.ptII.exitAfterWrapup property is set 1318 // then we don't actually call System.exit(). 1319 // 1320 StringUtilities.exit(0); 1321 } 1322 } 1323 1324 /////////////////////////////////////////////////////////////////// 1325 //// private methods //// 1326 1327 /** Check that clone(Workspace) method properly sets the fields. 1328 * In a cloned Director, Attribute or Actor, all 1329 * private fields should either point to null or to 1330 * distinct objects. 1331 * @param namedObj The NamedObj, usually a Director, Attribute 1332 * or actor to be checked. 1333 * @param namedObjClone the clone of the namedObj, created with 1334 * clone(new Workspace()) 1335 * @param field The field to be checked. 1336 * @return A string containing an error message if there is a problem, 1337 * otherwise return the empty string. 1338 * @exception CloneNotSupportedException If namedObj does not support 1339 * clone(Workspace). 1340 * @exception IllegalAccessException If there is a problem getting 1341 * a field. 1342 * @exception ClassNotFoundException If a class cannot be found. 1343 */ 1344 private static String _checkCloneField(NamedObj namedObj, 1345 NamedObj namedObjClone, Field field) 1346 throws CloneNotSupportedException, IllegalAccessException, 1347 ClassNotFoundException { 1348 Class namedObjClass = namedObj.getClass(); 1349 StringBuffer results = new StringBuffer(); 1350 // Tell the security manager we want to read private fields. 1351 // This will fail in an applet. 1352 field.setAccessible(true); 1353 Class fieldType = field.getType(); 1354 if (!fieldType.isPrimitive() && field.get(namedObj) != null 1355 && !Modifier.isStatic(field.getModifiers()) 1356 && !Modifier.isStatic(fieldType.getModifiers()) //matlab.Engine.ConversionParameters. 1357 /*&& !fieldType.isArray()*/ 1358 // Skip fields introduced by javascope 1359 && !fieldType.toString().equals( 1360 "COM.sun.suntest.javascope.database.CoverageUnit") 1361 && !field.getName().equals("js$p") 1362 // Skip fields introduced by backtracking 1363 && !(field.getName().indexOf("$RECORD$") != -1) 1364 && !(field.getName().indexOf("$RECORDS") != -1) 1365 && !(field.getName().indexOf("$CHECKPOINT") != -1) 1366 // Skip dependency injection fields 1367 && !(field.getName().indexOf("_implementation") != -1) 1368 // Skip immutables 1369 && !fieldType.equals(java.net.InetAddress.class) 1370 && !fieldType.equals(java.util.regex.Pattern.class) 1371 // SharedParameter has a _containerClass field 1372 && !fieldType.equals(Boolean.class) 1373 && !fieldType.equals(Class.class) 1374 && !fieldType.equals(String.class) 1375 && !fieldType.equals(Token.class) 1376 // Variable has various type fields 1377 && !fieldType.equals(ptolemy.data.type.Type.class) 1378 && !fieldType.equals(Settable.Visibility.class)) { 1379 1380 // If an object is equal and the default hashCode() from 1381 // Object is the same, then we have a problem. 1382 if (field.get(namedObj).equals(field.get(namedObjClone)) 1383 && System.identityHashCode(field.get(namedObj)) == System 1384 .identityHashCode(field.get(namedObjClone))) { 1385 1386 String message = ""; 1387 if (Class.forName("ptolemy.kernel.util.NamedObj") 1388 .isAssignableFrom(fieldType)) { 1389 NamedObj fieldNamedObj = (NamedObj) Class 1390 .forName("ptolemy.kernel.util.NamedObj") 1391 .cast(field.get(namedObj)); 1392 NamedObj cloneNamedObj = (NamedObj) Class 1393 .forName("ptolemy.kernel.util.NamedObj") 1394 .cast(field.get(namedObjClone)); 1395 message = "Field: " + fieldNamedObj.workspace().getName() 1396 + " Clone: " + cloneNamedObj.workspace().getName(); 1397 } 1398 1399 // Determine what code should go in clone(W) 1400 String assignment = field.getName(); 1401 // FIXME: extend this to more types 1402 if (Class.forName("ptolemy.kernel.Port") 1403 .isAssignableFrom(fieldType)) { 1404 assignment = ".getPort(\"" + assignment + "\")"; 1405 // } else if (fieldType.isInstance( new Attribute())) { 1406 } else if (Class.forName("ptolemy.kernel.util.Attribute") 1407 .isAssignableFrom(fieldType)) { 1408 Attribute fieldAttribute = (Attribute) field 1409 .get(namedObjClone); 1410 1411 if (fieldAttribute.getContainer() != namedObjClone) { 1412 // If the attribute is actually contained by a Port 1413 // and not by the AtomicActor, then get its value. 1414 // SDF actors that have ports that have 1415 // tokenConsumptionRate and tokenProductionRate 1416 // such as ConvolutionalCoder need this. 1417 assignment = "." 1418 + fieldAttribute.getContainer().getName() 1419 + ".getAttribute(\"" + fieldAttribute.getName() 1420 + "\")"; 1421 } else { 1422 assignment = ".getAttribute(\"" + assignment + "\")"; 1423 } 1424 } else { 1425 assignment = "\n\t/* Get the object method " 1426 + "or null? */ " + assignment; 1427 } 1428 1429 String shortClassName = field.getType().getName().substring( 1430 field.getType().getName().lastIndexOf(".") + 1); 1431 1432 //new Exception("Configuration._checkCloneField()").printStackTrace(); 1433 1434 results.append("The " + field.getName() + " " 1435 + field.getType().getName() + " field" 1436 + "\n\tin the clone of \"" + namedObjClass.getName() 1437 + "\"\n\tdoes not point to an " 1438 + "object distinct from the " 1439 + "master. \n\tThis may cause problems with " 1440 + "actor oriented classes." 1441 + "\n\tThe clone(Workspace) " 1442 + "method should have a line " + "like:\n newObject." 1443 + field.getName() + " = (" + shortClassName 1444 + ")newObject" + assignment + ";\n" + message); 1445 } 1446 1447 } 1448 return results.toString(); 1449 1450 } 1451 1452 /** Return an identifier for the specified effigy based on its 1453 * container (if any) and its name. 1454 * @return An identifier for the effigy. 1455 */ 1456 private String _effigyIdentifier(Effigy effigy, NamedObj entity) { 1457 // Set the identifier of the effigy to be that 1458 // of the parent with the model name appended. 1459 NamedObj parent = effigy.getContainer(); 1460 1461 if (!(parent instanceof Effigy)) { 1462 return effigy.getFullName(); 1463 } 1464 1465 Effigy parentEffigy = (Effigy) parent; 1466 1467 // Note that we add a # the first time, and 1468 // then add . after that. So 1469 // file:/c:/foo.xml#bar.bif is ok, but 1470 // file:/c:/foo.xml#bar#bif is not 1471 // If the title does not contain a legitimate 1472 // way to reference the submodel, then the user 1473 // is likely to look at the title and use the wrong 1474 // value if they xml edit files by hand. (cxh-4/02) 1475 String entityName = parentEffigy.identifier.getExpression(); 1476 String separator = "#"; 1477 1478 if (entityName.indexOf("#") >= 0) { 1479 separator = "."; 1480 } 1481 1482 return entityName + separator + entity.getName(); 1483 } 1484 1485 // Recursively search the specified composite for an instance of 1486 // PtolemyEffigy that matches the specified model. 1487 private PtolemyEffigy _findEffigyForModel(CompositeEntity composite, 1488 NamedObj model) { 1489 if (composite != null) { 1490 Iterator effigies = composite.entityList(PtolemyEffigy.class) 1491 .iterator(); 1492 1493 while (effigies.hasNext()) { 1494 PtolemyEffigy effigy = (PtolemyEffigy) effigies.next(); 1495 1496 // First see whether this effigy matches. 1497 if (effigy.getModel() == model) { 1498 return effigy; 1499 } 1500 1501 // Then see whether any effigy inside this one matches. 1502 PtolemyEffigy inside = _findEffigyForModel(effigy, model); 1503 1504 if (inside != null) { 1505 return inside; 1506 } 1507 } 1508 } 1509 1510 return null; 1511 } 1512 1513 /** Open the specified model without deferring to its class definition. 1514 * @param entity The model to open. 1515 * @param container The container for any new effigy. 1516 * @return The tableau that is created, or the first one found, 1517 * or null if none is created or found. 1518 * @exception IllegalActionException If constructing an effigy or tableau 1519 * fails. 1520 * @exception NameDuplicationException If a name conflict occurs (this 1521 * should not be thrown). 1522 */ 1523 private Tableau _openModel(NamedObj entity, CompositeEntity container) 1524 throws IllegalActionException, NameDuplicationException { 1525 if (entity == null) { 1526 throw new IllegalActionException("Nothing to open."); 1527 } 1528 // Search the model directory for an effigy that already 1529 // refers to this model. 1530 PtolemyEffigy effigy = getEffigy(entity); 1531 1532 if (effigy != null) { 1533 // Found one. Display all open tableaux. 1534 return effigy.showTableaux(); 1535 } else { 1536 // There is no pre-existing effigy. Create one. 1537 effigy = new PtolemyEffigy(workspace()); 1538 effigy.setModel(entity); 1539 1540 // Look to see whether the model has a URIAttribute. 1541 List attributes = entity.attributeList(URIAttribute.class); 1542 1543 if (attributes.size() > 0) { 1544 // The entity has a URI, which was probably 1545 // inserted by MoMLParser. 1546 URI uri = ((URIAttribute) attributes.get(0)).getURI(); 1547 1548 // Set the URI and identifier of the effigy. 1549 effigy.uri.setURI(uri); 1550 1551 // NOTE: The uri might be null, which results in 1552 // a null pointer exception below. In particular, 1553 // the class Effigy always has a URI attribute, but 1554 // the value might not get set. 1555 if (uri == null) { 1556 effigy.identifier 1557 .setExpression(_effigyIdentifier(effigy, entity)); 1558 } else { 1559 effigy.identifier.setExpression(uri.toString()); 1560 } 1561 1562 if (container == null) { 1563 // Put the effigy into the directory 1564 ModelDirectory directory = getDirectory(); 1565 if (directory == null) { 1566 throw new NullPointerException("While trying to open " 1567 + entity + " in " + container 1568 + ", getDirectory() returned null? " 1569 + "Perhaps an entity of name \"" 1570 + _DIRECTORY_NAME + "\" was not created?"); 1571 } 1572 effigy.setName(directory.uniqueName(entity.getName())); 1573 effigy.setContainer(directory); 1574 } else { 1575 effigy.setName(container.uniqueName(entity.getName())); 1576 effigy.setContainer(container); 1577 } 1578 1579 // Create a default tableau. 1580 return createPrimaryTableau(effigy); 1581 } else { 1582 // If we get here, then we are looking inside a model 1583 // that is defined within the same file as the parent, 1584 // probably. Create a new PtolemyEffigy 1585 // and open a tableau for it. 1586 // Put the effigy inside the effigy of the parent, 1587 // rather than directly into the directory. 1588 NamedObj parent = entity.getContainer(); 1589 PtolemyEffigy parentEffigy = null; 1590 1591 // Find the first container above in the hierarchy that 1592 // has an effigy. 1593 while (parent != null && parentEffigy == null) { 1594 parentEffigy = getEffigy(parent); 1595 parent = parent.getContainer(); 1596 } 1597 1598 boolean isContainerSet = false; 1599 1600 if (parentEffigy != null) { 1601 // OK, we can put it into this other effigy. 1602 effigy.setName(parentEffigy.uniqueName(entity.getName())); 1603 effigy.setContainer(parentEffigy); 1604 1605 // Set the uri of the effigy to that of 1606 // the parent. 1607 effigy.uri.setURI(parentEffigy.uri.getURI()); 1608 1609 // Indicate success. 1610 isContainerSet = true; 1611 } 1612 1613 // If the above code did not find an effigy to put 1614 // the new effigy within, then put it into the 1615 // directory directly or the specified container. 1616 if (!isContainerSet) { 1617 if (container == null) { 1618 CompositeEntity directory = getDirectory(); 1619 effigy.setName(directory.uniqueName(entity.getName())); 1620 effigy.setContainer(directory); 1621 } else { 1622 effigy.setName(container.uniqueName(entity.getName())); 1623 effigy.setContainer(container); 1624 } 1625 } 1626 1627 effigy.identifier 1628 .setExpression(_effigyIdentifier(effigy, entity)); 1629 1630 return createPrimaryTableau(effigy); 1631 } 1632 } 1633 } 1634 1635 // Call show() on all instances of Tableaux contained by the specified 1636 // container. 1637 private void _showTableaux(CompositeEntity container) { 1638 Iterator entities = container.entityList().iterator(); 1639 1640 while (entities.hasNext()) { 1641 Object entity = entities.next(); 1642 1643 if (entity instanceof Tableau) { 1644 ((Tableau) entity).show(); 1645 } else if (entity instanceof CompositeEntity) { 1646 _showTableaux((CompositeEntity) entity); 1647 } 1648 } 1649 } 1650 1651 /////////////////////////////////////////////////////////////////// 1652 //// private variables //// 1653 1654 /** The list of configurations that have been created. */ 1655 private static LinkedList<Configuration> _configurations = new LinkedList<Configuration>(); 1656}