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