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:
JavaInputStreamAdaptor(JNIEnv * env,jobject js,jbyteArray ar)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 
read(void * buffer,size_t size)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 
isAtEnd() const57     virtual bool isAtEnd() const {
58         return fIsAtEnd;
59     }
60 
61 private:
doRead(void * buffer,size_t size)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 
doSkip(size_t size)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 
CreateJavaInputStreamAdaptor(JNIEnv * env,jobject stream,jbyteArray storage)139 SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
140                                        jbyteArray storage) {
141     return new JavaInputStreamAdaptor(env, stream, storage);
142 }
143 
144 
adaptor_to_mem_stream(SkStream * stream)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 
CopyJavaInputStream(JNIEnv * env,jobject stream,jbyteArray storage)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:
SkJavaOutputStream(JNIEnv * env,jobject stream,jbyteArray storage)183     SkJavaOutputStream(JNIEnv* env, jobject stream, jbyteArray storage)
184         : fEnv(env), fJavaOutputStream(stream), fJavaByteArray(storage), fBytesWritten(0) {
185         fCapacity = env->GetArrayLength(storage);
186     }
187 
bytesWritten() const188     virtual size_t bytesWritten() const {
189         return fBytesWritten;
190     }
191 
write(const void * buffer,size_t size)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 
flush()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 
CreateJavaOutputStreamAdaptor(JNIEnv * env,jobject stream,jbyteArray storage)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 
findClassCheck(JNIEnv * env,const char classname[])255 static jclass findClassCheck(JNIEnv* env, const char classname[]) {
256     jclass clazz = env->FindClass(classname);
257     SkASSERT(!env->ExceptionCheck());
258     return clazz;
259 }
260 
getMethodIDCheck(JNIEnv * env,jclass clazz,const char methodname[],const char type[])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 
register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv * env)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