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