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}