24
24
import org .apache .http .HttpEntity ;
25
25
import org .apache .http .message .BasicHeader ;
26
26
27
- import java .io .BufferedOutputStream ;
27
+ import java .io .FileInputStream ;
28
28
import java .io .IOException ;
29
29
import java .io .InputStream ;
30
30
import java .io .OutputStream ;
@@ -46,15 +46,16 @@ class JsonStreamerEntity implements HttpEntity {
46
46
private static final UnsupportedOperationException ERR_UNSUPPORTED =
47
47
new UnsupportedOperationException ("Unsupported operation in this implementation." );
48
48
49
- // Size of the byte-array buffer used to read from streams.
50
- private static final int BUFFER_SIZE = 2048 ;
49
+ // Size of the byte-array buffer used in I/O streams.
50
+ private static final int BUFFER_SIZE = 4096 ;
51
+
52
+ // Buffer used for reading from input streams.
53
+ private final byte [] buffer = new byte [BUFFER_SIZE ];
51
54
52
55
// Reusable StringBuilder used by escape() method.
53
- // Base64, at worst, will make a binary stream grow in size by approximately
54
- // (n + 2 - ((n + 2) % 3)) / 3 * 4, which is roughly 1.3333333% for a
55
- // large 'n'.
56
- private static final StringBuilder BUILDER =
57
- new StringBuilder ((int )(BUFFER_SIZE * 1.35f ));
56
+ // Its size is just initial, if more space is needed, the system will
57
+ // automatically enlarge the buffer.
58
+ private static final StringBuilder BUILDER = new StringBuilder (128 );
58
59
59
60
private static final byte [] JSON_TRUE = "true" .getBytes ();
60
61
private static final byte [] JSON_FALSE = "false" .getBytes ();
@@ -68,33 +69,28 @@ class JsonStreamerEntity implements HttpEntity {
68
69
new BasicHeader ("Content-Type" , "application/json" );
69
70
private static final Header HEADER_GZIP_ENCODING =
70
71
new BasicHeader ("Content-Encoding" , "gzip" );
71
- private static final String APPLICATION_OCTET_STREAM =
72
- "application/octet-stream" ;
73
-
74
- // K/V objects to be uploaded.
75
- private final Map <String , Object > kvParams =
76
- new HashMap ();
77
72
78
- // Streams and their associated meta-data to be uploaded.
79
- private final Map <String , RequestParams .StreamWrapper > streamParams =
80
- new HashMap ();
73
+ // JSON data and associated meta-data to be uploaded.
74
+ private final Map <String , Object > jsonParams = new HashMap ();
81
75
82
76
// Whether to use gzip compression while uploading
83
77
private final Header contentEncoding ;
84
78
85
- public JsonStreamerEntity (boolean contentEncoding ) {
86
- this .contentEncoding = contentEncoding ? HEADER_GZIP_ENCODING : null ;
87
- }
79
+ private final ResponseHandlerInterface progressHandler ;
88
80
89
- public void addPart (String key , Object value ) {
90
- kvParams .put (key , value );
81
+ public JsonStreamerEntity (ResponseHandlerInterface progressHandler , boolean useGZipCompression ) {
82
+ this .progressHandler = progressHandler ;
83
+ this .contentEncoding = useGZipCompression ? HEADER_GZIP_ENCODING : null ;
91
84
}
92
85
93
- public void addPart (String key , InputStream inputStream , String name , String type ) {
94
- if (type == null ) {
95
- type = APPLICATION_OCTET_STREAM ;
96
- }
97
- streamParams .put (key , new RequestParams .StreamWrapper (inputStream , name , type ));
86
+ /**
87
+ * Add content parameter, identified by the given key, to the request.
88
+ *
89
+ * @param key entity's name
90
+ * @param value entity's value (Scalar, FileWrapper, StreamWrapper)
91
+ */
92
+ public void addPart (String key , Object value ) {
93
+ jsonParams .put (key , value );
98
94
}
99
95
100
96
@ Override
@@ -137,122 +133,176 @@ public InputStream getContent() throws IOException, UnsupportedOperationExceptio
137
133
}
138
134
139
135
@ Override
140
- public void writeTo (final OutputStream outstream ) throws IOException {
141
- if (outstream == null ) {
136
+ public void writeTo (final OutputStream out ) throws IOException {
137
+ if (out == null ) {
142
138
throw new IllegalStateException ("Output stream cannot be null." );
143
139
}
144
140
145
141
// Record the time when uploading started.
146
142
long now = System .currentTimeMillis ();
147
143
148
- // Keys used by the HashMaps.
149
- Set <String > keys ;
150
-
151
144
// Use GZIP compression when sending streams, otherwise just use
152
145
// a buffered output stream to speed things up a bit.
153
- OutputStream upload ;
154
- if (null != contentEncoding ) {
155
- upload = new GZIPOutputStream (new BufferedOutputStream (outstream ), BUFFER_SIZE );
156
- } else {
157
- upload = new BufferedOutputStream (outstream );
158
- }
146
+ OutputStream os = null != contentEncoding
147
+ ? new GZIPOutputStream (out , BUFFER_SIZE )
148
+ : out ;
159
149
160
150
// Always send a JSON object.
161
- upload .write ('{' );
151
+ os .write ('{' );
162
152
163
- // Send the K/V values.
164
- keys = kvParams .keySet ();
165
- for (String key : keys ) {
166
- // Write the JSON object's key.
167
- upload .write (escape (key ));
168
- upload .write (':' );
153
+ // Keys used by the HashMaps.
154
+ Set <String > keys = jsonParams .keySet ();
155
+
156
+ boolean isFileWrapper ;
169
157
158
+ // Go over all keys and handle each's value.
159
+ for (String key : keys ) {
170
160
// Evaluate the value (which cannot be null).
171
- Object value = kvParams .get (key );
161
+ Object value = jsonParams .get (key );
162
+
163
+ // Bail out prematurely if value's null.
164
+ if (value == null ) {
165
+ continue ;
166
+ }
172
167
173
- if (value instanceof Boolean ) {
174
- upload .write ((Boolean )value ? JSON_TRUE : JSON_FALSE );
168
+ // Write the JSON object's key.
169
+ os .write (escape (key ));
170
+ os .write (':' );
171
+
172
+ // Check if this is a FileWrapper.
173
+ isFileWrapper = value instanceof RequestParams .FileWrapper ;
174
+
175
+ // If a file should be uploaded.
176
+ if (isFileWrapper || value instanceof RequestParams .StreamWrapper ) {
177
+ // All uploads are sent as an object containing the file's details.
178
+ os .write ('{' );
179
+
180
+ // Determine how to handle this entry.
181
+ if (isFileWrapper ) {
182
+ writeToFromFile (os , (RequestParams .FileWrapper )value );
183
+ } else {
184
+ writeToFromStream (os , (RequestParams .StreamWrapper )value );
185
+ }
186
+
187
+ // End the file's object and prepare for next one.
188
+ os .write ('}' );
189
+ } else if (value instanceof Boolean ) {
190
+ os .write ((Boolean )value ? JSON_TRUE : JSON_FALSE );
175
191
} else if (value instanceof Long ) {
176
- upload .write ((((Number )value ).longValue () + "" ).getBytes ());
192
+ os .write ((((Number )value ).longValue () + "" ).getBytes ());
177
193
} else if (value instanceof Double ) {
178
- upload .write ((((Number )value ).doubleValue () + "" ).getBytes ());
194
+ os .write ((((Number )value ).doubleValue () + "" ).getBytes ());
179
195
} else if (value instanceof Float ) {
180
- upload .write ((((Number )value ).floatValue () + "" ).getBytes ());
196
+ os .write ((((Number )value ).floatValue () + "" ).getBytes ());
181
197
} else if (value instanceof Integer ) {
182
- upload .write ((((Number )value ).intValue () + "" ).getBytes ());
198
+ os .write ((((Number )value ).intValue () + "" ).getBytes ());
183
199
} else {
184
- upload .write (value .toString ().getBytes ());
200
+ os .write (value .toString ().getBytes ());
185
201
}
186
202
187
- upload .write (',' );
188
- }
189
-
190
- // Buffer used for reading from input streams.
191
- byte [] buffer = new byte [BUFFER_SIZE ];
192
-
193
- // Send the stream params.
194
- keys = streamParams .keySet ();
195
- for (String key : keys ) {
196
- RequestParams .StreamWrapper entry = streamParams .get (key );
197
-
198
- // Write the JSON object's key.
199
- upload .write (escape (key ));
200
-
201
- // All uploads are sent as an object containing the file's details.
202
- upload .write (':' );
203
- upload .write ('{' );
204
-
205
- // Send the streams's name.
206
- upload .write (STREAM_NAME );
207
- upload .write (':' );
208
- upload .write (escape (entry .name ));
209
- upload .write (',' );
210
-
211
- // Send the streams's content type.
212
- upload .write (STREAM_TYPE );
213
- upload .write (':' );
214
- upload .write (escape (entry .contentType ));
215
- upload .write (',' );
216
-
217
- // Prepare the file content's key.
218
- upload .write (STREAM_CONTENTS );
219
- upload .write (':' );
220
- upload .write ('"' );
221
-
222
- // Upload the file's contents in Base64.
223
- Base64OutputStream outputStream =
224
- new Base64OutputStream (upload , Base64 .NO_CLOSE | Base64 .NO_WRAP );
225
-
226
- // Read from input stream until no more data's left to read.
227
- int bytesRead ;
228
- while ((bytesRead = entry .inputStream .read (buffer )) != -1 ) {
229
- outputStream .write (buffer , 0 , bytesRead );
230
- }
231
-
232
- // Close the Base64 output stream.
233
- outputStream .close ();
234
-
235
- // End the file's object and prepare for next one.
236
- upload .write ('"' );
237
- upload .write ('}' );
238
- upload .write (',' );
203
+ os .write (',' );
239
204
}
240
205
241
206
// Include the elapsed time taken to upload everything.
242
207
// This might be useful for somebody, but it serves us well since
243
208
// there will almost always be a ',' as the last sent character.
244
- upload .write (STREAM_ELAPSED );
245
- upload .write (':' );
209
+ os .write (STREAM_ELAPSED );
210
+ os .write (':' );
246
211
long elapsedTime = System .currentTimeMillis () - now ;
247
- upload .write ((elapsedTime + "}" ).getBytes ());
212
+ os .write ((elapsedTime + "}" ).getBytes ());
248
213
249
214
Log .i (LOG_TAG , "Uploaded JSON in " + Math .floor (elapsedTime / 1000 ) + " seconds" );
250
215
251
216
// Flush the contents up the stream.
252
- upload .flush ();
253
- upload . close ( );
217
+ os .flush ();
218
+ AsyncHttpClient . silentCloseOutputStream ( os );
254
219
}
255
220
221
+ private void writeToFromStream (OutputStream os , RequestParams .StreamWrapper entry )
222
+ throws IOException {
223
+
224
+ // Send the meta data.
225
+ writeMetaData (os , entry .name , entry .contentType );
226
+
227
+ int bytesRead ;
228
+
229
+ // Upload the file's contents in Base64.
230
+ Base64OutputStream bos =
231
+ new Base64OutputStream (os , Base64 .NO_CLOSE | Base64 .NO_WRAP );
232
+
233
+ // Read from input stream until no more data's left to read.
234
+ while ((bytesRead = entry .inputStream .read (buffer )) != -1 ) {
235
+ bos .write (buffer , 0 , bytesRead );
236
+ }
237
+
238
+ // Close the Base64 output stream.
239
+ AsyncHttpClient .silentCloseOutputStream (bos );
240
+
241
+ // End the meta data.
242
+ endMetaData (os );
243
+
244
+ // Close input stream.
245
+ if (entry .autoClose ) {
246
+ // Safely close the input stream.
247
+ AsyncHttpClient .silentCloseInputStream (entry .inputStream );
248
+ }
249
+ }
250
+
251
+ private void writeToFromFile (OutputStream os , RequestParams .FileWrapper wrapper )
252
+ throws IOException {
253
+
254
+ // Send the meta data.
255
+ writeMetaData (os , wrapper .file .getName (), wrapper .contentType );
256
+
257
+ int bytesRead , bytesWritten = 0 , totalSize = (int )wrapper .file .length ();
258
+
259
+ // Open the file for reading.
260
+ FileInputStream in = new FileInputStream (wrapper .file );
261
+
262
+ // Upload the file's contents in Base64.
263
+ Base64OutputStream bos =
264
+ new Base64OutputStream (os , Base64 .NO_CLOSE | Base64 .NO_WRAP );
265
+
266
+ // Read from file until no more data's left to read.
267
+ while ((bytesRead = in .read (buffer )) != -1 ) {
268
+ bos .write (buffer , 0 , bytesRead );
269
+ bytesWritten += bytesRead ;
270
+ progressHandler .sendProgressMessage (bytesWritten , totalSize );
271
+ }
272
+
273
+ // Close the Base64 output stream.
274
+ AsyncHttpClient .silentCloseOutputStream (bos );
275
+
276
+ // End the meta data.
277
+ endMetaData (os );
278
+
279
+ // Safely close the input stream.
280
+ AsyncHttpClient .silentCloseInputStream (in );
281
+ }
282
+
283
+ private void writeMetaData (OutputStream os , String name , String contentType ) throws IOException {
284
+ // Send the streams's name.
285
+ os .write (STREAM_NAME );
286
+ os .write (':' );
287
+ os .write (escape (name ));
288
+ os .write (',' );
289
+
290
+ // Send the streams's content type.
291
+ os .write (STREAM_TYPE );
292
+ os .write (':' );
293
+ os .write (escape (contentType ));
294
+ os .write (',' );
295
+
296
+ // Prepare the file content's key.
297
+ os .write (STREAM_CONTENTS );
298
+ os .write (':' );
299
+ os .write ('"' );
300
+ }
301
+
302
+ private void endMetaData (OutputStream os ) throws IOException {
303
+ os .write ('"' );
304
+ }
305
+
256
306
// Curtosy of Simple-JSON: http://goo.gl/XoW8RF
257
307
// Changed a bit to suit our needs in this class.
258
308
static byte [] escape (String string ) {
@@ -310,11 +360,11 @@ static byte[] escape(String string) {
310
360
BUILDER .append ('"' );
311
361
312
362
try {
313
- return BUILDER .toString ().getBytes ();
363
+ return BUILDER .toString ().getBytes ();
314
364
} finally {
315
- // Empty the String buffer.
316
- // This is 20-30% faster than instantiating a new object.
317
- BUILDER .setLength (0 );
365
+ // Empty the String buffer.
366
+ // This is 20-30% faster than instantiating a new object.
367
+ BUILDER .setLength (0 );
318
368
}
319
369
}
320
370
}
0 commit comments