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