001package votorola.g.net; // Copyright 2011, 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 java.net.*;
005import votorola.g.lang.*;
006
007
008/** A writer that outputs strings encoded in MIME format
009  * application/x-www-form-urlencoded.  It does only rudimentary buffering.  Feeding it
010  * truly massive strings will stretch the buffer to an inefficient size [FIX with
011  * trimToSize].
012  *
013  *     @see URLEncoder
014  */
015public @ThreadSafe final class URLEncodedWriter extends Writer
016{
017
018
019    /** Constructs a URLEncodedWriter.
020      *
021      *     @see #encoding()
022      *     @param _out the underlying writer.
023      */
024    public URLEncodedWriter( String _encoding, Writer _out )
025    {
026        if( _out == null ) throw new NullPointerException(); // fail fast
027
028        encoding = _encoding;
029        out = _out;
030    }
031
032
033
034   // ------------------------------------------------------------------------------------
035
036
037    /** The assumed character encoding, as required by URLEncoder.
038      *
039      *     @see URLEncoder
040      */
041    public String encoding() { return encoding; }
042
043
044        private final String encoding;
045
046
047
048   // - A p p e n d a b l e --------------------------------------------------------------
049
050
051    public @Override URLEncodedWriter append( char c ) throws IOException
052    {
053        write( c );
054        return URLEncodedWriter.this;
055    }
056
057
058
059    public @Override URLEncodedWriter append( CharSequence csq ) throws IOException
060    {
061        synchronized( lock )
062        {
063            b.append( csq );
064            bCheck();
065        }
066        return URLEncodedWriter.this;
067    }
068
069
070
071    public @Override URLEncodedWriter append( CharSequence csq, int start, int end )
072      throws IOException
073    {
074        synchronized( lock )
075        {
076            b.append( csq, start, end );
077            bCheck();
078        }
079        return URLEncodedWriter.this;
080    }
081
082
083   // - C l o s e a b l e ----------------------------------------------------------------
084
085
086    public void close() throws IOException
087    {
088        synchronized( lock )
089        {
090            if( !isClosed ) flush();
091
092            isClosed = true;
093        }
094        out.close();
095    }
096
097
098        private @ThreadRestricted("holds lock") boolean isClosed;
099
100
101
102   // - F l u s h a b l e ----------------------------------------------------------------
103
104
105    public void flush() throws IOException
106    {
107        synchronized( lock )
108        {
109            openCheck();
110            bFlush();
111        }
112        out.flush();
113    }
114
115
116
117   // - W r i t e r ----------------------------------------------------------------------
118
119
120    public @Override void write( char[] cbuf ) throws IOException
121    {
122        synchronized( lock )
123        {
124            b.append( cbuf );
125            bCheck();
126        }
127    }
128
129
130
131    public void write( char[] cbuf, int off, int len ) throws IOException
132    {
133        synchronized( lock )
134        {
135            b.append( cbuf, off, len );
136            bCheck();
137        }
138    }
139
140
141
142    public @Override void write( int c ) throws IOException
143    {
144        synchronized( lock )
145        {
146            b.append( (char)c );
147            bCheck();
148        }
149    }
150
151
152
153    public @Override void write( String str ) throws IOException { append( str ); }
154
155
156
157    public @Override void write( String str, int off, int len ) throws IOException
158    {
159        append( str, off, off + len );
160    }
161
162
163
164//// P r i v a t e ///////////////////////////////////////////////////////////////////////
165
166
167      @ThreadRestricted("holds lock")
168    private final StringBuilder b = new StringBuilder( /*initial capacity*/FLUSH_THRESHOLD + 150 );
169
170
171
172    private @ThreadRestricted("holds lock") void bCheck() throws IOException
173    {
174        assert Thread.holdsLock( lock );
175        openCheck();
176        if( b.length() > FLUSH_THRESHOLD ) bFlush();
177    }
178
179
180
181    private @ThreadRestricted("holds lock") void bFlush() throws IOException
182    {
183        assert Thread.holdsLock( lock );
184        out.write( URLEncoder.encode( b.toString(), encoding ));
185        b.delete( 0, b.length() );
186    }
187
188
189
190    private static final int FLUSH_THRESHOLD = 350;
191
192
193
194      @ThreadRestricted("holds lock")
195    private void openCheck() throws IOException // uphold the contract of Writer
196    {
197        assert Thread.holdsLock( lock );
198        if( isClosed ) throw new IOException( "attempt to operate on a closed writer" );
199    }
200
201
202
203    private final Writer out;
204
205
206
207}