001/* 002 * Copyright (c) 2007-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: jianwu $' 006 * '$Date: 2013-07-02 23:10:26 +0000 (Tue, 02 Jul 2013) $' 007 * '$Revision: 32183 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.sdm.spa; 031 032import java.util.HashSet; 033import java.util.Iterator; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.Set; 037 038import ptolemy.actor.TypedAtomicActor; 039import ptolemy.actor.TypedIOPort; 040import ptolemy.data.ArrayToken; 041import ptolemy.data.BooleanToken; 042import ptolemy.data.IntToken; 043import ptolemy.data.RecordToken; 044import ptolemy.data.StringToken; 045import ptolemy.data.Token; 046import ptolemy.data.XMLToken; 047import ptolemy.data.expr.Parameter; 048import ptolemy.data.expr.StringParameter; 049import ptolemy.data.type.ArrayType; 050import ptolemy.data.type.BaseType; 051import ptolemy.data.type.MonotonicFunction; 052import ptolemy.data.type.RecordType; 053import ptolemy.data.type.Type; 054import ptolemy.graph.Inequality; 055import ptolemy.graph.InequalityTerm; 056import ptolemy.kernel.CompositeEntity; 057import ptolemy.kernel.util.Attribute; 058import ptolemy.kernel.util.IllegalActionException; 059import ptolemy.kernel.util.NameDuplicationException; 060import ptolemy.kernel.util.Settable; 061 062////////////////////////////////////////////////////////////////////////// 063//// ArrayPermute 064/** 065 * Create all permutations of input arrays. If <i>outputAll</i> is true, the 066 * output is an array; otherwise this actor produces the next permutation each 067 * time it fires. The permutation type is selected via <i>outputType</i>: either 068 * a record or an XML document. 069 * 070 * <p>Example: </p> 071 * <p> input port a: {1, 2} </p> 072 * <p> input port b: {"foo", "bar"} </p> 073 * <p> output: {{a=1, b="foo"}, {a=1, b="bar"}, {a=2, b="foo"}, {a=2, b="bar"}} </p> 074 * 075 * 076 * @author Daniel Crawl 077 * @version $Id: ArrayPermute.java 32183 2013-07-02 23:10:26Z jianwu $ 078 */ 079 080public class ArrayPermute extends TypedAtomicActor { 081 /** 082 * Construct an ArrayPermute with the given container and name. 083 * 084 * @param name 085 * The name of this actor. 086 * @exception IllegalActionException 087 * If the entity cannot be contained by the proposed 088 * container. 089 * @exception NameDuplicationException 090 * If the container already has an actor with this name. 091 */ 092 public ArrayPermute(CompositeEntity container, String name) 093 throws NameDuplicationException, IllegalActionException { 094 super(container, name); 095 096 // set default output type. 097 _curOutputKind = OutputKind.Record; 098 099 outputType = new StringParameter(this, "outputType"); 100 outputType.setExpression(_curOutputKind.getName()); 101 102 for (OutputKind kind : OutputKind.values()) { 103 outputType.addChoice(kind.getName()); 104 } 105 106 output = new TypedIOPort(this, "output", false, true); 107 108 outputAll = new Parameter(this, "outputAll", new BooleanToken(true)); 109 outputAll.setTypeEquals(BaseType.BOOLEAN); 110 111 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " 112 + "width=\"60\" height=\"20\" " + "style=\"fill:white\"/>\n" 113 + "</svg>\n"); 114 } 115 116 // ///////////////////////////////////////////////////////////////// 117 // // ports and parameters //// 118 119 /** The permutation output. */ 120 public TypedIOPort output = null; 121 122 /** The type of output: an array of records or an array of XML tokens. */ 123 public StringParameter outputType = null; 124 125 /** 126 * If true, output all permutations in an array. Otherwise output as 127 * individual tokens. 128 */ 129 public Parameter outputAll = null; 130 131 // ///////////////////////////////////////////////////////////////// 132 // // public methods //// 133 134 public void preinitialize() throws IllegalActionException { 135 super.preinitialize(); 136 137 _remaining = -1; 138 139 Object[] portArray = inputPortList().toArray(); 140 for (int i = 0; i < portArray.length; i++) { 141 TypedIOPort port = (TypedIOPort) portArray[i]; 142 Parameter param = (Parameter) port 143 .getAttribute("tokenConsumptionRate"); 144 145 try { 146 if (_outputAllVal && param != null) { 147 // remove 148 param.setContainer(null); 149 } else if (param == null) { 150 param = new Parameter(port, "tokenConsumptionRate"); 151 param.setVisibility(Settable.NOT_EDITABLE); 152 param.setTypeEquals(BaseType.INT); 153 } 154 } catch (NameDuplicationException e) { 155 throw new IllegalActionException(this, e.getMessage()); 156 } 157 } 158 } 159 160 public void initialize() throws IllegalActionException { 161 super.initialize(); 162 163 if (!_outputAllVal) { 164 _setTokenConsumptionRate(IntToken.ONE); 165 } 166 } 167 168 /** 169 * React to a change in attributes. 170 * 171 * @param attribute 172 * @exception IllegalActionException 173 */ 174 public void attributeChanged(Attribute attribute) 175 throws IllegalActionException { 176 if (attribute == outputType) { 177 String str = outputType.getExpression(); 178 179 if (str == null) { 180 throw new IllegalActionException(this, "Empty outputType."); 181 } else { 182 // make sure it's a valid type. 183 OutputKind kind = OutputKind.getKind(str); 184 185 if (kind == null) { 186 throw new IllegalActionException(this, 187 "Unknown output type: " + str); 188 } 189 190 _curOutputKind = kind; 191 } 192 } else if (attribute == outputAll) { 193 _outputAllVal = ((BooleanToken) outputAll.getToken()) 194 .booleanValue(); 195 } else { 196 super.attributeChanged(attribute); 197 } 198 } 199 200 /** Compute the permutations of the inputs and send to output port. */ 201 public void fire() throws IllegalActionException { 202 super.fire(); 203 204 if (_remaining == -1) { 205 List list = inputPortList(); 206 if (list.size() > 0) { 207 int count = 1; 208 209 _data = new LinkedList(); 210 211 _labels = new String[list.size()]; 212 _curPerm = new Token[list.size()]; 213 214 Object ports[] = list.toArray(); 215 for (int i = 0; i < ports.length; i++) { 216 TypedIOPort p = (TypedIOPort) ports[i]; 217 218 // collect the input port names 219 _labels[i] = p.getName(); 220 221 // collect all the input data 222 Token token = p.get(0); 223 ArrayToken array; 224 225 if(token instanceof ArrayToken) { 226 array = (ArrayToken) token; 227 } else { 228 array = new ArrayToken(new Token[] {token}); 229 } 230 231 _data.add(i, array); 232 233 // increment the number of elements in all permutations 234 count *= array.length(); 235 } 236 237 // allocate the permutation set 238 _set = _curOutputKind.allocateSet(count); 239 _setNext = 0; 240 241 // recursively build the set and then output it. 242 _permutate(list.size() - 1); 243 244 if (_outputAllVal) { 245 output.broadcast(new ArrayToken(_set)); 246 } else { 247 _remaining = _set.length - 1; 248 output.broadcast(_set[_remaining]); 249 _remaining--; 250 _setTokenConsumptionRate(IntToken.ZERO); 251 } 252 } 253 } else { 254 output.broadcast(_set[_remaining]); 255 _remaining--; 256 257 if (_remaining == -1) { 258 _setTokenConsumptionRate(IntToken.ONE); 259 } 260 } 261 } 262 263 /** Return the type constraints of this actor. The type constraint is 264 * that the type of the output ports is no less than the type of the 265 * fields of the input RecordToken. 266 * @return a list of Inequality. 267 */ 268 protected Set<Inequality> _customTypeConstraints() { 269 270 // Set the constraints between record fields and output ports. 271 Set<Inequality> constraints = new HashSet<Inequality>(); 272 273 // Since the input port has a clone of the above RecordType, need to 274 // get the type from the input port. 275 // RecordType inputTypes = (RecordType)input.getType(); 276 Iterator<?> outputPorts = outputPortList().iterator(); 277 278 while (outputPorts.hasNext()) { 279 TypedIOPort outputPort = (TypedIOPort) outputPorts.next(); 280 //String label = outputPort.getName(); 281 Inequality inequality = new Inequality(new FunctionTerm(), 282 outputPort.getTypeTerm()); 283 constraints.add(inequality); 284 } 285 286 return constraints; 287 } 288 289 /** 290 * Do not establish the usual default type constraints. 291 * @return null 292 */ 293 @Override 294 protected Set<Inequality> _defaultTypeConstraints() { 295 // See TypedAtomicActor for details. 296 return null; 297 } 298 299 // ///////////////////////////////////////////////////////////////// 300 // // private methods //// 301 302 /** Recursively create a set of input permutations. */ 303 private void _permutate(int level) throws IllegalActionException { 304 if (level >= 0) { 305 ArrayToken array = (ArrayToken) _data.get(level); 306 for (int i = 0; i < array.length(); i++) { 307 _curPerm[level] = array.getElement(i); 308 309 // base case 310 if (level == 0) { 311 // generate the token for the current permutation and 312 // add it to the set. 313 314 _set[_setNext] = _makeToken(); 315 _setNext++; 316 } else { 317 // recurse 318 _permutate(level - 1); 319 } 320 } 321 } 322 } 323 324 /** Make a token based on the current permutation. */ 325 private Token _makeToken() throws IllegalActionException { 326 Token retval = null; 327 switch (_curOutputKind) { 328 case XML: 329 String str = _genXML(); 330 331 try { 332 retval = new XMLToken(str); 333 } catch (Exception e) { 334 throw new IllegalActionException(this, 335 "Exception creating new XMLToken: " + e.getMessage()); 336 } 337 break; 338 339 case Record: 340 retval = new RecordToken(_labels, _curPerm); 341 break; 342 } 343 344 return retval; 345 } 346 347 /** Generate an XML string of the given permutation. */ 348 private String _genXML() { 349 StringBuffer retval = new StringBuffer("<"); 350 retval.append(_XML_ROOT_NAME); 351 retval.append(">"); 352 353 for (int i = 0; i < _curPerm.length; i++) { 354 String tokenStr = null; 355 356 // if the token is a StringToken, do stringValue so we 357 // don't get the surrounding quotes. 358 if (_curPerm[i] instanceof StringToken) { 359 tokenStr = ((StringToken) _curPerm[i]).stringValue(); 360 } else { 361 tokenStr = _curPerm[i].toString(); 362 } 363 364 retval.append("<" + _labels[i] + ">" + tokenStr + "</" + _labels[i] 365 + ">"); 366 } 367 retval.append("</"); 368 retval.append(_XML_ROOT_NAME); 369 retval.append(">"); 370 return retval.toString(); 371 } 372 373 /** Set the token consumption rate for all the input ports. */ 374 private void _setTokenConsumptionRate(IntToken token) 375 throws IllegalActionException { 376 Object[] portArray = inputPortList().toArray(); 377 for (int i = 0; i < portArray.length; i++) { 378 TypedIOPort port = (TypedIOPort) portArray[i]; 379 Parameter param = (Parameter) port 380 .getAttribute("tokenConsumptionRate"); 381 param.setToken(token); 382 } 383 } 384 385 // ///////////////////////////////////////////////////////////////// 386 // // private classes //// 387 388 /** A enumeration for the kinds of outputs */ 389 private enum OutputKind { 390 XML("XML"), Record("Record"); 391 392 OutputKind(String name) { 393 _name = name; 394 } 395 396 public String getName() { 397 return _name; 398 } 399 400 public static OutputKind getKind(String name) { 401 if (name == null) { 402 return null; 403 } else if (name.equals("XML")) { 404 return XML; 405 } else if (name.equals("Record")) { 406 return Record; 407 } else { 408 return null; 409 } 410 } 411 412 /** Allocate a token array of size <i>count</i>. */ 413 public Token[] allocateSet(int count) { 414 Token[] retval = null; 415 switch (this) { 416 case XML: 417 retval = new XMLToken[count]; 418 break; 419 case Record: 420 retval = new RecordToken[count]; 421 break; 422 } 423 return retval; 424 } 425 426 private String _name; 427 } 428 429 /** A class to determine the type of the output port. */ 430 private class FunctionTerm extends MonotonicFunction { 431 /* 432 * Get the type. If output kind is XML, return array of xml. Otherwise, 433 * return array of records with a label and value type corresponding to 434 * each input type. 435 */ 436 public Object getValue() { 437 Type retval = null; 438 switch (_curOutputKind) { 439 case XML: 440 retval = new ArrayType(BaseType.XMLTOKEN); 441 break; 442 443 case Record: 444 Object[] portArray = inputPortList().toArray(); 445 String labels[] = new String[portArray.length]; 446 Type types[] = new Type[portArray.length]; 447 for (int i = 0; i < portArray.length; i++) { 448 TypedIOPort port = (TypedIOPort) portArray[i]; 449 labels[i] = port.getName(); 450 Type inType = port.getType(); 451 452 if (inType instanceof ArrayType) { 453 types[i] = ((ArrayType) inType).getElementType(); 454 } else { 455 types[i] = inType; 456 } 457 458 } 459 460 RecordType recordType = new RecordType(labels, types); 461 462 if (_outputAllVal) { 463 retval = new ArrayType(recordType); 464 } else { 465 retval = recordType; 466 } 467 break; 468 } 469 470 //System.out.println("returning type = " + retval); 471 return retval; 472 } 473 474 /** 475 * Return the variables. If output is XML or have no inputs, return 476 * array of size 0. Otherwise, return terms from input ports. 477 */ 478 public InequalityTerm[] getVariables() { 479 InequalityTerm[] retval = null; 480 481 Object[] portArray = inputPortList().toArray(); 482 if (portArray.length == 0 || _curOutputKind == OutputKind.XML) { 483 retval = new InequalityTerm[0]; 484 } else { 485 List<InequalityTerm> terms = new LinkedList<InequalityTerm>(); 486 for (int i = 0; i < portArray.length; i++) { 487 TypedIOPort port = (TypedIOPort) portArray[i]; 488 InequalityTerm term = port.getTypeTerm(); 489 if(term.isSettable()) { 490 terms.add(term); 491 } 492 } 493 retval = terms.toArray(new InequalityTerm[0]); 494 } 495 496 return retval; 497 } 498 } 499 500 // ///////////////////////////////////////////////////////////////// 501 // // private variables //// 502 503 /** A list of the input arrays. */ 504 private LinkedList _data = null; 505 506 /** The set of permutations. */ 507 private Token _set[] = null; 508 509 /** The next index to use in the set. */ 510 private int _setNext = 0; 511 512 /** The names of the input arrays. */ 513 private String _labels[] = null; 514 515 /** The current permutation. */ 516 private Token _curPerm[] = null; 517 518 /** XML root element's name */ 519 private static final String _XML_ROOT_NAME = "perm"; 520 521 /** The currently selected output type. */ 522 private OutputKind _curOutputKind; 523 524 /** 525 * If true, output array of all permutations, else output next permutation. 526 */ 527 private boolean _outputAllVal; 528 529 /** Index of next permutation to output. */ 530 private int _remaining; 531}