001/* Copyright 2016 Clifton Labs
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 * http://www.apache.org/licenses/LICENSE-2.0
006 * Unless required by applicable law or agreed to in writing, software
007 * distributed under the License is distributed on an "AS IS" BASIS,
008 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
009 * See the License for the specific language governing permissions and
010 * limitations under the License. */
011package org.json.simple;
012
013import java.io.IOException;
014import java.io.Reader;
015import java.io.StringReader;
016import java.io.StringWriter;
017import java.io.Writer;
018import java.util.Collection;
019import java.util.EnumSet;
020import java.util.Iterator;
021import java.util.LinkedList;
022import java.util.Map;
023import java.util.Set;
024
025/** Jsoner provides JSON utilities for escaping strings to be JSON compatible, thread safe parsing (RFC 4627) JSON
026 * strings, and serializing data to strings in JSON format.
027 * @since 2.0.0 */
028public class Jsoner{
029        /** Flags to tweak the behavior of the primary deserialization method. */
030        private static enum DeserializationOptions{
031                /** Whether multiple JSON values can be deserialized as a root element. */
032                ALLOW_CONCATENATED_JSON_VALUES,
033                /** Whether a JsonArray can be deserialized as a root element. */
034                ALLOW_JSON_ARRAYS,
035                /** Whether a boolean, null, Number, or String can be deserialized as a root element. */
036                ALLOW_JSON_DATA,
037                /** Whether a JsonObject can be deserialized as a root element. */
038                ALLOW_JSON_OBJECTS;
039        }
040
041        /** Flags to tweak the behavior of the primary serialization method. */
042        private static enum SerializationOptions{
043                /** Instead of aborting serialization on non-JSON values that are Enums it will continue serialization with the
044                 * Enums' "${PACKAGE}.${DECLARING_CLASS}.${NAME}".
045                 * @see Enum
046                 * @deprecated 2.3.0 the enum should implement Jsonable instead. */
047                @Deprecated
048                ALLOW_FULLY_QUALIFIED_ENUMERATIONS,
049                /** Instead of aborting serialization on non-JSON values it will continue serialization by serializing the
050                 * non-JSON value directly into the now invalid JSON. Be mindful that invalid JSON will not successfully
051                 * deserialize. */
052                ALLOW_INVALIDS,
053                /** Instead of aborting serialization on non-JSON values that implement Jsonable it will continue serialization
054                 * by deferring serialization to the Jsonable.
055                 * @see Jsonable */
056                ALLOW_JSONABLES,
057                /** Instead of aborting serialization on non-JSON values it will continue serialization by using reflection to
058                 * best describe the value as a JsonObject.
059                 * @deprecated 2.3.0 there is no passive way to accomplish this contract and so will be abandoned. */
060                @Deprecated
061                ALLOW_UNDEFINEDS;
062        }
063
064        /** The possible States of a JSON deserializer. */
065        private static enum States{
066                /** Post-parsing state. */
067                DONE,
068                /** Pre-parsing state. */
069                INITIAL,
070                /** Parsing error, ParsingException should be thrown. */
071                PARSED_ERROR,
072                PARSING_ARRAY,
073                /** Parsing a key-value pair inside of an object. */
074                PARSING_ENTRY,
075                PARSING_OBJECT;
076        }
077
078        private Jsoner(){
079                /* Keeping it classy. */
080        }
081
082        /** Deserializes a readable stream according to the RFC 4627 JSON specification.
083         * @param readableDeserializable representing content to be deserialized as JSON.
084         * @return either a boolean, null, Number, String, JsonObject, or JsonArray that best represents the deserializable.
085         * @throws DeserializationException if an unexpected token is encountered in the deserializable. To recover from a
086         *         DeserializationException: fix the deserializable
087         *         to no longer have an unexpected token and try again. */
088        public static Object deserialize(final Reader readableDeserializable) throws DeserializationException{
089                return Jsoner.deserialize(readableDeserializable, EnumSet.of(DeserializationOptions.ALLOW_JSON_ARRAYS, DeserializationOptions.ALLOW_JSON_OBJECTS, DeserializationOptions.ALLOW_JSON_DATA)).get(0);
090        }
091
092        /** Deserialize a stream with all deserialized JSON values are wrapped in a JsonArray.
093         * @param deserializable representing content to be deserialized as JSON.
094         * @param flags representing the allowances and restrictions on deserialization.
095         * @return the allowable object best represented by the deserializable.
096         * @throws DeserializationException if a disallowed or unexpected token is encountered in the deserializable. To
097         *         recover from a DeserializationException: fix the
098         *         deserializable to no longer have a disallowed or unexpected token and try again. */
099        private static JsonArray deserialize(final Reader deserializable, final Set<DeserializationOptions> flags) throws DeserializationException{
100                final Yylex lexer = new Yylex(deserializable);
101                Yytoken token;
102                States currentState;
103                int returnCount = 1;
104                final LinkedList<States> stateStack = new LinkedList<>();
105                final LinkedList<Object> valueStack = new LinkedList<>();
106                stateStack.addLast(States.INITIAL);
107                //System.out.println("//////////DESERIALIZING//////////");
108                do{
109                        /* Parse through the parsable string's tokens. */
110                        currentState = Jsoner.popNextState(stateStack);
111                        token = Jsoner.lexNextToken(lexer);
112                        switch(currentState){
113                                case DONE:
114                                        /* The parse has finished a JSON value. */
115                                        if(!flags.contains(DeserializationOptions.ALLOW_CONCATENATED_JSON_VALUES) || Yytoken.Types.END.equals(token.getType())){
116                                                /* Break if concatenated values are not allowed or if an END token is read. */
117                                                break;
118                                        }
119                                        /* Increment the amount of returned JSON values and treat the token as if it were a fresh parse. */
120                                        returnCount += 1;
121                                        /* Fall through to the case for the initial state. */
122                                        //$FALL-THROUGH$
123                                case INITIAL:
124                                        /* The parse has just started. */
125                                        switch(token.getType()){
126                                                case DATUM:
127                                                        /* A boolean, null, Number, or String could be detected. */
128                                                        if(flags.contains(DeserializationOptions.ALLOW_JSON_DATA)){
129                                                                valueStack.addLast(token.getValue());
130                                                                stateStack.addLast(States.DONE);
131                                                        }else{
132                                                                throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.DISALLOWED_TOKEN, token);
133                                                        }
134                                                        break;
135                                                case LEFT_BRACE:
136                                                        /* An object is detected. */
137                                                        if(flags.contains(DeserializationOptions.ALLOW_JSON_OBJECTS)){
138                                                                valueStack.addLast(new JsonObject());
139                                                                stateStack.addLast(States.PARSING_OBJECT);
140                                                        }else{
141                                                                throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.DISALLOWED_TOKEN, token);
142                                                        }
143                                                        break;
144                                                case LEFT_SQUARE:
145                                                        /* An array is detected. */
146                                                        if(flags.contains(DeserializationOptions.ALLOW_JSON_ARRAYS)){
147                                                                valueStack.addLast(new JsonArray());
148                                                                stateStack.addLast(States.PARSING_ARRAY);
149                                                        }else{
150                                                                throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.DISALLOWED_TOKEN, token);
151                                                        }
152                                                        break;
153                                                default:
154                                                        /* Neither a JSON array or object was detected. */
155                                                        throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token);
156                                        }
157                                        break;
158                                case PARSED_ERROR:
159                                        /* The parse could be in this state due to the state stack not having a state to pop off. */
160                                        throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token);
161                                case PARSING_ARRAY:
162                                        switch(token.getType()){
163                                                case COMMA:
164                                                        /* The parse could detect a comma while parsing an array since it separates each element. */
165                                                        stateStack.addLast(currentState);
166                                                        break;
167                                                case DATUM:
168                                                        /* The parse found an element of the array. */
169                                                        JsonArray val = (JsonArray)valueStack.getLast();
170                                                        val.add(token.getValue());
171                                                        stateStack.addLast(currentState);
172                                                        break;
173                                                case LEFT_BRACE:
174                                                        /* The parse found an object in the array. */
175                                                        val = (JsonArray)valueStack.getLast();
176                                                        final JsonObject object = new JsonObject();
177                                                        val.add(object);
178                                                        valueStack.addLast(object);
179                                                        stateStack.addLast(currentState);
180                                                        stateStack.addLast(States.PARSING_OBJECT);
181                                                        break;
182                                                case LEFT_SQUARE:
183                                                        /* The parse found another array in the array. */
184                                                        val = (JsonArray)valueStack.getLast();
185                                                        final JsonArray array = new JsonArray();
186                                                        val.add(array);
187                                                        valueStack.addLast(array);
188                                                        stateStack.addLast(currentState);
189                                                        stateStack.addLast(States.PARSING_ARRAY);
190                                                        break;
191                                                case RIGHT_SQUARE:
192                                                        /* The parse found the end of the array. */
193                                                        if(valueStack.size() > returnCount){
194                                                                valueStack.removeLast();
195                                                        }else{
196                                                                /* The parse has been fully resolved. */
197                                                                stateStack.addLast(States.DONE);
198                                                        }
199                                                        break;
200                                                default:
201                                                        /* Any other token is invalid in an array. */
202                                                        throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token);
203                                        }
204                                        break;
205                                case PARSING_OBJECT:
206                                        /* The parse has detected the start of an object. */
207                                        switch(token.getType()){
208                                                case COMMA:
209                                                        /* The parse could detect a comma while parsing an object since it separates each key value
210                                                         * pair. Continue parsing the object. */
211                                                        stateStack.addLast(currentState);
212                                                        break;
213                                                case DATUM:
214                                                        /* The token ought to be a key. */
215                                                        if(token.getValue() instanceof String){
216                                                                /* JSON keys are always strings, strings are not always JSON keys but it is going to be
217                                                                 * treated as one. Continue parsing the object. */
218                                                                final String key = (String)token.getValue();
219                                                                valueStack.addLast(key);
220                                                                stateStack.addLast(currentState);
221                                                                stateStack.addLast(States.PARSING_ENTRY);
222                                                        }else{
223                                                                /* Abort! JSON keys are always strings and it wasn't a string. */
224                                                                throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token);
225                                                        }
226                                                        break;
227                                                case RIGHT_BRACE:
228                                                        /* The parse has found the end of the object. */
229                                                        if(valueStack.size() > returnCount){
230                                                                /* There are unresolved values remaining. */
231                                                                valueStack.removeLast();
232                                                        }else{
233                                                                /* The parse has been fully resolved. */
234                                                                stateStack.addLast(States.DONE);
235                                                        }
236                                                        break;
237                                                default:
238                                                        /* The parse didn't detect the end of an object or a key. */
239                                                        throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token);
240                                        }
241                                        break;
242                                case PARSING_ENTRY:
243                                        switch(token.getType()){
244                                                /* Parsed pair keys can only happen while parsing objects. */
245                                                case COLON:
246                                                        /* The parse could detect a colon while parsing a key value pair since it separates the key
247                                                         * and value from each other. Continue parsing the entry. */
248                                                        stateStack.addLast(currentState);
249                                                        break;
250                                                case DATUM:
251                                                        /* The parse has found a value for the parsed pair key. */
252                                                        String key = (String)valueStack.removeLast();
253                                                        JsonObject parent = (JsonObject)valueStack.getLast();
254                                                        parent.put(key, token.getValue());
255                                                        break;
256                                                case LEFT_BRACE:
257                                                        /* The parse has found an object for the parsed pair key. */
258                                                        key = (String)valueStack.removeLast();
259                                                        parent = (JsonObject)valueStack.getLast();
260                                                        final JsonObject object = new JsonObject();
261                                                        parent.put(key, object);
262                                                        valueStack.addLast(object);
263                                                        stateStack.addLast(States.PARSING_OBJECT);
264                                                        break;
265                                                case LEFT_SQUARE:
266                                                        /* The parse has found an array for the parsed pair key. */
267                                                        key = (String)valueStack.removeLast();
268                                                        parent = (JsonObject)valueStack.getLast();
269                                                        final JsonArray array = new JsonArray();
270                                                        parent.put(key, array);
271                                                        valueStack.addLast(array);
272                                                        stateStack.addLast(States.PARSING_ARRAY);
273                                                        break;
274                                                default:
275                                                        /* The parse didn't find anything for the parsed pair key. */
276                                                        throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token);
277                                        }
278                                        break;
279                                default:
280                                        break;
281                        }
282                        //System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~");
283                        //System.out.println(currentState);
284                        //System.out.println(token);
285                        //System.out.println(valueStack);
286                        //System.out.println(stateStack);
287                        /* If we're not at the END and DONE then do the above again. */
288                }while(!(States.DONE.equals(currentState) && Yytoken.Types.END.equals(token.getType())));
289                //System.out.println("!!!!!!!!!!DESERIALIZED!!!!!!!!!!");
290                return new JsonArray(valueStack);
291        }
292
293        /** A convenience method that assumes a StringReader to deserialize a string.
294         * @param deserializable representing content to be deserialized as JSON.
295         * @return either a boolean, null, Number, String, JsonObject, or JsonArray that best represents the deserializable.
296         * @throws DeserializationException if an unexpected token is encountered in the deserializable. To recover from a
297         *         DeserializationException: fix the deserializable
298         *         to no longer have an unexpected token and try again.
299         * @see Jsoner#deserialize(Reader)
300         * @see StringReader */
301        public static Object deserialize(final String deserializable) throws DeserializationException{
302                Object returnable;
303                StringReader readableDeserializable = null;
304                try{
305                        readableDeserializable = new StringReader(deserializable);
306                        returnable = Jsoner.deserialize(readableDeserializable);
307                }catch(final NullPointerException caught){
308                        /* They both have the same recovery scenario.
309                         * See StringReader.
310                         * If deserializable is null, it should be reasonable to expect null back. */
311                        returnable = null;
312                }finally{
313                        if(readableDeserializable != null){
314                                readableDeserializable.close();
315                        }
316                }
317                return returnable;
318        }
319
320        /** A convenience method that assumes a JsonArray must be deserialized.
321         * @param deserializable representing content to be deserializable as a JsonArray.
322         * @param defaultValue representing what would be returned if deserializable isn't a JsonArray or an IOException,
323         *        NullPointerException, or DeserializationException occurs during deserialization.
324         * @return a JsonArray that represents the deserializable, or the defaultValue if there isn't a JsonArray that
325         *         represents deserializable.
326         * @see Jsoner#deserialize(Reader) */
327        public static JsonArray deserialize(final String deserializable, final JsonArray defaultValue){
328                StringReader readable = null;
329                JsonArray returnable;
330                try{
331                        readable = new StringReader(deserializable);
332                        returnable = Jsoner.deserialize(readable, EnumSet.of(DeserializationOptions.ALLOW_JSON_ARRAYS)).<JsonArray> getCollection(0);
333                }catch(NullPointerException | DeserializationException caught){
334                        /* Don't care, just return the default value. */
335                        returnable = defaultValue;
336                }finally{
337                        if(readable != null){
338                                readable.close();
339                        }
340                }
341                return returnable;
342        }
343
344        /** A convenience method that assumes a JsonObject must be deserialized.
345         * @param deserializable representing content to be deserializable as a JsonObject.
346         * @param defaultValue representing what would be returned if deserializable isn't a JsonObject or an IOException,
347         *        NullPointerException, or DeserializationException occurs during deserialization.
348         * @return a JsonObject that represents the deserializable, or the defaultValue if there isn't a JsonObject that
349         *         represents deserializable.
350         * @see Jsoner#deserialize(Reader) */
351        public static JsonObject deserialize(final String deserializable, final JsonObject defaultValue){
352                StringReader readable = null;
353                JsonObject returnable;
354                try{
355                        readable = new StringReader(deserializable);
356                        returnable = Jsoner.deserialize(readable, EnumSet.of(DeserializationOptions.ALLOW_JSON_OBJECTS)).<JsonObject> getMap(0);
357                }catch(NullPointerException | DeserializationException caught){
358                        /* Don't care, just return the default value. */
359                        returnable = defaultValue;
360                }finally{
361                        if(readable != null){
362                                readable.close();
363                        }
364                }
365                return returnable;
366        }
367
368        /** A convenience method that assumes multiple RFC 4627 JSON values (except numbers) have been concatenated together
369         * for deserilization which will be collectively returned in a JsonArray wrapper.
370         * There may be numbers included, they just must not be concatenated together as it is prone to
371         * NumberFormatExceptions (thus causing a DeserializationException) or the numbers no longer represent their
372         * respective values.
373         * Examples:
374         * "123null321" returns [123, null, 321]
375         * "nullnullnulltruefalse\"\"{}[]" returns [null, null, null, true, false, "", {}, []]
376         * "123" appended to "321" returns [123321]
377         * "12.3" appended to "3.21" throws DeserializationException(NumberFormatException)
378         * "123" appended to "-321" throws DeserializationException(NumberFormatException)
379         * "123e321" appended to "-1" throws DeserializationException(NumberFormatException)
380         * "null12.33.21null" throws DeserializationException(NumberFormatException)
381         * @param deserializable representing concatenated content to be deserialized as JSON in one reader. Its contents
382         *        may
383         *        not contain two numbers concatenated together.
384         * @return a JsonArray that contains each of the concatenated objects as its elements. Each concatenated element is
385         *         either a boolean, null, Number, String, JsonArray, or JsonObject that best represents the concatenated
386         *         content inside deserializable.
387         * @throws DeserializationException if an unexpected token is encountered in the deserializable. To recover from a
388         *         DeserializationException: fix the deserializable to no longer have an unexpected token and try again. */
389        public static JsonArray deserializeMany(final Reader deserializable) throws DeserializationException{
390                return Jsoner.deserialize(deserializable, EnumSet.of(DeserializationOptions.ALLOW_JSON_ARRAYS, DeserializationOptions.ALLOW_JSON_OBJECTS, DeserializationOptions.ALLOW_JSON_DATA, DeserializationOptions.ALLOW_CONCATENATED_JSON_VALUES));
391        }
392
393        /** Escapes potentially confusing or important characters in the String provided.
394         * @param escapable an unescaped string.
395         * @return an escaped string for usage in JSON; An escaped string is one that has escaped all of the quotes ("),
396         *         backslashes (\), return character (\r), new line character (\n), tab character (\t),
397         *         backspace character (\b), form feed character (\f) and other control characters [u0000..u001F] or
398         *         characters [u007F..u009F], [u2000..u20FF] with a
399         *         backslash (\) which itself must be escaped by the backslash in a java string. */
400        public static String escape(final String escapable){
401                final StringBuilder builder = new StringBuilder();
402                final int characters = escapable.length();
403                for(int i = 0; i < characters; i++){
404                        final char character = escapable.charAt(i);
405                        switch(character){
406                                case '"':
407                                        builder.append("\\\"");
408                                        break;
409                                case '\\':
410                                        builder.append("\\\\");
411                                        break;
412                                case '\b':
413                                        builder.append("\\b");
414                                        break;
415                                case '\f':
416                                        builder.append("\\f");
417                                        break;
418                                case '\n':
419                                        builder.append("\\n");
420                                        break;
421                                case '\r':
422                                        builder.append("\\r");
423                                        break;
424                                case '\t':
425                                        builder.append("\\t");
426                                        break;
427                                case '/':
428                                        builder.append("\\/");
429                                        break;
430                                default:
431                                        /* The many characters that get replaced are benign to software but could be mistaken by people
432                                         * reading it for a JSON relevant character. */
433                                        if(((character >= '\u0000') && (character <= '\u001F')) || ((character >= '\u007F') && (character <= '\u009F')) || ((character >= '\u2000') && (character <= '\u20FF'))){
434                                                final String characterHexCode = Integer.toHexString(character);
435                                                builder.append("\\u");
436                                                for(int k = 0; k < (4 - characterHexCode.length()); k++){
437                                                        builder.append("0");
438                                                }
439                                                builder.append(characterHexCode.toUpperCase());
440                                        }else{
441                                                /* Character didn't need escaping. */
442                                                builder.append(character);
443                                        }
444                        }
445                }
446                return builder.toString();
447        }
448
449        /** Processes the lexer's reader for the next token.
450         * @param lexer represents a text processor being used in the deserialization process.
451         * @return a token representing a meaningful element encountered by the lexer.
452         * @throws DeserializationException if an unexpected character is encountered while processing the text. */
453        private static Yytoken lexNextToken(final Yylex lexer) throws DeserializationException{
454                Yytoken returnable;
455                /* Parse through the next token. */
456                try{
457                        returnable = lexer.yylex();
458                }catch(final IOException caught){
459                        throw new DeserializationException(-1, DeserializationException.Problems.UNEXPECTED_EXCEPTION, caught);
460                }
461                if(returnable == null){
462                        /* If there isn't another token, it must be the end. */
463                        returnable = new Yytoken(Yytoken.Types.END, null);
464                }
465                return returnable;
466        }
467
468        /** Creates a new JsonKey that wraps the given string and value. This function should NOT be
469         * used in favor of existing constants and enumerations to make code easier to maintain.
470         * @param key represents the JsonKey as a String.
471         * @param value represents the value the JsonKey uses.
472         * @return a JsonKey that represents the provided key and value. */
473        public static JsonKey mintJsonKey(final String key, final Object value){
474                return new JsonKey(){
475                        @Override
476                        public String getKey(){
477                                return key;
478                        }
479
480                        @Override
481                        public Object getValue(){
482                                return value;
483                        }
484                };
485        }
486
487        /** Used for state transitions while deserializing.
488         * @param stateStack represents the deserialization states saved for future processing.
489         * @return a state for deserialization context so it knows how to consume the next token. */
490        private static States popNextState(final LinkedList<States> stateStack){
491                if(stateStack.size() > 0){
492                        return stateStack.removeLast();
493                }else{
494                        return States.PARSED_ERROR;
495                }
496        }
497
498        /** Formats the JSON string to be more easily human readable using tabs for indentation.
499         * @param printable representing a JSON formatted string with out extraneous characters, like one returned from
500         *        Jsoner#serialize(Object).
501         * @return printable except it will have '\n' then '\t' characters inserted after '[', '{', ',' and before ']' '}'
502         *         tokens in the JSON. It will return null if printable isn't a JSON string. */
503        public static String prettyPrint(final String printable){
504                return Jsoner.prettyPrint(printable, "\t");
505        }
506
507        /** Formats the JSON string to be more easily human readable using an arbitrary amount of spaces for indentation.
508         * @param printable representing a JSON formatted string with out extraneous characters, like one returned from
509         *        Jsoner#serialize(Object).
510         * @param spaces representing the amount of spaces to use for indentation. Must be between 2 and 10.
511         * @return printable except it will have '\n' then space characters inserted after '[', '{', ',' and before ']' '}'
512         *         tokens in the JSON. It will return null if printable isn't a JSON string.
513         * @throws IllegalArgumentException if spaces isn't between [2..10].
514         * @see Jsoner#prettyPrint(String)
515         * @since 2.2.0 to allow pretty printing with spaces instead of tabs. */
516        public static String prettyPrint(final String printable, final int spaces){
517                if((spaces > 10) || (spaces < 2)){
518                        throw new IllegalArgumentException("Indentation with spaces must be between 2 and 10.");
519                }
520                final StringBuilder indentation = new StringBuilder("");
521                for(int i = 0; i < spaces; i++){
522                        indentation.append(" ");
523                }
524                return Jsoner.prettyPrint(printable, indentation.toString());
525        }
526
527        /** Makes the JSON string more easily human readable using indentation of the caller's choice.
528         * @param printable representing a JSON formatted string with out extraneous characters, like one returned from
529         *        Jsoner#serialize(Object).
530         * @param indentation representing the indentation used to format the JSON string.
531         * @return printable except it will have '\n' then indentation characters inserted after '[', '{', ',' and before
532         *         ']' '}'
533         *         tokens in the JSON. It will return null if printable isn't a JSON string. */
534        private static String prettyPrint(final String printable, final String indentation){
535                final Yylex lexer = new Yylex(new StringReader(printable));
536                Yytoken lexed;
537                final StringBuilder returnable = new StringBuilder();
538                int level = 0;
539                try{
540                        do{
541                                lexed = Jsoner.lexNextToken(lexer);
542                                switch(lexed.getType()){
543                                        case COLON:
544                                                returnable.append(":");
545                                                break;
546                                        case COMMA:
547                                                returnable.append(lexed.getValue());
548                                                returnable.append("\n");
549                                                for(int i = 0; i < level; i++){
550                                                        returnable.append(indentation);
551                                                }
552                                                break;
553                                        case END:
554                                                break;
555                                        case LEFT_BRACE:
556                                        case LEFT_SQUARE:
557                                                returnable.append(lexed.getValue());
558                                                returnable.append("\n");
559                                                level++;
560                                                for(int i = 0; i < level; i++){
561                                                        returnable.append(indentation);
562                                                }
563                                                break;
564                                        case RIGHT_BRACE:
565                                        case RIGHT_SQUARE:
566                                                returnable.append("\n");
567                                                level--;
568                                                for(int i = 0; i < level; i++){
569                                                        returnable.append(indentation);
570                                                }
571                                                returnable.append(lexed.getValue());
572                                                break;
573                                        default:
574                                                if(lexed.getValue() instanceof String){
575                                                        returnable.append("\"");
576                                                        returnable.append(Jsoner.escape((String)lexed.getValue()));
577                                                        returnable.append("\"");
578                                                }else{
579                                                        returnable.append(lexed.getValue());
580                                                }
581                                                break;
582                                }
583                                //System.out.println(lexed);
584                        }while(!lexed.getType().equals(Yytoken.Types.END));
585                }catch(final DeserializationException caught){
586                        /* This is according to the method's contract. */
587                        return null;
588                }
589                //System.out.println(printable);
590                //System.out.println(returnable);
591                //System.out.println(Jsoner.escape(returnable.toString()));
592                return returnable.toString();
593        }
594
595        /** A convenience method that assumes a StringWriter.
596         * @param jsonSerializable represents the object that should be serialized as a string in JSON format.
597         * @return a string, in JSON format, that represents the object provided.
598         * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON.
599         * @see Jsoner#serialize(Object, Writer)
600         * @see StringWriter */
601        public static String serialize(final Object jsonSerializable){
602                final StringWriter writableDestination = new StringWriter();
603                try{
604                        Jsoner.serialize(jsonSerializable, writableDestination);
605                }catch(final IOException caught){
606                        /* See StringWriter. */
607                }
608                return writableDestination.toString();
609        }
610
611        /** Serializes values according to the RFC 4627 JSON specification. It will also trust the serialization provided by
612         * any Jsonables it serializes and serializes Enums that don't implement Jsonable as a string of their fully
613         * qualified name.
614         * @param jsonSerializable represents the object that should be serialized in JSON format.
615         * @param writableDestination represents where the resulting JSON text is written to.
616         * @throws IOException if the writableDestination encounters an I/O problem, like being closed while in use.
617         * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON. */
618        public static void serialize(final Object jsonSerializable, final Writer writableDestination) throws IOException{
619                Jsoner.serialize(jsonSerializable, writableDestination, EnumSet.of(SerializationOptions.ALLOW_JSONABLES, SerializationOptions.ALLOW_FULLY_QUALIFIED_ENUMERATIONS));
620        }
621
622        /** Serialize values to JSON and write them to the provided writer based on behavior flags.
623         * @param jsonSerializable represents the object that should be serialized to a string in JSON format.
624         * @param writableDestination represents where the resulting JSON text is written to.
625         * @param replacement represents what is serialized instead of a non-JSON value when replacements are allowed.
626         * @param flags represents the allowances and restrictions on serialization.
627         * @throws IOException if the writableDestination encounters an I/O problem.
628         * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON.
629         * @see SerializationOptions */
630        private static void serialize(final Object jsonSerializable, final Writer writableDestination, final Set<SerializationOptions> flags) throws IOException{
631                if(jsonSerializable == null){
632                        /* When a null is passed in the word null is supported in JSON. */
633                        writableDestination.write("null");
634                }else if(((jsonSerializable instanceof Jsonable) && flags.contains(SerializationOptions.ALLOW_JSONABLES))){
635                        /* Writes the writable as defined by the writable. */
636                        writableDestination.write(((Jsonable)jsonSerializable).toJson());
637                }else if((jsonSerializable instanceof Enum) && flags.contains(SerializationOptions.ALLOW_FULLY_QUALIFIED_ENUMERATIONS)){
638                        /* Writes the enum as a special case of string. All enums (unless they implement Jsonable) will be the
639                         * string literal "${DECLARING_CLASS_NAME}.${ENUM_NAME}" as their value. */
640                        @SuppressWarnings("rawtypes")
641                        final Enum e = (Enum)jsonSerializable;
642                        writableDestination.write('"');
643                        writableDestination.write(e.getDeclaringClass().getName());
644                        writableDestination.write('.');
645                        writableDestination.write(e.name());
646                        writableDestination.write('"');
647                }else if(jsonSerializable instanceof String){
648                        /* Make sure the string is properly escaped. */
649                        writableDestination.write('"');
650                        writableDestination.write(Jsoner.escape((String)jsonSerializable));
651                        writableDestination.write('"');
652                }else if(jsonSerializable instanceof Character){
653                        /* Make sure the string is properly escaped. */
654                        //writableDestination.write('"');
655                        writableDestination.write(Jsoner.escape(jsonSerializable.toString()));
656                        //writableDestination.write('"');
657                }else if(jsonSerializable instanceof Double){
658                        if(((Double)jsonSerializable).isInfinite() || ((Double)jsonSerializable).isNaN()){
659                                /* Infinite and not a number are not supported by the JSON specification, so null is used instead. */
660                                writableDestination.write("null");
661                        }else{
662                                writableDestination.write(jsonSerializable.toString());
663                        }
664                }else if(jsonSerializable instanceof Float){
665                        if(((Float)jsonSerializable).isInfinite() || ((Float)jsonSerializable).isNaN()){
666                                /* Infinite and not a number are not supported by the JSON specification, so null is used instead. */
667                                writableDestination.write("null");
668                        }else{
669                                writableDestination.write(jsonSerializable.toString());
670                        }
671                }else if(jsonSerializable instanceof Number){
672                        writableDestination.write(jsonSerializable.toString());
673                }else if(jsonSerializable instanceof Boolean){
674                        writableDestination.write(jsonSerializable.toString());
675                }else if(jsonSerializable instanceof Map){
676                        /* Writes the map in JSON object format. */
677                        boolean isFirstEntry = true;
678                        @SuppressWarnings("rawtypes")
679                        final Iterator entries = ((Map)jsonSerializable).entrySet().iterator();
680                        writableDestination.write('{');
681                        while(entries.hasNext()){
682                                if(isFirstEntry){
683                                        isFirstEntry = false;
684                                }else{
685                                        writableDestination.write(',');
686                                }
687                                @SuppressWarnings("rawtypes")
688                                final Map.Entry entry = (Map.Entry)entries.next();
689                                Jsoner.serialize(entry.getKey(), writableDestination, flags);
690                                writableDestination.write(':');
691                                Jsoner.serialize(entry.getValue(), writableDestination, flags);
692                        }
693                        writableDestination.write('}');
694                }else if(jsonSerializable instanceof Collection){
695                        /* Writes the collection in JSON array format. */
696                        boolean isFirstElement = true;
697                        @SuppressWarnings("rawtypes")
698                        final Iterator elements = ((Collection)jsonSerializable).iterator();
699                        writableDestination.write('[');
700                        while(elements.hasNext()){
701                                if(isFirstElement){
702                                        isFirstElement = false;
703                                }else{
704                                        writableDestination.write(',');
705                                }
706                                Jsoner.serialize(elements.next(), writableDestination, flags);
707                        }
708                        writableDestination.write(']');
709                }else if(jsonSerializable instanceof byte[]){
710                        /* Writes the array in JSON array format. */
711                        final byte[] writableArray = (byte[])jsonSerializable;
712                        final int numberOfElements = writableArray.length;
713                        writableDestination.write('[');
714                        for(int i = 0; i < numberOfElements; i++){
715                                if(i == (numberOfElements - 1)){
716                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
717                                }else{
718                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
719                                        writableDestination.write(',');
720                                }
721                        }
722                        writableDestination.write(']');
723                }else if(jsonSerializable instanceof short[]){
724                        /* Writes the array in JSON array format. */
725                        final short[] writableArray = (short[])jsonSerializable;
726                        final int numberOfElements = writableArray.length;
727                        writableDestination.write('[');
728                        for(int i = 0; i < numberOfElements; i++){
729                                if(i == (numberOfElements - 1)){
730                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
731                                }else{
732                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
733                                        writableDestination.write(',');
734                                }
735                        }
736                        writableDestination.write(']');
737                }else if(jsonSerializable instanceof int[]){
738                        /* Writes the array in JSON array format. */
739                        final int[] writableArray = (int[])jsonSerializable;
740                        final int numberOfElements = writableArray.length;
741                        writableDestination.write('[');
742                        for(int i = 0; i < numberOfElements; i++){
743                                if(i == (numberOfElements - 1)){
744                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
745                                }else{
746                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
747                                        writableDestination.write(',');
748                                }
749                        }
750                        writableDestination.write(']');
751                }else if(jsonSerializable instanceof long[]){
752                        /* Writes the array in JSON array format. */
753                        final long[] writableArray = (long[])jsonSerializable;
754                        final int numberOfElements = writableArray.length;
755                        writableDestination.write('[');
756                        for(int i = 0; i < numberOfElements; i++){
757                                if(i == (numberOfElements - 1)){
758                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
759                                }else{
760                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
761                                        writableDestination.write(',');
762                                }
763                        }
764                        writableDestination.write(']');
765                }else if(jsonSerializable instanceof float[]){
766                        /* Writes the array in JSON array format. */
767                        final float[] writableArray = (float[])jsonSerializable;
768                        final int numberOfElements = writableArray.length;
769                        writableDestination.write('[');
770                        for(int i = 0; i < numberOfElements; i++){
771                                if(i == (numberOfElements - 1)){
772                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
773                                }else{
774                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
775                                        writableDestination.write(',');
776                                }
777                        }
778                        writableDestination.write(']');
779                }else if(jsonSerializable instanceof double[]){
780                        /* Writes the array in JSON array format. */
781                        final double[] writableArray = (double[])jsonSerializable;
782                        final int numberOfElements = writableArray.length;
783                        writableDestination.write('[');
784                        for(int i = 0; i < numberOfElements; i++){
785                                if(i == (numberOfElements - 1)){
786                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
787                                }else{
788                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
789                                        writableDestination.write(',');
790                                }
791                        }
792                        writableDestination.write(']');
793                }else if(jsonSerializable instanceof boolean[]){
794                        /* Writes the array in JSON array format. */
795                        final boolean[] writableArray = (boolean[])jsonSerializable;
796                        final int numberOfElements = writableArray.length;
797                        writableDestination.write('[');
798                        for(int i = 0; i < numberOfElements; i++){
799                                if(i == (numberOfElements - 1)){
800                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
801                                }else{
802                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
803                                        writableDestination.write(',');
804                                }
805                        }
806                        writableDestination.write(']');
807                }else if(jsonSerializable instanceof char[]){
808                        /* Writes the array in JSON array format. */
809                        final char[] writableArray = (char[])jsonSerializable;
810                        final int numberOfElements = writableArray.length;
811                        writableDestination.write("[\"");
812                        for(int i = 0; i < numberOfElements; i++){
813                                if(i == (numberOfElements - 1)){
814                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
815                                }else{
816                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
817                                        writableDestination.write("\",\"");
818                                }
819                        }
820                        writableDestination.write("\"]");
821                }else if(jsonSerializable instanceof Object[]){
822                        /* Writes the array in JSON array format. */
823                        final Object[] writableArray = (Object[])jsonSerializable;
824                        final int numberOfElements = writableArray.length;
825                        writableDestination.write('[');
826                        for(int i = 0; i < numberOfElements; i++){
827                                if(i == (numberOfElements - 1)){
828                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
829                                }else{
830                                        Jsoner.serialize(writableArray[i], writableDestination, flags);
831                                        writableDestination.write(",");
832                                }
833                        }
834                        writableDestination.write(']');
835                }else{
836                        /* It cannot by any measure be safely serialized according to specification. */
837                        if(flags.contains(SerializationOptions.ALLOW_INVALIDS)){
838                                /* Can be helpful for debugging how it isn't valid. */
839                                writableDestination.write(jsonSerializable.toString());
840                        }else{
841                                /* Notify the caller the cause of failure for the serialization. */
842                                throw new IllegalArgumentException("Encountered a: " + jsonSerializable.getClass().getName() + " as: " + jsonSerializable.toString() + "  that isn't JSON serializable.\n  Try:\n    1) Implementing the Jsonable interface for the object to return valid JSON. If it already does it probably has a bug.\n    2) If you cannot edit the source of the object or couple it with this library consider wrapping it in a class that does implement the Jsonable interface.\n    3) Otherwise convert it to a boolean, null, number, JsonArray, JsonObject, or String value before serializing it.\n    4) If you feel it should have serialized you could use a more tolerant serialization for debugging purposes.");
843                        }
844                }
845                //System.out.println(writableDestination.toString());
846        }
847
848        /** Serializes like the first version of this library.
849         * It has been adapted to use Jsonable for serializing custom objects, but otherwise works like the old JSON string
850         * serializer. It
851         * will allow non-JSON values in its output like the old one. It can be helpful for last resort log statements and
852         * debugging errors in self generated JSON. Anything serialized using this method isn't guaranteed to be
853         * deserializable.
854         * @param jsonSerializable represents the object that should be serialized in JSON format.
855         * @param writableDestination represents where the resulting JSON text is written to.
856         * @throws IOException if the writableDestination encounters an I/O problem, like being closed while in use. */
857        public static void serializeCarelessly(final Object jsonSerializable, final Writer writableDestination) throws IOException{
858                Jsoner.serialize(jsonSerializable, writableDestination, EnumSet.of(SerializationOptions.ALLOW_JSONABLES, SerializationOptions.ALLOW_INVALIDS));
859        }
860
861        /** Serializes JSON values and only JSON values according to the RFC 4627 JSON specification.
862         * @param jsonSerializable represents the object that should be serialized in JSON format.
863         * @param writableDestination represents where the resulting JSON text is written to.
864         * @throws IOException if the writableDestination encounters an I/O problem, like being closed while in use.
865         * @throws IllegalArgumentException if the jsonSerializable isn't serializable in raw JSON. */
866        public static void serializeStrictly(final Object jsonSerializable, final Writer writableDestination) throws IOException{
867                Jsoner.serialize(jsonSerializable, writableDestination, EnumSet.noneOf(SerializationOptions.class));
868        }
869}