001/* Check that an assertion predicate is satisfied, and throw an exception if not. 002 003 Copyright (c) 2013-2014 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 027 */ 028package ptolemy.actor.lib; 029 030import java.io.Writer; 031import java.util.HashMap; 032import java.util.List; 033 034import ptolemy.actor.TypedIOPort; 035import ptolemy.data.BooleanToken; 036import ptolemy.data.expr.SingletonParameter; 037import ptolemy.data.expr.StringParameter; 038import ptolemy.data.type.BaseType; 039import ptolemy.kernel.CompositeEntity; 040import ptolemy.kernel.Port; 041import ptolemy.kernel.util.IllegalActionException; 042import ptolemy.kernel.util.InternalErrorException; 043import ptolemy.kernel.util.KernelException; 044import ptolemy.kernel.util.NameDuplicationException; 045import ptolemy.kernel.util.Workspace; 046 047/** Check that an assertion predicate is satisfied, and throw an exception if not. 048 * To use this actor, add any number of input ports. 049 * Corresponding output ports will be automatically added. 050 * Specify an expression that references the inputs and yields a boolean result. 051 * When the actor fires, if the expression evaluates to false, then the actor 052 * will throw an exception with the message given by the {@link #message} parameter. 053 * Otherwise, it will copy the inputs to the corresponding output ports. 054 * 055 * @author Ilge Akkaya, David Broman, Edward A. Lee 056 * @version $Id$ 057 * @since Ptolemy II 10.0 058 * @Pt.ProposedRating Yellow (eal) 059 * @Pt.AcceptedRating Red (eal) 060 */ 061public class Assert extends Expression { 062 063 /** Construct an instance of Assert. 064 * @param container The container. 065 * @param name The name of this actor. 066 * @exception IllegalActionException If the actor cannot be contained 067 * by the proposed container. 068 * @exception NameDuplicationException If the container already has an 069 * actor with this name. 070 */ 071 public Assert(CompositeEntity container, String name) 072 throws NameDuplicationException, IllegalActionException { 073 super(container, name); 074 075 // Hide the output port. 076 SingletonParameter showName = new SingletonParameter(output, "_hide"); 077 showName.setPersistent(false); 078 showName.setExpression("true"); 079 080 // Set the type of the output port to ensure that the expression is predicate. 081 output.setTypeEquals(BaseType.BOOLEAN); 082 083 message = new StringParameter(this, "message"); 084 message.setExpression("Assertion failed."); 085 } 086 087 /////////////////////////////////////////////////////////////////// 088 //// ports and parameters //// 089 090 /** The error message to display when the assertion is violated. 091 * This is a string that defaults to "Assertion failed.". 092 */ 093 public StringParameter message; 094 095 /////////////////////////////////////////////////////////////////// 096 //// public methods //// 097 098 /** Clone the actor into the specified workspace. 099 * @param workspace The workspace for the new object. 100 * @return A new actor. 101 * @exception CloneNotSupportedException If a derived class contains 102 * an attribute that cannot be cloned. 103 */ 104 @Override 105 public Object clone(Workspace workspace) throws CloneNotSupportedException { 106 Assert newObject = null; 107 // NOTE: The following flag will be copied into the clone. 108 _cloning = true; 109 try { 110 newObject = (Assert) super.clone(workspace); 111 newObject._outputPortMap = new HashMap<String, TypedIOPort>(); 112 // Reconstruct the output port map. 113 List<TypedIOPort> inputs = newObject.inputPortList(); 114 for (TypedIOPort input : inputs) { 115 String name = input.getName(); 116 String outputPortName = _OUTPUT_PORT_PREFIX + name; 117 TypedIOPort output = (TypedIOPort) newObject 118 .getPort(outputPortName); 119 newObject._outputPortMap.put(name, output); 120 } 121 } finally { 122 _cloning = false; 123 if (newObject == null) { 124 throw new CloneNotSupportedException( 125 "super.clone(Workspace) returned null?"); 126 } else { 127 newObject._cloning = false; 128 } 129 } 130 return newObject; 131 } 132 133 /** Override the base class to check the result of the evaluation 134 * of the expression. If the result is false, throw an exception. 135 * Otherwise, copy the inputs to the corresponding outputs. 136 * @exception IllegalActionException If the expression evaluates to false, 137 * or if the superclass throws it. 138 */ 139 @Override 140 public void fire() throws IllegalActionException { 141 super.fire(); 142 143 if (!((BooleanToken) _result).booleanValue()) { 144 StringBuffer info = new StringBuffer(); 145 info.append(message.stringValue()); 146 info.append("\nAssertion: "); 147 info.append(expression.getExpression()); 148 info.append("\nInput values:\n"); 149 for (String name : _tokenMap.keySet()) { 150 info.append(" "); 151 info.append(name); 152 info.append(" = "); 153 info.append(_tokenMap.get(name).toString()); 154 info.append("\n"); 155 } 156 throw new IllegalActionException(this, info.toString()); 157 } 158 159 // If we get here, assertion has passed. 160 // Copy the inputs to the outputs. 161 for (String inputName : _tokenMap.keySet()) { 162 TypedIOPort outputPort = _outputPortMap.get(inputName); 163 // NOTE: Expression does not seem to allow an input port to be a multiport. 164 // If that changes, then we need to iterate here over all input channels. 165 outputPort.send(0, _tokenMap.get(inputName)); 166 } 167 } 168 169 /** Override the base class to create a specialized port. 170 * @param name The name for the new port. 171 * @return The new port. 172 * @exception NameDuplicationException If the actor already has a port 173 * with the specified name. 174 */ 175 @Override 176 public Port newPort(String name) throws NameDuplicationException { 177 try { 178 return new AssertPort(this, name); 179 } catch (IllegalActionException e) { 180 throw new InternalErrorException(e); 181 } 182 } 183 184 /////////////////////////////////////////////////////////////////// 185 //// protected methods //// 186 187 /** Override the base class to create an output port corresponding 188 * to each new input port added. The output port will have as its 189 * display name the name of the input port. It will not be persistent 190 * (will not be exported to the MoML file). 191 * @param port The port to add to this entity. 192 * @exception IllegalActionException If the port has no name. 193 * @exception NameDuplicationException If the port name collides with a 194 * name already in the entity. 195 */ 196 @Override 197 protected void _addPort(TypedIOPort port) 198 throws IllegalActionException, NameDuplicationException { 199 super._addPort(port); 200 201 if (_creatingOutputPort || _cloning) { 202 return; 203 } 204 205 final String name = port.getName(); 206 if (name.equals("output") || name.startsWith(_OUTPUT_PORT_PREFIX)) { 207 return; 208 } 209 // NOTE: Don't want to do this if this Assert is a clone 210 // under construction because later the superclass will try 211 // to clone the output port and will fail. 212 // The _cloning flag above tells us that. 213 _createOutputPort(port); 214 } 215 216 /** Override the base class to remove the corresponding 217 * output port, if the specified port is an input port, or 218 * the corresponding input port, if the specified port is an 219 * output port. 220 * @param port The port to remove from this entity. 221 * name already in the entity. 222 */ 223 @Override 224 protected void _removePort(Port port) { 225 super._removePort(port); 226 String name = port.getName(); 227 228 // Remove the corresponding output port. 229 // NOTE: If a corresponding output port exists, then remove 230 // it whether this is an output port or not. The user may 231 // have accidentally added an output port. 232 String outputPortName = _OUTPUT_PORT_PREFIX + name; 233 Port outputPort = getPort(outputPortName); 234 if (outputPort != null) { 235 try { 236 outputPort.setContainer(null); 237 } catch (KernelException e) { 238 throw new InternalErrorException(e); 239 } 240 } 241 // If this port name matches the pattern of an output 242 // port, then remove the corresponding input port, if it exists. 243 if (name.startsWith(_OUTPUT_PORT_PREFIX)) { 244 // Remove the corresponding input port. 245 String inputName = name.substring(_OUTPUT_PORT_PREFIX.length()); 246 Port inputPort = getPort(inputName); 247 if (inputPort != null) { 248 try { 249 inputPort.setContainer(null); 250 } catch (KernelException e) { 251 throw new InternalErrorException(e); 252 } 253 } 254 } 255 } 256 257 /////////////////////////////////////////////////////////////////// 258 //// private methods //// 259 260 /** Create the corresponding output port for the given input port. 261 * @param port The input port. 262 * @exception InternalErrorException If something goes wrong. 263 */ 264 private void _createOutputPort(final TypedIOPort port) { 265 // Show the name of the input port. 266 SingletonParameter showName; 267 _creatingOutputPort = true; 268 try { 269 showName = new SingletonParameter(port, "_showName"); 270 showName.setPersistent(false); 271 showName.setExpression("true"); 272 273 String name = port.getName(); 274 275 // If there is already a port with the correct name, use that. 276 String outputPortName = _OUTPUT_PORT_PREFIX + name; 277 TypedIOPort outputPort = (TypedIOPort) Assert.this 278 .getPort(outputPortName); 279 if (outputPort == null) { 280 outputPort = new TypedIOPort(Assert.this, outputPortName, false, 281 true) { 282 // Make sure that this output port _never_ appears in MoML. 283 // If it is allowed to appear, subtle bugs will arise, for example 284 // when copying and pasting in actor-oriented classes. 285 @Override 286 public void exportMoML(Writer output, int depth, 287 String name) { 288 } 289 }; 290 // Display name should match the input port name. 291 outputPort.setDisplayName(name); 292 showName = new SingletonParameter(outputPort, "_showName"); 293 showName.setExpression("true"); 294 } 295 _outputPortMap.put(name, outputPort); 296 } catch (KernelException e) { 297 throw new InternalErrorException(e); 298 } finally { 299 _creatingOutputPort = false; 300 } 301 } 302 303 /////////////////////////////////////////////////////////////////// 304 //// private variables //// 305 306 /** Flag indicating that we are being cloned. 307 * Note that this flag will be cloned into the clone, so it needs 308 * to be reset in the both the clone and clonee. 309 */ 310 private boolean _cloning = false; 311 312 /** Flag indicating that we are adding a corresponding output port. */ 313 private boolean _creatingOutputPort; 314 315 /** Map from input port name to the corresponding output port. */ 316 private HashMap<String, TypedIOPort> _outputPortMap = new HashMap<String, TypedIOPort>(); 317 318 /** Prefix given to output port names. */ 319 private final static String _OUTPUT_PORT_PREFIX = "_correspondingOutputPort_"; 320 321 /////////////////////////////////////////////////////////////////// 322 //// inner classes //// 323 324 /** Class for ports created by the user for this actor. 325 * These should all be input ports, in theory. 326 * This class ensures that if you change the name of the 327 * port, then the name and displayName of the corresponding output 328 * port are both changed. 329 */ 330 public static class AssertPort extends TypedIOPort { 331 /** Construct a port for this actor. 332 * @param container The container. 333 * @param name The name. 334 * @exception IllegalActionException If the port cannot be contained 335 * by the proposed container. 336 * @exception NameDuplicationException If the container already has a 337 * port with this name. 338 */ 339 public AssertPort(Assert container, String name) 340 throws IllegalActionException, NameDuplicationException { 341 super(container, name); 342 } 343 344 // Override setName() to also change the name of the corresponding 345 // output port. 346 @Override 347 public void setName(final String name) 348 throws IllegalActionException, NameDuplicationException { 349 final String oldName = getName(); 350 super.setName(name); 351 // No need to do anything for the first name setting 352 // or if the name is not changing. 353 if (oldName != null && !oldName.equals(name)) { 354 // FIXME: The port dialog complains about this! 355 // But the operation succeeds. 356 Assert container = (Assert) getContainer(); 357 TypedIOPort outputPort = container._outputPortMap.get(oldName); 358 if (outputPort != null) { 359 outputPort.setName(_OUTPUT_PORT_PREFIX + name); 360 outputPort.setDisplayName(name); 361 container._outputPortMap.remove(oldName); 362 container._outputPortMap.put(name, outputPort); 363 } 364 } 365 } 366 } 367}