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