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