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 9 static jmethodID gInputStream_readMethodID; 10 static jmethodID gInputStream_skipMethodID; 11 12 /** 13 * Wrapper for a Java InputStream. 14 */ 15 class JavaInputStreamAdaptor : public SkStream { 16 public: 17 JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar) 18 : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) { 19 SkASSERT(ar); 20 fCapacity = env->GetArrayLength(ar); 21 SkASSERT(fCapacity > 0); 22 fBytesRead = 0; 23 fIsAtEnd = false; 24 } 25 26 virtual size_t read(void* buffer, size_t size) { 27 JNIEnv* env = fEnv; 28 if (NULL == buffer) { 29 if (0 == size) { 30 return 0; 31 } else { 32 /* InputStream.skip(n) can return <=0 but still not be at EOF 33 If we see that value, we need to call read(), which will 34 block if waiting for more data, or return -1 at EOF 35 */ 36 size_t amountSkipped = 0; 37 do { 38 size_t amount = this->doSkip(size - amountSkipped); 39 if (0 == amount) { 40 char tmp; 41 amount = this->doRead(&tmp, 1); 42 if (0 == amount) { 43 // if read returned 0, we're at EOF 44 fIsAtEnd = true; 45 break; 46 } 47 } 48 amountSkipped += amount; 49 } while (amountSkipped < size); 50 return amountSkipped; 51 } 52 } 53 return this->doRead(buffer, size); 54 } 55 56 virtual bool isAtEnd() const { 57 return fIsAtEnd; 58 } 59 60 private: 61 size_t doRead(void* buffer, size_t size) { 62 JNIEnv* env = fEnv; 63 size_t bytesRead = 0; 64 // read the bytes 65 do { 66 jint requested = 0; 67 if (size > static_cast<size_t>(fCapacity)) { 68 requested = fCapacity; 69 } else { 70 // This is safe because requested is clamped to (jint) 71 // fCapacity. 72 requested = static_cast<jint>(size); 73 } 74 75 jint n = env->CallIntMethod(fJavaInputStream, 76 gInputStream_readMethodID, fJavaByteArray, 0, requested); 77 if (env->ExceptionCheck()) { 78 env->ExceptionDescribe(); 79 env->ExceptionClear(); 80 SkDebugf("---- read threw an exception\n"); 81 // Consider the stream to be at the end, since there was an error. 82 fIsAtEnd = true; 83 return 0; 84 } 85 86 if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications. 87 fIsAtEnd = true; 88 break; // eof 89 } 90 91 env->GetByteArrayRegion(fJavaByteArray, 0, n, 92 reinterpret_cast<jbyte*>(buffer)); 93 if (env->ExceptionCheck()) { 94 env->ExceptionDescribe(); 95 env->ExceptionClear(); 96 SkDebugf("---- read:GetByteArrayRegion threw an exception\n"); 97 // The error was not with the stream itself, but consider it to be at the 98 // end, since we do not have a way to recover. 99 fIsAtEnd = true; 100 return 0; 101 } 102 103 buffer = (void*)((char*)buffer + n); 104 bytesRead += n; 105 size -= n; 106 fBytesRead += n; 107 } while (size != 0); 108 109 return bytesRead; 110 } 111 112 size_t doSkip(size_t size) { 113 JNIEnv* env = fEnv; 114 115 jlong skipped = env->CallLongMethod(fJavaInputStream, 116 gInputStream_skipMethodID, (jlong)size); 117 if (env->ExceptionCheck()) { 118 env->ExceptionDescribe(); 119 env->ExceptionClear(); 120 SkDebugf("------- skip threw an exception\n"); 121 return 0; 122 } 123 if (skipped < 0) { 124 skipped = 0; 125 } 126 127 return (size_t)skipped; 128 } 129 130 JNIEnv* fEnv; 131 jobject fJavaInputStream; // the caller owns this object 132 jbyteArray fJavaByteArray; // the caller owns this object 133 jint fCapacity; 134 size_t fBytesRead; 135 bool fIsAtEnd; 136 }; 137 138 SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, 139 jbyteArray storage) { 140 return new JavaInputStreamAdaptor(env, stream, storage); 141 } 142 143 144 static SkMemoryStream* adaptor_to_mem_stream(SkStream* stream) { 145 SkASSERT(stream != NULL); 146 size_t bufferSize = 4096; 147 size_t streamLen = 0; 148 size_t len; 149 char* data = (char*)sk_malloc_throw(bufferSize); 150 151 while ((len = stream->read(data + streamLen, 152 bufferSize - streamLen)) != 0) { 153 streamLen += len; 154 if (streamLen == bufferSize) { 155 bufferSize *= 2; 156 data = (char*)sk_realloc_throw(data, bufferSize); 157 } 158 } 159 data = (char*)sk_realloc_throw(data, streamLen); 160 161 SkMemoryStream* streamMem = new SkMemoryStream(); 162 streamMem->setMemoryOwned(data, streamLen); 163 return streamMem; 164 } 165 166 SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream, 167 jbyteArray storage) { 168 SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage)); 169 if (NULL == adaptor.get()) { 170 return NULL; 171 } 172 return adaptor_to_mem_stream(adaptor.get()); 173 } 174 175 /////////////////////////////////////////////////////////////////////////////// 176 177 static jmethodID gOutputStream_writeMethodID; 178 static jmethodID gOutputStream_flushMethodID; 179 180 class SkJavaOutputStream : public SkWStream { 181 public: 182 SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage) 183 : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage), fBytesWritten(0) { 184 fCapacity = env->GetArrayLength(storage); 185 } 186 187 virtual size_t bytesWritten() const { 188 return fBytesWritten; 189 } 190 191 virtual bool write(const void* buffer, size_t size) { 192 JNIEnv* env = fEnv; 193 jbyteArray storage = fJavaByteArray; 194 195 while (size > 0) { 196 jint requested = 0; 197 if (size > static_cast<size_t>(fCapacity)) { 198 requested = fCapacity; 199 } else { 200 // This is safe because requested is clamped to (jint) 201 // fCapacity. 202 requested = static_cast<jint>(size); 203 } 204 205 env->SetByteArrayRegion(storage, 0, requested, 206 reinterpret_cast<const jbyte*>(buffer)); 207 if (env->ExceptionCheck()) { 208 env->ExceptionDescribe(); 209 env->ExceptionClear(); 210 SkDebugf("--- write:SetByteArrayElements threw an exception\n"); 211 return false; 212 } 213 214 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID, 215 storage, 0, requested); 216 if (env->ExceptionCheck()) { 217 env->ExceptionDescribe(); 218 env->ExceptionClear(); 219 SkDebugf("------- write threw an exception\n"); 220 return false; 221 } 222 223 buffer = (void*)((char*)buffer + requested); 224 size -= requested; 225 fBytesWritten += requested; 226 } 227 return true; 228 } 229 230 virtual void flush() { 231 fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_flushMethodID); 232 } 233 234 private: 235 JNIEnv* fEnv; 236 jobject fJavaOutputStream; // the caller owns this object 237 jbyteArray fJavaByteArray; // the caller owns this object 238 jint fCapacity; 239 size_t fBytesWritten; 240 }; 241 242 SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, 243 jbyteArray storage) { 244 static bool gInited; 245 246 if (!gInited) { 247 248 gInited = true; 249 } 250 251 return new SkJavaOutputStream(env, stream, storage); 252 } 253 254 static jclass findClassCheck(JNIEnv* env, const char classname[]) { 255 jclass clazz = env->FindClass(classname); 256 SkASSERT(!env->ExceptionCheck()); 257 return clazz; 258 } 259 260 static jmethodID getMethodIDCheck(JNIEnv* env, jclass clazz, 261 const char methodname[], const char type[]) { 262 jmethodID id = env->GetMethodID(clazz, methodname, type); 263 SkASSERT(!env->ExceptionCheck()); 264 return id; 265 } 266 267 int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env) { 268 jclass inputStream_Clazz = findClassCheck(env, "java/io/InputStream"); 269 gInputStream_readMethodID = getMethodIDCheck(env, inputStream_Clazz, "read", "([BII)I"); 270 gInputStream_skipMethodID = getMethodIDCheck(env, inputStream_Clazz, "skip", "(J)J"); 271 272 jclass outputStream_Clazz = findClassCheck(env, "java/io/OutputStream"); 273 gOutputStream_writeMethodID = getMethodIDCheck(env, outputStream_Clazz, "write", "([BII)V"); 274 gOutputStream_flushMethodID = getMethodIDCheck(env, outputStream_Clazz, "flush", "()V"); 275 276 return 0; 277 } 278