001package votorola.g.script; // Copyright 2011-2012, Michael Allan.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE.
002
003import java.io.*;
004import votorola.g.lang.*;
005
006
007/** A writer that outputs JSON string content, properly escaping any characters that
008  * require it.
009  *
010  *     @see <a href='http://tools.ietf.org/html/rfc4627'>RFC 4627</a>
011  */
012public @ThreadSafe final class JSONStringWriter extends Writer
013{
014
015
016    /** Constructs a JSONStringWriter.
017      *
018      *     @param _out the underlying writer.
019      */
020    public JSONStringWriter( Writer _out )
021    {
022        if( _out == null ) throw new NullPointerException(); // fail fast
023
024        out = _out;
025    }
026
027
028
029   // - A p p e n d a b l e --------------------------------------------------------------
030
031
032    public @Override Writer append( final char c ) throws IOException
033    {
034        switch( c )
035        {
036            case '\\':
037            case '"':
038                out.append( '\\' ).append( c );
039                break;
040            case '\b':
041                out.append( '\\' ).append( 'b' );
042                break;
043            case '\f':
044                out.append( '\\' ).append( 'f' );
045                break;
046            case '\n':
047                out.append( '\\' ).append( 'n' );
048                break;
049            case '\r':
050                out.append( '\\' ).append( 'r' );
051                break;
052            case '\t':
053                out.append( '\\' ).append( 't' );
054                break;
055            default:
056                if( c > 0x1F ) out.append( c );
057                else
058                {
059                    // Note that JSON string content, like Java, is always Unicode.  So no
060                    // escaping need be done for out-of-range characters, only for these
061                    // control characters.
062                    out.append( '\\' ).append( 'u' );
063                    out.append( String.format( "%04x", (int)c ));
064                }
065        }
066        return JSONStringWriter.this;
067    }
068
069
070
071    public @Override Writer append( final CharSequence csq ) throws IOException
072    {
073        final int cN = csq.length();
074        for( int c = 0; c < cN; ++c ) append( csq.charAt( c ));
075        return JSONStringWriter.this;
076    }
077
078
079
080    /** Throws {@linkplain UnsupportedOperationException UnsupportedOperationException}
081      * because it is not yet needed.
082      */
083    public @Override Writer append( CharSequence _csq, int _start, int _end ) throws IOException
084    {
085        throw new UnsupportedOperationException();
086    }
087
088
089
090   // - C l o s e a b l e ----------------------------------------------------------------
091
092
093    /** Does nothing but close the underlying writer.  This writer itself need not be
094      * closed.
095      */
096    public @Override void close() throws IOException { out.close(); }
097      // Closed state is never tested during operations on this writer.  Closure has no
098      // effect on its state.  The underlying writer (out) is relied on to fulfill the
099      // contract in this regard.
100
101
102
103   // - F l u s h a b l e ----------------------------------------------------------------
104
105
106    /** Does nothing but flush the underlying writer.  This writer itself need not be
107      * flushed.
108      */
109    public @Override void flush() throws IOException { out.flush(); }
110
111
112
113   // - W r i t e r ----------------------------------------------------------------------
114
115
116    /** Throws {@linkplain UnsupportedOperationException UnsupportedOperationException}
117      * because it is not yet needed.
118      */
119    public @Override void write( char[] _cbuf ) throws IOException
120    {
121        throw new UnsupportedOperationException();
122    }
123
124
125
126    /** Throws {@linkplain UnsupportedOperationException UnsupportedOperationException}
127      * because it is not yet needed.
128      */
129    public @Override void write( char[] _cbuf, int _off, int _len ) throws IOException
130    {
131        throw new UnsupportedOperationException();
132    }
133
134
135
136    public @Override void write( final int c ) throws IOException { append( (char)c ); }
137
138
139
140    /** Throws {@linkplain UnsupportedOperationException UnsupportedOperationException}
141      * because it is not yet needed.
142      */
143    public @Override void write( String _str, int _off, int _len ) throws IOException
144    {
145        throw new UnsupportedOperationException();
146    }
147
148
149
150    public @Override void write( final String str ) throws IOException { append( str ); }
151
152
153
154//// P r i v a t e ///////////////////////////////////////////////////////////////////////
155
156
157    private final Writer out;
158
159
160
161}