CreateJavaOutputStreamAdaptor.cpp revision c7797525084ba0ea441e394aa0a2ba35d6ff3320
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* CreateJavaInputStreamAdaptor(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 193 194static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) { 195 SkASSERT(adaptor != NULL); 196 SkDynamicMemoryWStream wStream; 197 const int bufferSize = 256 * 1024; // 256 KB, same as ViewStateSerializer. 198 uint8_t buffer[bufferSize]; 199 do { 200 size_t bytesRead = adaptor->read(buffer, bufferSize); 201 wStream.write(buffer, bytesRead); 202 } while (!adaptor->isAtEnd()); 203 SkAutoTUnref<SkData> data(wStream.copyToData()); 204 return new SkMemoryStream(data.get()); 205} 206 207SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, 208 jbyteArray storage) { 209 SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage)); 210 if (NULL == adaptor.get()) { 211 return NULL; 212 } 213 return adaptor_to_mem_stream(adaptor.get()); 214} 215 216/** 217 * Wrapper for a Java InputStream which is rewindable and 218 * has a length. 219 */ 220class RewindableJavaStream : public SkStreamRewindable { 221public: 222 // RewindableJavaStream takes ownership of adaptor. 223 RewindableJavaStream(JavaInputStreamAdaptor* adaptor, size_t length) 224 : fAdaptor(adaptor) 225 , fLength(length) { 226 SkASSERT(fAdaptor != NULL); 227 } 228 229 virtual ~RewindableJavaStream() { 230 fAdaptor->unref(); 231 } 232 233 virtual bool rewind() { 234 return fAdaptor->doRewind(); 235 } 236 237 virtual size_t read(void* buffer, size_t size) { 238 return fAdaptor->read(buffer, size); 239 } 240 241 virtual bool isAtEnd() const { 242 return fAdaptor->isAtEnd(); 243 } 244 245 virtual size_t getLength() const { 246 return fLength; 247 } 248 249 virtual bool hasLength() const { 250 return true; 251 } 252 253 virtual SkStreamRewindable* duplicate() const { 254 // Duplicating this stream requires rewinding and 255 // reading, which modify this Stream (and could 256 // fail, leaving this one invalid). 257 SkASSERT(false); 258 return NULL; 259 } 260 261private: 262 JavaInputStreamAdaptor* fAdaptor; 263 const size_t fLength; 264}; 265 266/** 267 * If jstream is a ByteArrayInputStream, return its remaining length. Otherwise 268 * return 0. 269 */ 270static size_t get_length_from_byte_array_stream(JNIEnv* env, jobject jstream) { 271 static jclass byteArrayInputStream_Clazz; 272 static jfieldID countField; 273 static jfieldID posField; 274 275 byteArrayInputStream_Clazz = env->FindClass("java/io/ByteArrayInputStream"); 276 RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz); 277 278 countField = env->GetFieldID(byteArrayInputStream_Clazz, "count", "I"); 279 RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz); 280 posField = env->GetFieldID(byteArrayInputStream_Clazz, "pos", "I"); 281 RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz); 282 283 if (env->IsInstanceOf(jstream, byteArrayInputStream_Clazz)) { 284 // Return the remaining length, to keep the same behavior of using the rest of the 285 // stream. 286 return env->GetIntField(jstream, countField) - env->GetIntField(jstream, posField); 287 } 288 return 0; 289} 290 291/** 292 * If jstream is a class that has a length, return it. Otherwise 293 * return 0. 294 * Only checks for a set of subclasses. 295 */ 296static size_t get_length_if_supported(JNIEnv* env, jobject jstream) { 297 size_t len = get_length_from_byte_array_stream(env, jstream); 298 if (len > 0) { 299 return len; 300 } 301 return 0; 302} 303 304SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream, 305 jbyteArray storage) { 306 SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage)); 307 if (NULL == adaptor.get()) { 308 return NULL; 309 } 310 311 const size_t length = get_length_if_supported(env, stream); 312 if (length > 0 && env->CallBooleanMethod(stream, gInputStream_markSupportedMethodID)) { 313 // Set the readLimit for mark to the end of the stream, so it can 314 // be rewound regardless of how much has been read. 315 env->CallVoidMethod(stream, gInputStream_markMethodID, length); 316 // RewindableJavaStream will unref adaptor when it is destroyed. 317 return new RewindableJavaStream(static_cast<JavaInputStreamAdaptor*>(adaptor.detach()), 318 length); 319 } 320 321 return adaptor_to_mem_stream(adaptor.get()); 322} 323 324android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject jstream) { 325 static jclass assetInputStream_Clazz; 326 static jmethodID getAssetIntMethodID; 327 328 assetInputStream_Clazz = env->FindClass("android/content/res/AssetManager$AssetInputStream"); 329 RETURN_NULL_IF_NULL(assetInputStream_Clazz); 330 331 getAssetIntMethodID = env->GetMethodID(assetInputStream_Clazz, "getAssetInt", "()I"); 332 RETURN_NULL_IF_NULL(getAssetIntMethodID); 333 334 if (!env->IsInstanceOf(jstream, assetInputStream_Clazz)) { 335 return NULL; 336 } 337 338 jint jasset = env->CallIntMethod(jstream, getAssetIntMethodID); 339 android::Asset* a = reinterpret_cast<android::Asset*>(jasset); 340 if (NULL == a) { 341 jniThrowNullPointerException(env, "NULL native asset"); 342 return NULL; 343 } 344 return new android::AssetStreamAdaptor(a); 345} 346 347/////////////////////////////////////////////////////////////////////////////// 348 349static jmethodID gOutputStream_writeMethodID; 350static jmethodID gOutputStream_flushMethodID; 351 352class SkJavaOutputStream : public SkWStream { 353public: 354 SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage) 355 : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage) { 356 fCapacity = env->GetArrayLength(storage); 357 } 358 359 virtual bool write(const void* buffer, size_t size) { 360 JNIEnv* env = fEnv; 361 jbyteArray storage = fJavaByteArray; 362 363 while (size > 0) { 364 size_t requested = size; 365 if (requested > fCapacity) { 366 requested = fCapacity; 367 } 368 369 env->SetByteArrayRegion(storage, 0, requested, 370 reinterpret_cast<const jbyte*>(buffer)); 371 if (env->ExceptionCheck()) { 372 env->ExceptionDescribe(); 373 env->ExceptionClear(); 374 SkDebugf("--- write:SetByteArrayElements threw an exception\n"); 375 return false; 376 } 377 378 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID, 379 storage, 0, requested); 380 if (env->ExceptionCheck()) { 381 env->ExceptionDescribe(); 382 env->ExceptionClear(); 383 SkDebugf("------- write threw an exception\n"); 384 return false; 385 } 386 387 buffer = (void*)((char*)buffer + requested); 388 size -= requested; 389 } 390 return true; 391 } 392 393 virtual void flush() { 394 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID); 395 } 396 397private: 398 JNIEnv* fEnv; 399 jobject fJavaOutputStream; // the caller owns this object 400 jbyteArray fJavaByteArray; // the caller owns this object 401 size_t fCapacity; 402}; 403 404SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, 405 jbyteArray storage) { 406 static bool gInited; 407 408 if (!gInited) { 409 jclass outputStream_Clazz = env->FindClass("java/io/OutputStream"); 410 RETURN_NULL_IF_NULL(outputStream_Clazz); 411 412 gOutputStream_writeMethodID = env->GetMethodID(outputStream_Clazz, 413 "write", "([BII)V"); 414 RETURN_NULL_IF_NULL(gOutputStream_writeMethodID); 415 gOutputStream_flushMethodID = env->GetMethodID(outputStream_Clazz, 416 "flush", "()V"); 417 RETURN_NULL_IF_NULL(gOutputStream_flushMethodID); 418 419 gInited = true; 420 } 421 422 return new SkJavaOutputStream(env, stream, storage); 423} 424