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