001/* Converts a Token to a string containing JSON-formatted data.
002
003 Copyright (c) 2012-2016 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 */
028
029package ptolemy.actor.lib.conversions.json;
030
031import java.util.Set;
032
033import ptolemy.actor.lib.conversions.Converter;
034import ptolemy.data.ArrayToken;
035import ptolemy.data.DateToken;
036import ptolemy.data.LongToken;
037import ptolemy.data.MatrixToken;
038import ptolemy.data.RecordToken;
039import ptolemy.data.ScalarToken;
040import ptolemy.data.StringToken;
041import ptolemy.data.Token;
042import ptolemy.data.type.BaseType;
043import ptolemy.graph.Inequality;
044import ptolemy.kernel.CompositeEntity;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.NameDuplicationException;
047
048/**
049An actor that converts a Token into a StringToken containing JSON-formatted
050data. Nested structures in ArrayToken or RecordToken translate into
051correspondingly nested JSON output.
052
053<p><a href="http://www.json.org/">http://www.json.org/</a>
054- a description of the JSON format.</p>
055
056@see JSONToToken
057@author  Marten Lohstroh and Edward A. Lee
058@version $Id$
059@since Ptolemy II 10.0
060@Pt.ProposedRating Yellow (marten)
061@Pt.AcceptedRating Red (chx)
062 */
063public class TokenToJSON extends Converter {
064
065    /** Construct a TokenToJSON actor with the given container and name.
066     *  @param container The container.
067     *  @param name The name of this actor.
068     *  @exception IllegalActionException If the actor cannot be contained
069     *   by the proposed container.
070     *  @exception NameDuplicationException If the container already has an
071     *   actor with this name.
072     */
073    public TokenToJSON(CompositeEntity container, String name)
074            throws NameDuplicationException, IllegalActionException {
075        super(container, name);
076        output.setTypeEquals(BaseType.STRING);
077    }
078
079    ///////////////////////////////////////////////////////////////////
080    ////                         public methods                    ////
081
082    /** Construct a string that represents the argument in JSON format.
083     *  If the argument is a RecordToken, then a JSON object is returned
084     *  (a string that starts with '{' and ends with '}').
085     *  If the argument is an ArrayToken, then a JSON array is returned
086     *  (a string that starts with '[' and ends with ']').
087     *  In both cases, the contents of the record and array are constructed
088     *  recursively.
089     *  If the argument is any of the ScalarTokens, then a string representation
090     *  of the number or boolean is returned.
091     *  If the argument is null or a nil token, then the string "null" is returned.
092     *  If the argument is a StringToken, return its value (with quotation marks).
093     *  If the argument is a MatrixToken, then the matrix is represented as a
094     *  JSON array with the elements in row-scanned order (raster scan).
095     *
096     *  @param input Data to represent in JSON.
097     *  @return a string that represent the input in JSON format
098     *  @exception IllegalActionException If the Token found on the input cannot
099     *  be expressed in JSON format
100     */
101    public static String constructJSON(Token input)
102            throws IllegalActionException {
103        if (input == null || input.isNil()) {
104            return "null";
105        } else if (input instanceof LongToken) {
106            // The 'L' suffix is not supported in JSON.
107            String result = input.toString();
108            return result.substring(0, result.length() - 1);
109        } else if (input instanceof ScalarToken || input instanceof StringToken
110                || input instanceof DateToken) {
111            return input.toString();
112        } else if (input instanceof ArrayToken) {
113            return _scanArrayToken((ArrayToken) input);
114        } else if (input instanceof MatrixToken) {
115            return _scanArrayToken(((MatrixToken) input).toArray());
116        } else if (input instanceof RecordToken) {
117            return _scanRecordToken((RecordToken) input);
118        } else {
119            throw new IllegalActionException(
120                    "Conversion to JSON not supported for: "
121                            + input.toString());
122        }
123    }
124
125    /** Read a Token from the input and produce a corresponding JSON-formatted
126     *  string on the output.
127     *  @exception IllegalActionException If the input Token cannot be
128     *  converted to JSON.
129     */
130    @Override
131    public void fire() throws IllegalActionException {
132        super.fire();
133        output.send(0, new StringToken(constructJSON(input.get(0))));
134    }
135
136    /** Return false if the input port has no token, otherwise return
137     *  what the superclass returns (presumably true).
138     *  @exception IllegalActionException If there is no director.
139     */
140    @Override
141    public boolean prefire() throws IllegalActionException {
142        if (!input.hasToken(0)) {
143            return false;
144        }
145        return super.prefire();
146    }
147
148    ///////////////////////////////////////////////////////////////////
149    ////                         protected methods                 ////
150
151    /** Do not establish the usual default type constraints.
152     */
153    @Override
154    protected Set<Inequality> _defaultTypeConstraints() {
155        return null;
156    }
157
158    ///////////////////////////////////////////////////////////////////
159    ////                         private methods                   ////
160
161    /** Iterate over the elements in an ArrayToken and return a string starting
162     *  with '[' and ending with ']' that has the JSON representation of the elements
163     *  of the array separated by ",".
164     *
165     *  @param token An ArrayToken.
166     *  @return A JSON representation of the array.
167     *  @exception IllegalActionException If an element of the array cannot be expressed in JSON.
168     */
169    private static String _scanArrayToken(ArrayToken token)
170            throws IllegalActionException {
171        StringBuffer result = new StringBuffer("[");
172        boolean first = true;
173        for (Token element : token.arrayValue()) {
174            if (!first) {
175                result.append(",");
176            }
177            first = false;
178            result.append(constructJSON(element));
179        }
180        result.append("]");
181        return result.toString();
182    }
183
184    /** Iterate over the fields in an RecordToken and return a string starting
185     *  with '{' and ending with '}' that has the JSON representation of the fields
186     *  of the record separated by ", ".
187     *
188     *  @param token A RecordToken.
189     *  @return A JSON representation of the record.
190     *  @exception IllegalActionException If a field of the record cannot be expressed in JSON.
191     */
192    private static String _scanRecordToken(RecordToken token)
193            throws IllegalActionException {
194        StringBuffer result = new StringBuffer("{");
195        boolean first = true;
196        for (String label : token.labelSet()) {
197            if (!first) {
198                result.append(",");
199            }
200            first = false;
201            result.append("\"");
202            result.append(label);
203            result.append("\":");
204            result.append(constructJSON(token.get(label)));
205        }
206        result.append("}");
207        return result.toString();
208    }
209}