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