CreateJavaOutputStreamAdaptor.cpp revision ca32021b43f326af7d3f4ae041f8db297f98a518
1#include "CreateJavaOutputStreamAdaptor.h" 2#include "JNIHelp.h" 3#include "SkData.h" 4#include "SkRefCnt.h" 5#include "SkStream.h" 6#include "SkTypes.h" 7#include "Utils.h" 8#include <androidfw/Asset.h> 9 10#define RETURN_NULL_IF_NULL(value) \ 11 do { if (!(value)) { SkASSERT(0); return NULL; } } while (false) 12 13#define RETURN_ZERO_IF_NULL(value) \ 14 do { if (!(value)) { SkASSERT(0); return 0; } } while (false) 15 16static jmethodID gInputStream_resetMethodID; 17static jmethodID gInputStream_markMethodID; 18static jmethodID gInputStream_markSupportedMethodID; 19static jmethodID gInputStream_readMethodID; 20static jmethodID gInputStream_skipMethodID; 21 22class RewindableJavaStream; 23 24/** 25 * Non-rewindable wrapper for a Java InputStream. 26 */ 27class JavaInputStreamAdaptor : public SkStream { 28public: 29 JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar) 30 : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) { 31 SkASSERT(ar); 32 fCapacity = env->GetArrayLength(ar); 33 SkASSERT(fCapacity > 0); 34 fBytesRead = 0; 35 fIsAtEnd = false; 36 } 37 38 virtual size_t read(void* buffer, size_t size) { 39 JNIEnv* env = fEnv; 40 if (NULL == buffer) { 41 if (0 == size) { 42 return 0; 43 } else { 44 /* InputStream.skip(n) can return <=0 but still not be at EOF 45 If we see that value, we need to call read(), which will 46 block if waiting for more data, or return -1 at EOF 47 */ 48 size_t amountSkipped = 0; 49 do { 50 size_t amount = this->doSkip(size - amountSkipped); 51 if (0 == amount) { 52 char tmp; 53 amount = this->doRead(&tmp, 1); 54 if (0 == amount) { 55 // if read returned 0, we're at EOF 56 fIsAtEnd = true; 57 break; 58 } 59 } 60 amountSkipped += amount; 61 } while (amountSkipped < size); 62 return amountSkipped; 63 } 64 } 65 return this->doRead(buffer, size); 66 } 67 68 virtual bool isAtEnd() const { 69 return fIsAtEnd; 70 } 71 72private: 73 // Does not override rewind, since a JavaInputStreamAdaptor's interface 74 // does not support rewinding. RewindableJavaStream, which is a friend, 75 // will be able to call this method to rewind. 76 bool doRewind() { 77 JNIEnv* env = fEnv; 78 79 fBytesRead = 0; 80 fIsAtEnd = false; 81 82 env->CallVoidMethod(fJavaInputStream, gInputStream_resetMethodID); 83 if (env->ExceptionCheck()) { 84 env->ExceptionDescribe(); 85 env->ExceptionClear(); 86 SkDebugf("------- reset threw an exception\n"); 87 return false; 88 } 89 return true; 90 } 91 92 size_t doRead(void* buffer, size_t size) { 93 JNIEnv* env = fEnv; 94 size_t bytesRead = 0; 95 // read the bytes 96 do { 97 size_t requested = size; 98 if (requested > fCapacity) 99 requested = fCapacity; 100 101 jint n = env->CallIntMethod(fJavaInputStream, 102 gInputStream_readMethodID, fJavaByteArray, 0, requested); 103 if (env->ExceptionCheck()) { 104 env->ExceptionDescribe(); 105 env->ExceptionClear(); 106 SkDebugf("---- read threw an exception\n"); 107 return 0; 108 } 109 110 if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications. 111 fIsAtEnd = true; 112 break; // eof 113 } 114 115 env->GetByteArrayRegion(fJavaByteArray, 0, n, 116 reinterpret_cast<jbyte*>(buffer)); 117 if (env->ExceptionCheck()) { 118 env->ExceptionDescribe(); 119 env->ExceptionClear(); 120 SkDebugf("---- read:GetByteArrayRegion threw an exception\n"); 121 return 0; 122 } 123 124 buffer = (void*)((char*)buffer + n); 125 bytesRead += n; 126 size -= n; 127 fBytesRead += n; 128 } while (size != 0); 129 130 return bytesRead; 131 } 132 133 size_t doSkip(size_t size) { 134 JNIEnv* env = fEnv; 135 136 jlong skipped = env->CallLongMethod(fJavaInputStream, 137 gInputStream_skipMethodID, (jlong)size); 138 if (env->ExceptionCheck()) { 139 env->ExceptionDescribe(); 140 env->ExceptionClear(); 141 SkDebugf("------- skip threw an exception\n"); 142 return 0; 143 } 144 if (skipped < 0) { 145 skipped = 0; 146 } 147 148 return (size_t)skipped; 149 } 150 151 JNIEnv* fEnv; 152 jobject fJavaInputStream; // the caller owns this object 153 jbyteArray fJavaByteArray; // the caller owns this object 154 size_t fCapacity; 155 size_t fBytesRead; 156 bool fIsAtEnd; 157 158 // Allows access to doRewind and fBytesRead. 159 friend class RewindableJavaStream; 160}; 161 162SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream, 163 jbyteArray storage) { 164 static bool gInited; 165 166 if (!gInited) { 167 jclass inputStream_Clazz = env->FindClass("java/io/InputStream"); 168 RETURN_NULL_IF_NULL(inputStream_Clazz); 169 170 gInputStream_resetMethodID = env->GetMethodID(inputStream_Clazz, 171 "reset", "()V"); 172 gInputStream_markMethodID = env->GetMethodID(inputStream_Clazz, 173 "mark", "(I)V"); 174 gInputStream_markSupportedMethodID = env->GetMethodID(inputStream_Clazz, 175 "markSupported", "()Z"); 176 gInputStream_readMethodID = env->GetMethodID(inputStream_Clazz, 177 "read", "([BII)I"); 178 gInputStream_skipMethodID = env->GetMethodID(inputStream_Clazz, 179 "skip", "(J)J"); 180 181 RETURN_NULL_IF_NULL(gInputStream_resetMethodID); 182 RETURN_NULL_IF_NULL(gInputStream_markMethodID); 183 RETURN_NULL_IF_NULL(gInputStream_markSupportedMethodID); 184 RETURN_NULL_IF_NULL(gInputStream_readMethodID); 185 RETURN_NULL_IF_NULL(gInputStream_skipMethodID); 186 187 gInited = true; 188 } 189 190 return new JavaInputStreamAdaptor(env, stream, storage); 191} 192 193static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) { 194 SkASSERT(adaptor != NULL); 195 SkDynamicMemoryWStream wStream; 196 const int bufferSize = 256 * 1024; // 256 KB, same as ViewStateSerializer. 197 uint8_t buffer[bufferSize]; 198 do { 199 size_t bytesRead = adaptor->read(buffer, bufferSize); 200 wStream.write(buffer, bytesRead); 201 } while (!adaptor->isAtEnd()); 202 SkAutoTUnref<SkData> data(wStream.copyToData()); 203 return new SkMemoryStream(data.get()); 204} 205 206SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream, 207 jbyteArray storage) { 208 SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage)); 209 if (NULL == adaptor.get()) { 210 return NULL; 211 } 212 return adaptor_to_mem_stream(adaptor.get()); 213} 214 215/** 216 * Wrapper for a Java InputStream which is rewindable and 217 * has a length. 218 */ 219class RewindableJavaStream : public SkStreamRewindable { 220public: 221 // RewindableJavaStream takes ownership of adaptor. 222 RewindableJavaStream(JavaInputStreamAdaptor* adaptor, size_t length) 223 : fAdaptor(adaptor) 224 , fLength(length) { 225 SkASSERT(fAdaptor != NULL); 226 } 227 228 virtual ~RewindableJavaStream() { 229 fAdaptor->unref(); 230 } 231 232 virtual bool rewind() { 233 return fAdaptor->doRewind(); 234 } 235 236 virtual size_t read(void* buffer, size_t size) { 237 return fAdaptor->read(buffer, size); 238 } 239 240 virtual bool isAtEnd() const { 241 return fAdaptor->isAtEnd(); 242 } 243 244 virtual size_t getLength() const { 245 return fLength; 246 } 247 248 virtual bool hasLength() const { 249 return true; 250 } 251 252 virtual SkStreamRewindable* duplicate() const { 253 // Duplicating this stream requires rewinding and 254 // reading, which modify this Stream (and could 255 // fail, leaving this one invalid). 256 SkASSERT(false); 257 return NULL; 258 } 259 260private: 261 JavaInputStreamAdaptor* fAdaptor; 262 const size_t fLength; 263}; 264 265/** 266 * If jstream is a ByteArrayInputStream, return its remaining length. Otherwise 267 * return 0. 268 */ 269static size_t get_length_from_byte_array_stream(JNIEnv* env, jobject jstream) { 270 static jclass byteArrayInputStream_Clazz; 271 static jfieldID countField; 272 static jfieldID posField; 273 274 byteArrayInputStream_Clazz = env->FindClass("java/io/ByteArrayInputStream"); 275 RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz); 276 277 countField = env->GetFieldID(byteArrayInputStream_Clazz, "count", "I"); 278 RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz); 279 posField = env->GetFieldID(byteArrayInputStream_Clazz, "pos", "I"); 280 RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz); 281 282 if (env->IsInstanceOf(jstream, byteArrayInputStream_Clazz)) { 283 // Return the remaining length, to keep the same behavior of using the rest of the 284 // stream. 285 return env->GetIntField(jstream, countField) - env->GetIntField(jstream, posField); 286 } 287 return 0; 288} 289 290/** 291 * If jstream is a class that has a length, return it. Otherwise 292 * return 0. 293 * Only checks for a set of subclasses. 294 */ 295static size_t get_length_if_supported(JNIEnv* env, jobject jstream) { 296 size_t len = get_length_from_byte_array_stream(env, jstream); 297 if (len > 0) { 298 return len; 299 } 300 return 0; 301} 302 303SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream, 304 jbyteArray storage) { 305 SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage)); 306 if (NULL == adaptor.get()) { 307 return NULL; 308 } 309 310 const size_t length = get_length_if_supported(env, stream); 311 if (length > 0 && env->CallBooleanMethod(stream, gInputStream_markSupportedMethodID)) { 312 // Set the readLimit for mark to the end of the stream, so it can 313 // be rewound regardless of how much has been read. 314 env->CallVoidMethod(stream, gInputStream_markMethodID, length); 315 // RewindableJavaStream will unref adaptor when it is destroyed. 316 return new RewindableJavaStream(static_cast<JavaInputStreamAdaptor*>(adaptor.detach()), 317 length); 318 } 319 320 return adaptor_to_mem_stream(adaptor.get()); 321} 322 323android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject jstream) { 324 static jclass assetInputStream_Clazz; 325 static jmethodID getAssetIntMethodID; 326 327 assetInputStream_Clazz = env->FindClass("android/content/res/AssetManager$AssetInputStream"); 328 RETURN_NULL_IF_NULL(assetInputStream_Clazz); 329 330 getAssetIntMethodID = env->GetMethodID(assetInputStream_Clazz, "getAssetInt", "()I"); 331 RETURN_NULL_IF_NULL(getAssetIntMethodID); 332 333 if (!env->IsInstanceOf(jstream, assetInputStream_Clazz)) { 334 return NULL; 335 } 336 337 jint jasset = env->CallIntMethod(jstream, getAssetIntMethodID); 338 android::Asset* a = reinterpret_cast<android::Asset*>(jasset); 339 if (NULL == a) { 340 jniThrowNullPointerException(env, "NULL native asset"); 341 return NULL; 342 } 343 return new android::AssetStreamAdaptor(a); 344} 345 346/////////////////////////////////////////////////////////////////////////////// 347 348static jmethodID gOutputStream_writeMethodID; 349static jmethodID gOutputStream_flushMethodID; 350 351class SkJavaOutputStream : public SkWStream { 352public: 353 SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage) 354 : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage) { 355 fCapacity = env->GetArrayLength(storage); 356 } 357 358 virtual bool write(const void* buffer, size_t size) { 359 JNIEnv* env = fEnv; 360 jbyteArray storage = fJavaByteArray; 361 362 while (size > 0) { 363 size_t requested = size; 364 if (requested > fCapacity) { 365 requested = fCapacity; 366 } 367 368 env->SetByteArrayRegion(storage, 0, requested, 369 reinterpret_cast<const jbyte*>(buffer)); 370 if (env->ExceptionCheck()) { 371 env->ExceptionDescribe(); 372 env->ExceptionClear(); 373 SkDebugf("--- write:SetByteArrayElements threw an exception\n"); 374 return false; 375 } 376 377 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID, 378 storage, 0, requested); 379 if (env->ExceptionCheck()) { 380 env->ExceptionDescribe(); 381 env->ExceptionClear(); 382 SkDebugf("------- write threw an exception\n"); 383 return false; 384 } 385 386 buffer = (void*)((char*)buffer + requested); 387 size -= requested; 388 } 389 return true; 390 } 391 392 virtual void flush() { 393 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID); 394 } 395 396private: 397 JNIEnv* fEnv; 398 jobject fJavaOutputStream; // the caller owns this object 399 jbyteArray fJavaByteArray; // the caller owns this object 400 size_t fCapacity; 401}; 402 403SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, 404 jbyteArray storage) { 405 static bool gInited; 406 407 if (!gInited) { 408 jclass outputStream_Clazz = env->FindClass("java/io/OutputStream"); 409 RETURN_NULL_IF_NULL(outputStream_Clazz); 410 411 gOutputStream_writeMethodID = env->GetMethodID(outputStream_Clazz, 412 "write", "([BII)V"); 413 RETURN_NULL_IF_NULL(gOutputStream_writeMethodID); 414 gOutputStream_flushMethodID = env->GetMethodID(outputStream_Clazz, 415 "flush", "()V"); 416 RETURN_NULL_IF_NULL(gOutputStream_flushMethodID); 417 418 gInited = true; 419 } 420 421 return new SkJavaOutputStream(env, stream, storage); 422} 423