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