OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / gnu / gcj / convert / natIconv.cc
1 // natIconv.cc -- Java side of iconv() reader.
2
3 /* Copyright (C) 2000, 2001, 2003, 2006  Free Software Foundation
4
5    This file is part of libgcj.
6
7 This software is copyrighted work licensed under the terms of the
8 Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
9 details.  */
10
11 /* Author: Tom Tromey <tromey@redhat.com>.  */
12
13 #include <config.h>
14
15 #include <gcj/cni.h>
16 #include <jvm.h>
17
18 #include <gnu/gcj/convert/Input_iconv.h>
19 #include <gnu/gcj/convert/Output_iconv.h>
20 #include <java/io/CharConversionException.h>
21 #include <java/io/UnsupportedEncodingException.h>
22
23 #include <errno.h>
24
25 #ifdef HAVE_ICONV
26 #include <iconv.h>
27
28 template<typename T>
29 static inline size_t
30 iconv_adapter (size_t (*iconv_f) (iconv_t, T, size_t *, char **, size_t *),
31                iconv_t handle, char **inbuf, size_t *inavail,
32                char **outbuf, size_t *outavail)
33 {
34   return (*iconv_f) (handle, (T) inbuf, inavail, outbuf, outavail);
35 }
36
37 #endif
38
39 void
40 gnu::gcj::convert::Input_iconv::init (jstring encoding)
41 {
42 #ifdef HAVE_ICONV
43   jsize len = _Jv_GetStringUTFLength (encoding);
44   char buffer[len + 1];
45   _Jv_GetStringUTFRegion (encoding, 0, encoding->length(), buffer);
46   buffer[len] = '\0';
47
48   iconv_t h = iconv_open ("UCS-2", buffer);
49   if (h == (iconv_t) -1)
50     throw new ::java::io::UnsupportedEncodingException (encoding);
51
52   JvAssert (h != NULL);
53   handle = reinterpret_cast<gnu::gcj::RawData *> (h);
54 #else /* HAVE_ICONV */
55   // If no iconv, just throw an exception.
56   throw new ::java::io::UnsupportedEncodingException (encoding);
57 #endif /* HAVE_ICONV */
58 }
59
60 void
61 gnu::gcj::convert::Input_iconv::finalize (void)
62 {
63 #ifdef HAVE_ICONV
64   if (handle != NULL)
65     {
66       iconv_close ((iconv_t) handle);
67       handle = NULL;
68     }
69 #endif /* HAVE_ICONV */
70 }
71
72 jint
73 gnu::gcj::convert::Input_iconv::read (jcharArray outbuffer,
74                                       jint outpos, jint count)
75 {
76 #ifdef HAVE_ICONV
77   jbyte *bytes = elements (inbuffer);
78   jchar *out = elements (outbuffer);
79   size_t inavail = inlength - inpos;
80   size_t old_in = inavail;
81   size_t outavail = count * sizeof (jchar);
82   size_t old_out = outavail;
83
84   char *inbuf = (char *) &bytes[inpos];
85   char *outbuf = (char *) &out[outpos];
86
87   size_t r = iconv_adapter (iconv, (iconv_t) handle,
88                             &inbuf, &inavail,
89                             &outbuf, &outavail);
90
91   if (r == (size_t) -1)
92     {
93       // If we see EINVAL then there is an incomplete sequence at the
94       // end of the input buffer.  If we see E2BIG then we ran out of
95       // space in the output buffer.  However, in both these cases
96       // some conversion might have taken place.  So we fall through
97       // to the normal case.
98       if (errno != EINVAL && errno != E2BIG)
99         throw new ::java::io::CharConversionException ();
100     }
101
102   if (iconv_byte_swap)
103     {
104       size_t max = (old_out - outavail) / sizeof (jchar);
105       for (size_t i = 0; i < max; ++i)
106         {
107           // Byte swap.
108           jchar c = (((out[outpos + i] & 0xff) << 8)
109                      | ((out[outpos + i] >> 8) & 0xff));
110           outbuf[i] = c;
111         }
112     }
113
114   inpos += old_in - inavail;
115   return (old_out - outavail) / sizeof (jchar);
116 #else /* HAVE_ICONV */
117   return -1;
118 #endif /* HAVE_ICONV */
119 }
120
121 void
122 gnu::gcj::convert::Input_iconv::done ()
123 {
124 #ifdef HAVE_ICONV
125   // 50 bytes should be enough for any reset sequence.
126   size_t avail = 50;
127   char tmp[avail];
128   char *p = tmp;
129   // Calling iconv() with a NULL INBUF pointer will cause iconv() to
130   // switch to its initial state.  We don't care about the output that
131   // might be generated in that situation.
132   iconv_adapter (iconv, (iconv_t) handle, NULL, NULL, &p, &avail);
133   BytesToUnicode::done ();
134 #else /* HAVE_ICONV */
135   // If no iconv, do nothing
136 #endif /* HAVE_ICONV */
137 }
138
139 void
140 gnu::gcj::convert::Output_iconv::init (jstring encoding)
141 {
142 #ifdef HAVE_ICONV
143   jsize len = _Jv_GetStringUTFLength (encoding);
144   char buffer[len + 1];
145   _Jv_GetStringUTFRegion (encoding, 0, encoding->length(), buffer);
146   buffer[len] = '\0';
147
148   iconv_t h = iconv_open (buffer, "UCS-2");
149   if (h == (iconv_t) -1)
150     throw new ::java::io::UnsupportedEncodingException (encoding);
151
152   JvAssert (h != NULL);
153   handle = reinterpret_cast<gnu::gcj::RawData *> (h);
154 #else /* HAVE_ICONV */
155   // If no iconv, just throw an exception.
156   throw new ::java::io::UnsupportedEncodingException (encoding);
157 #endif /* HAVE_ICONV */
158 }
159
160 void
161 gnu::gcj::convert::Output_iconv::finalize (void)
162 {
163 #ifdef HAVE_ICONV
164   if (handle != NULL)
165     {
166       iconv_close ((iconv_t) handle);
167       handle = NULL;
168     }
169 #endif /* HAVE_ICONV */
170 }
171
172 jint
173 gnu::gcj::convert::Output_iconv::write (jcharArray inbuffer,
174                                         jint inpos, jint inlength)
175 {
176 #ifdef HAVE_ICONV
177   jchar *chars = elements (inbuffer);
178   jbyte *out = elements (buf);
179   jchar *temp_buffer = NULL;
180
181   size_t inavail = inlength * sizeof (jchar);
182   size_t old_in = inavail;
183
184   size_t outavail = buf->length - count;
185   size_t old_out = outavail;
186
187   char *inbuf = (char *) &chars[inpos];
188   char *outbuf = (char *) &out[count];
189
190   if (iconv_byte_swap)
191     {
192       // Ugly performance penalty -- don't use losing systems!
193       temp_buffer = (jchar *) _Jv_Malloc (inlength * sizeof (jchar));
194       for (int i = 0; i < inlength; ++i)
195         {
196           // Byte swap.
197           jchar c = (((chars[inpos + i] & 0xff) << 8)
198                      | ((chars[inpos + i] >> 8) & 0xff));
199           temp_buffer[i] = c;
200         }
201       inbuf = (char *) temp_buffer;
202     }
203
204   size_t loop_old_in = old_in;
205   while (1)
206     {
207       size_t r = iconv_adapter (iconv, (iconv_t) handle,
208                                 &inbuf, &inavail,
209                                 &outbuf, &outavail);
210       if (r == (size_t) -1)
211         {
212           if (errno == EINVAL)
213             {
214               // Incomplete byte sequence at the end of the input
215               // buffer.  This shouldn't be able to happen here.
216               break;
217             }
218           else if (errno == E2BIG)
219             {
220               // Output buffer is too small.
221               break;
222             }
223           else if (errno == EILSEQ || inavail == loop_old_in)
224             {
225               // Untranslatable sequence.  Since glibc 2.1.3 doesn't
226               // properly set errno, we also assume that this is what
227               // is happening if no conversions took place.  (This can
228               // be a bogus assumption if in fact the output buffer is
229               // too small.)  We skip the first character and try
230               // again.
231               inavail -= 2;
232               if (inavail == 0)
233                 break;
234               loop_old_in -= 2;
235               inbuf += 2;
236             }
237         }
238       else
239         break;
240     }
241
242   if (temp_buffer != NULL)
243     _Jv_Free (temp_buffer);
244
245   count += old_out - outavail;
246   return (old_in - inavail) / sizeof (jchar);
247 #else /* HAVE_ICONV */
248   return -1;
249 #endif /* HAVE_ICONV */
250 }
251
252 jboolean
253 gnu::gcj::convert::IOConverter::iconv_init (void)
254 {
255   // Some versions of iconv() always return their UCS-2 results in
256   // big-endian order, and they also require UCS-2 inputs to be in
257   // big-endian order.  For instance, glibc 2.1.3 does this.  If the
258   // UTF-8=>UCS-2 iconv converter has this feature, then we assume
259   // that all UCS-2 converters do.  (This might not be the best
260   // heuristic, but is is all we've got.)
261   jboolean result = false;
262 #ifdef HAVE_ICONV
263   iconv_t handle = iconv_open ("UCS-2", "UTF-8");
264   if (handle != (iconv_t) -1)
265     {
266       jchar c;
267       unsigned char in[3];
268       char *inp, *outp;
269       size_t inc, outc, r;
270
271       // This is the UTF-8 encoding of \ufeff.
272       in[0] = 0xef;
273       in[1] = 0xbb;
274       in[2] = 0xbf;
275
276       inp = (char *) in;
277       inc = 3;
278       outp = (char *) &c;
279       outc = 2;
280
281       r = iconv_adapter (iconv, handle, &inp, &inc, &outp, &outc);
282       // Conversion must be complete for us to use the result.
283       if (r != (size_t) -1 && inc == 0 && outc == 0)
284         result = (c != 0xfeff);
285
286       // Release iconv handle.
287       iconv_close (handle);
288     }
289 #endif /* HAVE_ICONV */
290   return result;
291 }
292
293 void
294 gnu::gcj::convert::Output_iconv::done ()
295 {
296 #ifdef HAVE_ICONV
297   // 50 bytes should be enough for any reset sequence.
298   size_t avail = 50;
299   char tmp[avail];
300   char *p = tmp;
301   // Calling iconv() with a NULL INBUF pointer will cause iconv() to
302   // switch to its initial state.  We don't care about the output that
303   // might be generated in that situation.
304   iconv_adapter (iconv, (iconv_t) handle, NULL, NULL, &p, &avail);
305   UnicodeToBytes::done ();
306 #else /* HAVE_ICONV */
307   // If no iconv, do nothing
308 #endif /* HAVE_ICONV */
309 }