1818
1919package com .loopj .android .http ;
2020
21- import android .util .Base64 ;
22- import android .util .Base64OutputStream ;
2321import android .util .Log ;
2422
2523import org .apache .http .Header ;
2624import org .apache .http .HttpEntity ;
2725import org .apache .http .message .BasicHeader ;
2826
2927import java .io .BufferedOutputStream ;
30- import java .io .File ;
31- import java .io .FileInputStream ;
3228import java .io .IOException ;
3329import java .io .InputStream ;
3430import java .io .OutputStream ;
@@ -51,42 +47,47 @@ class JsonStreamerEntity implements HttpEntity {
5147
5248 private static final byte [] JSON_TRUE = "true" .getBytes ();
5349 private static final byte [] JSON_FALSE = "false" .getBytes ();
54- private static final byte [] STREAM_NAME = escape ("name" , true ). getBytes ( );
55- private static final byte [] STREAM_TYPE = escape ("type" , true ). getBytes ( );
56- private static final byte [] STREAM_CONTENTS = escape ("contents" , true ). getBytes ( );
57- private static final byte [] STREAM_ELAPSED = escape ("_elapsed" , true ). getBytes ( );
50+ private static final byte [] STREAM_NAME = escape ("name" );
51+ private static final byte [] STREAM_TYPE = escape ("type" );
52+ private static final byte [] STREAM_CONTENTS = escape ("contents" );
53+ private static final byte [] STREAM_ELAPSED = escape ("_elapsed" );
5854
59- private static final Header HEADER_JSON =
55+ private static final Header HEADER_JSON_CONTENT =
6056 new BasicHeader ("Content-Type" , "application/json" );
57+ private static final Header HEADER_GZIP_ENCODING =
58+ new BasicHeader ("Content-Encoding" , "gzip" );
6159 private static final String APPLICATION_OCTET_STREAM =
6260 "application/octet-stream" ;
6361
64- // Size of the byte-array buffer used to read from files .
62+ // Size of the byte-array buffer used to read from streams .
6563 private static final int BUFFER_SIZE = 2048 ;
6664
65+ // Reusable StringBuilder used by escape() method.
66+ // Base64, at worst, will make a binary stream grow in size by approximately
67+ // (n + 2 - ((n + 2) % 3)) / 3 * 4, which is roughly 1.3333333% for a
68+ // large 'n'.
69+ private static final StringBuilder BUILDER =
70+ new StringBuilder ((int )(BUFFER_SIZE * 1.35f ));
71+
6772 // K/V objects to be uploaded.
68- private final Map <String , Object > kvParams = new HashMap <String , Object >();
73+ private final Map <String , Object > kvParams =
74+ new HashMap <String , Object >();
6975
7076 // Streams and their associated meta-data to be uploaded.
71- private final Map <String , RequestParams .StreamWrapper > streamParams = new HashMap <String , RequestParams .StreamWrapper >();
77+ private final Map <String , RequestParams .StreamWrapper > streamParams =
78+ new HashMap <String , RequestParams .StreamWrapper >();
7279
7380 // Whether to use gzip compression while uploading
7481 private final Header contentEncoding ;
7582
7683 public JsonStreamerEntity (boolean contentEncoding ) {
77- this .contentEncoding = contentEncoding
78- ? new BasicHeader ("Content-Encoding" , "gzip" )
79- : null ;
84+ this .contentEncoding = contentEncoding ? HEADER_GZIP_ENCODING : null ;
8085 }
8186
8287 public void addPart (String key , Object value ) {
8388 kvParams .put (key , value );
8489 }
8590
86- public void addPart (String key , File file , String type ) throws IOException {
87- addPart (key , new FileInputStream (file ), file .getName (), type );
88- }
89-
9091 public void addPart (String key , InputStream inputStream , String name , String type ) {
9192 if (type == null ) {
9293 type = APPLICATION_OCTET_STREAM ;
@@ -121,7 +122,7 @@ public Header getContentEncoding() {
121122
122123 @ Override
123124 public Header getContentType () {
124- return HEADER_JSON ;
125+ return HEADER_JSON_CONTENT ;
125126 }
126127
127128 @ Override
@@ -139,13 +140,16 @@ public void writeTo(final OutputStream outstream) throws IOException {
139140 throw new IllegalStateException ("Output stream cannot be null." );
140141 }
141142
143+ // Record the time when uploading started.
142144 long now = System .currentTimeMillis ();
143- Log .i (LOG_TAG , "Started dumping at: " + now );
144145
145- OutputStream upload ;
146+ // Keys used by the HashMaps.
147+ Set <String > keys ;
146148
147- // GZIPOutputStream is available only from API level 8 and onward.
148- if (null != contentEncoding ) {
149+ // Use GZIP compression when sending streams, otherwise just use
150+ // a buffered output stream to speed things up a bit.
151+ OutputStream upload ;
152+ if (null != contentEncoding ) {
149153 upload = new GZIPOutputStream (new BufferedOutputStream (outstream ), BUFFER_SIZE );
150154 } else {
151155 upload = new BufferedOutputStream (outstream );
@@ -154,21 +158,18 @@ public void writeTo(final OutputStream outstream) throws IOException {
154158 // Always send a JSON object.
155159 upload .write ('{' );
156160
157- // Keys used by the HashMaps.
158- Set <String > keys ;
159-
160161 // Send the K/V values.
161162 keys = kvParams .keySet ();
162163 for (String key : keys ) {
163164 // Write the JSON object's key.
164- upload .write (escape (key , true ). getBytes ( ));
165+ upload .write (escape (key ));
165166 upload .write (':' );
166167
167168 // Evaluate the value (which cannot be null).
168169 Object value = kvParams .get (key );
169170
170171 if (value instanceof Boolean ) {
171- upload .write ((Boolean ) value ? JSON_TRUE : JSON_FALSE );
172+ upload .write ((Boolean )value ? JSON_TRUE : JSON_FALSE );
172173 } else if (value instanceof Long ) {
173174 upload .write ((((Number )value ).longValue () + "" ).getBytes ());
174175 } else if (value instanceof Double ) {
@@ -189,113 +190,125 @@ public void writeTo(final OutputStream outstream) throws IOException {
189190
190191 // Send the stream params.
191192 keys = streamParams .keySet ();
192- for (String key : keys ) {
193+ for (String key : keys ) {
193194 RequestParams .StreamWrapper entry = streamParams .get (key );
194195
195196 // Write the JSON object's key.
196- upload .write (escape (key , true ). getBytes ( ));
197+ upload .write (escape (key ));
197198
198199 // All uploads are sent as an object containing the file's details.
199- upload .write (":{" .getBytes ());
200+ upload .write (':' );
201+ upload .write ('{' );
200202
201203 // Send the streams's name.
202204 upload .write (STREAM_NAME );
203205 upload .write (':' );
204- upload .write (escape (entry .name , true ). getBytes ( ));
206+ upload .write (escape (entry .name ));
205207
206208 // Send the streams's content type.
207209 upload .write (STREAM_TYPE );
208210 upload .write (':' );
209- upload .write (escape (entry .contentType , true ). getBytes ( ));
211+ upload .write (escape (entry .contentType ));
210212
211213 // Prepare the file content's key.
212214 upload .write (STREAM_CONTENTS );
213215 upload .write (':' );
214216 upload .write ('"' );
215217
216- // Write the file's contents in Base64.
217- Base64OutputStream outputStream = new Base64OutputStream (upload , Base64 .NO_CLOSE | Base64 .NO_WRAP );
218+ // Upload the file's contents in Base64.
219+ Base64OutputStream outputStream =
220+ new Base64OutputStream (upload , Base64 .NO_CLOSE | Base64 .NO_WRAP );
221+
222+ // Read from input stream until no more data's left to read.
218223 int bytesRead ;
219- while (- 1 != ( bytesRead = entry .inputStream .read (buffer ))) {
224+ while (( bytesRead = entry .inputStream .read (buffer )) != - 1 ) {
220225 outputStream .write (buffer , 0 , bytesRead );
221226 }
222227
223- // Close the output stream.
228+ // Close the Base64 output stream.
224229 outputStream .close ();
225230
226- // Close the file's object.
231+ // End the file's object and prepare for next one .
227232 upload .write ('"' );
228233 upload .write ('}' );
229234 upload .write (',' );
230235 }
231236
232237 // Include the elapsed time taken to upload everything.
238+ // This might be useful for somebody, but it serves us well since
239+ // there will almost always be a ',' as the last sent character.
233240 upload .write (STREAM_ELAPSED );
234241 upload .write (':' );
235242 long elapsedTime = System .currentTimeMillis () - now ;
236243 upload .write ((elapsedTime + "}" ).getBytes ());
237244
238- Log .i (LOG_TAG , "JSON was uploaded in " + Math .floor (elapsedTime / 1000 ) + " seconds" );
245+ Log .i (LOG_TAG , "Uploaded JSON in " + Math .floor (elapsedTime / 1000 ) + " seconds" );
239246
240247 // Flush the contents up the stream.
241248 upload .flush ();
242249 upload .close ();
243250 }
244251
245- // Curtosy of Simple-JSON:
246- // http://goo.gl/XoW8RF
247- private static String escape (String string , boolean quotes ) {
248- StringBuilder sb = new StringBuilder ();
252+ // Curtosy of Simple-JSON: http://goo.gl/XoW8RF
253+ // Changed a bit to suit our needs in this class.
254+ static byte [] escape (String string ) {
255+ // Surround with quotations.
256+ BUILDER .append ('"' );
257+
249258 int length = string .length (), pos = -1 ;
250- if (quotes ) {
251- sb .append ('"' );
252- }
253259 while (++pos < length ) {
254260 char ch = string .charAt (pos );
255261 switch (ch ) {
256262 case '"' :
257- sb .append ("\\ \" " );
263+ BUILDER .append ("\\ \" " );
258264 break ;
259265 case '\\' :
260- sb .append ("\\ \\ " );
266+ BUILDER .append ("\\ \\ " );
261267 break ;
262268 case '\b' :
263- sb .append ("\\ b" );
269+ BUILDER .append ("\\ b" );
264270 break ;
265271 case '\f' :
266- sb .append ("\\ f" );
272+ BUILDER .append ("\\ f" );
267273 break ;
268274 case '\n' :
269- sb .append ("\\ n" );
275+ BUILDER .append ("\\ n" );
270276 break ;
271277 case '\r' :
272- sb .append ("\\ r" );
278+ BUILDER .append ("\\ r" );
273279 break ;
274280 case '\t' :
275- sb .append ("\\ t" );
281+ BUILDER .append ("\\ t" );
276282 break ;
277283 case '/' :
278- sb .append ("\\ /" );
284+ BUILDER .append ("\\ /" );
279285 break ;
280286 default :
281287 // Reference: http://www.unicode.org/versions/Unicode5.1.0/
282288 if ((ch >= '\u0000' && ch <= '\u001F' ) || (ch >= '\u007F' && ch <= '\u009F' ) || (ch >= '\u2000' && ch <= '\u20FF' )) {
283289 String intString = Integer .toHexString (ch );
284- sb .append ("\\ u" );
290+ BUILDER .append ("\\ u" );
285291 int intLength = 4 - intString .length ();
286292 for (int zero = 0 ; zero < intLength ; zero ++) {
287- sb .append ('0' );
293+ BUILDER .append ('0' );
288294 }
289- sb .append (intString .toUpperCase ());
295+ BUILDER .append (intString .toUpperCase ());
290296 } else {
291- sb .append (ch );
297+ BUILDER .append (ch );
292298 }
293299 break ;
294300 }
295301 }
296- if (quotes ) {
297- sb .append ('"' );
302+
303+ // Surround with quotations.
304+ BUILDER .append ('"' );
305+
306+ try {
307+ return BUILDER .toString ().getBytes ();
308+ } finally {
309+ // Empty the String buffer.
310+ // This is 20-30% faster than instantiating a new object.
311+ BUILDER .setLength (0 );
298312 }
299- return sb .toString ();
300313 }
301314}
0 commit comments