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