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}