001/*
002 * Copyright (c) 2007-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: welker $'
006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 
007 * '$Revision: 24234 $'
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 ptolemy.actor.TypedAtomicActor;
033import ptolemy.actor.TypedIOPort;
034import ptolemy.data.ArrayToken;
035import ptolemy.data.BooleanToken;
036import ptolemy.data.StringToken;
037import ptolemy.data.Token;
038import ptolemy.data.XMLToken;
039import ptolemy.data.expr.Parameter;
040import ptolemy.data.type.ArrayType;
041import ptolemy.data.type.BaseType;
042import ptolemy.data.type.Type;
043import ptolemy.kernel.CompositeEntity;
044import ptolemy.kernel.util.Attribute;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.NameDuplicationException;
047
048//////////////////////////////////////////////////////////////////////////
049//// XMLAssembler
050/**
051 * 
052 * On each firing, read one token from each input port and assemble them into an
053 * XML document where the root element name is specified by the output port
054 * name.
055 * 
056 * @author Daniel Crawl
057 * @version $Id: XMLAssembler.java 24234 2010-05-06 05:21:26Z welker $
058 */
059public class XMLAssembler extends TypedAtomicActor {
060
061        /**
062         * Construct a XMLAssembler source with the given container and name.
063         * 
064         * @param name
065         *            The name of this actor.
066         * @exception IllegalActionException
067         *                If the entity cannot be contained by the proposed
068         *                container.
069         * @exception NameDuplicationException
070         *                If the container already has an actor with this name.
071         */
072        public XMLAssembler(CompositeEntity container, String name)
073                        throws NameDuplicationException, IllegalActionException {
074                super(container, name);
075
076                inputNil = new Parameter(this, "inputNil");
077                inputNil.setTypeEquals(BaseType.BOOLEAN);
078                inputNil.setExpression(String.valueOf(_inputNilVal));
079
080                encloseInputPortName = new Parameter(this, "encloseInputPortName");
081                encloseInputPortName.setTypeEquals(BaseType.BOOLEAN);
082                encloseInputPortName.setExpression(String
083                                .valueOf(_encloseInputPortNameVal));
084
085                _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" "
086                                + "width=\"60\" height=\"20\" " + "style=\"fill:white\"/>\n"
087                                + "</svg>\n");
088        }
089
090        // /////////////////////////////////////////////////////////////////
091        // // ports and parameters ////
092
093        /**
094         * If true, then for each unconnected input port an element is created in
095         * the output document with an attribute nil whose value is "true".
096         */
097        public Parameter inputNil = null;
098
099        /**
100         * If true, then each token received will be added to an element with the
101         * name of the input port. By setting it false, input XML documents can be
102         * merged into a single document.
103         */
104        public Parameter encloseInputPortName = null;
105
106        // /////////////////////////////////////////////////////////////////
107        // // public methods ////
108
109        /**
110         * React to a change in an attribute.
111         * 
112         * @param attribute
113         *            The changed parameter.
114         * @exception IllegalActionException
115         *                If the parameter set is not valid.
116         */
117        public void attributeChanged(Attribute attribute)
118                        throws IllegalActionException {
119                if (attribute == inputNil) {
120                        Token token = inputNil.getToken();
121                        _inputNilVal = ((BooleanToken) token).booleanValue();
122                } else if (attribute == encloseInputPortName) {
123                        Token token = encloseInputPortName.getToken();
124                        _encloseInputPortNameVal = ((BooleanToken) token).booleanValue();
125                }
126                super.attributeChanged(attribute);
127        }
128
129        /**
130         * Set all output ports whose types have not been set to XMLToken.
131         * 
132         * @exception IllegalActionException
133         */
134        public void preinitialize() throws IllegalActionException {
135                super.preinitialize();
136
137                Object[] portArray = outputPortList().toArray();
138                for (int i = 0; i < portArray.length; i++) {
139                        TypedIOPort port = (TypedIOPort) portArray[0];
140                        if (port.getType() == BaseType.UNKNOWN) {
141                                port.setTypeEquals(BaseType.XMLTOKEN);
142                        }
143                }
144        }
145
146        /**
147         * Read each input port, assemble the XML document, and send it to all
148         * output ports.
149         * 
150         * @exception IllegalActionException
151         *                If it is thrown by the send() method sending out the
152         *                token.
153         */
154        public void fire() throws IllegalActionException {
155                super.fire();
156
157                StringBuffer xmlStr = new StringBuffer();
158
159                // read a value from each port into an xml string
160                Object[] inPortArray = inputPortList().toArray();
161                for (int i = 0; i < inPortArray.length; i++) {
162                        TypedIOPort port = (TypedIOPort) inPortArray[i];
163                        String name = port.getName();
164
165                        // see if there's nothing connected and we allow nils
166                        if (_inputNilVal && port.getWidth() == 0) {
167                                xmlStr.append("<" + name + " nil=\"true\"/>");
168                        } else {
169                                _addTokenStr(xmlStr, port);
170                        }
171                }
172
173                // create documents and send for each output port
174                Object[] outPortArray = outputPortList().toArray();
175                for (int i = 0; i < outPortArray.length; i++) {
176                        TypedIOPort port = (TypedIOPort) outPortArray[i];
177                        String name = port.getName();
178
179                        String outputStr = "<" + name + ">" + xmlStr.toString() + "</"
180                                        + name + ">";
181
182                        Token token = null;
183
184                        try {
185                                token = new XMLToken(outputStr);
186                        } catch (Exception e) {
187                                throw new IllegalActionException(this,
188                                                "XMLAssembler: unable to create XMLToken with string: "
189                                                                + xmlStr);
190                        }
191
192                        port.broadcast(token);
193                }
194        }
195
196        // /////////////////////////////////////////////////////////////////
197        // // private methods ////
198
199        /**
200         * Consume and add the token to a string buffer.
201         * 
202         * @param buf
203         *            the buffer
204         * @param port
205         *            input port that supplies the token
206         */
207        private void _addTokenStr(StringBuffer buf, TypedIOPort port)
208                        throws IllegalActionException {
209                Token token = port.get(0);
210
211                if (port.getType() instanceof ArrayType) {
212                        Token[] array = ((ArrayToken) token).arrayValue();
213                        for (int i = 0; i < array.length; i++) {
214                                _addOneTokenStr(buf, port.getName(), array[i]);
215                        }
216                } else {
217                        _addOneTokenStr(buf, port.getName(), token);
218                }
219        }
220
221        /**
222         * Add a single value from a token, optionally enclosing it in the port's
223         * name, to a StringBuffer.
224         * 
225         * @param buf
226         *            the buffer
227         * @param portName
228         *            the name of the input port
229         * @param token
230         *            the token
231         */
232        private void _addOneTokenStr(StringBuffer buf, String portName, Token token) {
233                if (_encloseInputPortNameVal) {
234                        buf.append("<" + portName + ">");
235                }
236
237                // if it's a string token, do a stringValue so we don't get
238                // the quotes
239                Type tokenType = token.getType();
240                if (tokenType == BaseType.STRING) {
241                        buf.append(((StringToken) token).stringValue());
242                } else {
243                        String tokenStr = token.toString();
244
245                        // see if xml token with beginning <?xml
246                        if (tokenType == BaseType.XMLTOKEN
247                                        && tokenStr.indexOf("<?xml") == 0) {
248                                tokenStr = tokenStr.substring(tokenStr.indexOf(">") + 1);
249                        }
250
251                        buf.append(tokenStr);
252                }
253
254                if (_encloseInputPortNameVal) {
255                        buf.append("</" + portName + ">");
256                }
257        }
258
259        // /////////////////////////////////////////////////////////////////
260        // // private members ////
261
262        // whether to add element with nil="true" attribute for unconnected
263        // input ports. (see inputNil)
264        private boolean _inputNilVal = true;
265
266        // whether to put each token value read into an element with the same
267        // name as input port. (see encloseInputPortName)
268        private boolean _encloseInputPortNameVal = true;
269}