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