~vcs-imports/xena/trunk

« back to all changes in this revision

Viewing changes to ext/src/javahelp/jhMaster/JavaHelp/src/impl/com/sun/java/help/impl/.svn/text-base/XmlReader.java.svn-base

  • Committer: matthewoliver
  • Date: 2009-12-10 03:18:07 UTC
  • Revision ID: vcs-imports@canonical.com-20091210031807-l086qguzdlljtkl9
Merged Xena Testing into Xena Stable for the Xena 5 release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * @(#)XmlReader.java   1.5 06/10/30
 
3
 * 
 
4
 * Copyright (c) 2006 Sun Microsystems, Inc.  All Rights Reserved.
 
5
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 
6
 * 
 
7
 * This code is free software; you can redistribute it and/or modify it
 
8
 * under the terms of the GNU General Public License version 2 only, as
 
9
 * published by the Free Software Foundation.  Sun designates this
 
10
 * particular file as subject to the "Classpath" exception as provided
 
11
 * by Sun in the LICENSE file that accompanied this code.
 
12
 * 
 
13
 * This code is distributed in the hope that it will be useful, but WITHOUT
 
14
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 
15
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 
16
 * version 2 for more details (a copy is included in the LICENSE file that
 
17
 * accompanied this code).
 
18
 * 
 
19
 * You should have received a copy of the GNU General Public License version
 
20
 * 2 along with this work; if not, write to the Free Software Foundation,
 
21
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 
22
 * 
 
23
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 
24
 * CA 95054 USA or visit www.sun.com if you need additional information or
 
25
 * have any questions.
 
26
 */
 
27
/*
 
28
 * @(#) XmlReader.java 1.5 - last change made 10/30/06
 
29
 */
 
30
 
 
31
package com.sun.java.help.impl;
 
32
 
 
33
import java.io.*;
 
34
import java.net.URLConnection;
 
35
 
 
36
/**
 
37
 * This handles several XML-related tasks that normal java.io Readers
 
38
 * don't support, inluding use of IETF standard encoding names and
 
39
 * automatic detection of most XML encodings.  The former is needed
 
40
 * for interoperability; the latter is needed to conform with the XML
 
41
 * spec.  This class also optimizes reading some common encodings by
 
42
 * providing low-overhead unsynchronized Reader support.
 
43
 *
 
44
 * <P> Note that the autodetection facility should be used only on
 
45
 * data streams which have an unknown character encoding.  For example,
 
46
 * it should never be used on MIME text/xml entities.
 
47
 *
 
48
 * <P> Note that XML processors are only required to support UTF-8 and
 
49
 * UTF-16 character encodings.  Autodetection permits the underlying Java
 
50
 * implementation to provide support for many other encodings, such as
 
51
 * ISO-8859-5, Shift_JIS, EUC-JP, and ISO-2022-JP.
 
52
 *
 
53
 * @author David Brownell
 
54
 * @author Roger D. Brinkley
 
55
 * @version 1.24
 
56
 */
 
57
 
 
58
final public class XmlReader extends Reader
 
59
{
 
60
    private boolean             closed;
 
61
    private InputStreamReader   in;
 
62
 
 
63
    //
 
64
    // This class either handles reading itself, in common encodings
 
65
    // (US-ASCII, ISO-Latin-1, UTF-8) or delegates to another Reader.
 
66
    //
 
67
    // Autodetection requires reading/buffering part of the XML declaration,
 
68
    // then potentially switching _after entire characters are read_ to
 
69
    // delegate further operations to such a Reader.  The reader will get
 
70
    // no header (e.g. no UTF-16 Byte Order Mark).  This can work since,
 
71
    // XML declarations contain only ASCII characters, which are a subset
 
72
    // of many encodings (Shift_JIS, ISO-8859-*, EUC-*, ISO-2022-*, more).
 
73
    // 
 
74
    // It's got do this efficiently:  character I/O is solidly on the
 
75
    // critical path.  Synchronization isn't needed, and buffering must
 
76
    // as always be done only with care.  (So keep buffer length over
 
77
    // 2 Kbytes to avoid excess buffering, since most URL handlers stuff
 
78
    // a BufferedInputStream between here and the real data source.  That
 
79
    // class tries to be smart enough not to try buffering if you ask for
 
80
    // more data than it could buffer for you.)
 
81
    //
 
82
    private InputStream         raw;
 
83
    private byte                buffer [];
 
84
    private boolean             isASCII, isLatin1;
 
85
    private int                 offset, length;
 
86
 
 
87
    // 2nd half of UTF-8 surrogate pair
 
88
    private char                nextChar;
 
89
    
 
90
    private int                 switchover;
 
91
    private String              encodingAssigned;
 
92
 
 
93
    /**
 
94
     * Constructs the reader from a URLConnection. Uses the encoding 
 
95
     * specified in the HTTP header or autodetects
 
96
     * the encoding to use according to the heuristic specified
 
97
     * in the XML 1.0 recommendation.
 
98
     *
 
99
     * @param uc the URLConnection from which the reader is constructed
 
100
     * @exception IOException on error
 
101
     * @exception UnsupportedEncodingException when the input stream
 
102
     *  is not in an encoding which is supported; this is a fatal XML
 
103
     *  error.
 
104
     */
 
105
    public static Reader createReader (URLConnection uc) throws IOException
 
106
    {
 
107
        String encoding = getEncodingFromContentType(uc.getContentType());
 
108
        if (encoding == null) {
 
109
            return createReader (uc.getInputStream());
 
110
        }
 
111
        return createReader (uc.getInputStream(), encoding);
 
112
    }
 
113
 
 
114
    /**
 
115
     * Gets the encoding from the content type string.
 
116
     * If there is a charset definition specified as a parameter
 
117
     * of the content type specification, it will be used when
 
118
     * loading input streams using the associated XmlReader.
 
119
     * For example if the type is specified as 
 
120
     * <code>text/xml; charset=EUC-JP</code> the Reader will
 
121
     * use the <code>EUC-JP</code> charset for translating
 
122
     * to unicode.
 
123
     * 
 
124
     * @param type the non-null mime type for the content editing
 
125
     *   support.
 
126
     */
 
127
    private static String getEncodingFromContentType(String type) {
 
128
 
 
129
        debug ("type=" + type);
 
130
        // The type could have optional info is part of it,
 
131
        // for example some charset info.  We need to strip that
 
132
        // of and save it.
 
133
        if (type == null) {
 
134
            return null;
 
135
        }
 
136
        int parm = type.indexOf(";");
 
137
        if (parm > -1) {
 
138
            // Save the paramList.
 
139
            String paramList = type.substring(parm);
 
140
            // update the content type string.
 
141
            type = type.substring(0, parm).trim();
 
142
            if (type.compareTo("text/xml") == 0) {
 
143
                // Set the charset name from the paramlist
 
144
                return getCharsetFromContentTypeParameters(paramList);
 
145
            }
 
146
        }
 
147
        return null;
 
148
    }
 
149
 
 
150
    /**
 
151
     * This method get's the charset information specified as part
 
152
     * of the content type in the http header information.
 
153
     */
 
154
    private static String getCharsetFromContentTypeParameters(String paramlist) {
 
155
        String charset = null;
 
156
        try {
 
157
            // paramlist is handed to us with a leading ';', strip it.
 
158
            int semi = paramlist.indexOf(';');
 
159
            if (semi > -1 && semi < paramlist.length()-1) {
 
160
                paramlist = paramlist.substring(semi + 1);
 
161
            }
 
162
 
 
163
            if (paramlist.length() > 0) {
 
164
                // parse the paramlist into attr-value pairs & get the
 
165
                // charset pair's value
 
166
                HeaderParser hdrParser = new HeaderParser(paramlist);
 
167
                charset = hdrParser.findValue("charset");
 
168
                return charset;
 
169
            }
 
170
        }
 
171
        catch (IndexOutOfBoundsException e) {
 
172
            // malformed parameter list, use charset we have
 
173
        }
 
174
        catch (NullPointerException e) {
 
175
            // malformed parameter list, use charset we have
 
176
        }
 
177
        catch (Exception e) {
 
178
            // malformed parameter list, use charset we have; but complain
 
179
            System.err.println("Indexer.getCharsetFromContentTypeParameters failed on: " + paramlist);
 
180
            e.printStackTrace();
 
181
        }
 
182
        return charset;
 
183
    }
 
184
 
 
185
    /**
 
186
     * Constructs the reader from an input stream, autodetecting
 
187
     * the encoding to use according to the heuristic specified
 
188
     * in the XML 1.0 recommendation.
 
189
     *
 
190
     * @param in the input stream from which the reader is constructed
 
191
     * @exception IOException on error
 
192
     * @exception UnsupportedEncodingException when the input stream
 
193
     *  is not in an encoding which is supported; this is a fatal XML
 
194
     *  error.
 
195
     */
 
196
    public static Reader createReader (InputStream in) throws IOException
 
197
    {
 
198
        return new XmlReader (in);
 
199
    }
 
200
 
 
201
    /**
 
202
     * Creates a reader supporting the given encoding, mapping
 
203
     * from standard encoding names to ones that understood by
 
204
     * Java where necessary.
 
205
     *
 
206
     * @param in the input stream from which the reader is constructed
 
207
     * @param encoding the IETF standard name of the encoding to use;
 
208
     *  if null, autodetection is used.
 
209
     * @exception IOException on error
 
210
     * @exception UnsupportedEncodingException when the input stream
 
211
     *  is not in an encoding which is supported; this is a fatal XML
 
212
     *  error.
 
213
     */
 
214
    public static Reader createReader (InputStream in, String encoding)
 
215
    throws IOException
 
216
    {
 
217
        if (encoding == null)
 
218
            return new XmlReader (in);
 
219
 
 
220
        // UTF-16 == ISO-10646-UCS-2 plus surrogates.
 
221
        // The sun.io "Unicode" encoders/decoders don't check
 
222
        // for correctly paired surrogates, so they accept UTF-16.
 
223
 
 
224
        if ("UTF-16".equalsIgnoreCase (encoding)
 
225
                || "ISO-106460-UCS-2".equalsIgnoreCase (encoding))
 
226
            encoding = "Unicode";
 
227
        else if ("UTF-8".equalsIgnoreCase (encoding))
 
228
            return new XmlReader (in, "UTF-8");
 
229
        else if ("EUC-JP".equalsIgnoreCase (encoding))
 
230
            encoding = "EUCJIS";        // works on JDK 1.1 and 1.2
 
231
        else if (isAsciiName (encoding))
 
232
            return new XmlReader (in, "US-ASCII");
 
233
        else if (isLatinName (encoding))
 
234
            return new XmlReader (in, "ISO-8859-1");
 
235
 
 
236
        // XXX we should provide provide better "unsupported encoding"
 
237
        // diagnostics than this produces...
 
238
        return new InputStreamReader (in, encoding);
 
239
    }
 
240
 
 
241
    private XmlReader (InputStream in, String encoding)
 
242
    throws IOException
 
243
    {
 
244
        buffer = new byte [8 * 1024];
 
245
        length = 0;
 
246
        raw = in;
 
247
        if ("US-ASCII".equals (encoding))
 
248
            setASCII ();
 
249
        else if ("ISO-8859-1".equals (encoding))
 
250
            setLatin1 ();
 
251
        else if (!"UTF-8".equals (encoding))
 
252
            throw new UnsupportedEncodingException (encoding);
 
253
        else
 
254
            setUTF8 ();
 
255
    }
 
256
 
 
257
    private static boolean isAsciiName (String encoding)
 
258
    {
 
259
        return "US-ASCII".equalsIgnoreCase (encoding)
 
260
                || "ASCII".equalsIgnoreCase (encoding);
 
261
    }
 
262
 
 
263
    private static boolean isLatinName (String encoding)
 
264
    {
 
265
        return "ISO-8859-1".equalsIgnoreCase (encoding)
 
266
                || "Latin1".equalsIgnoreCase (encoding)
 
267
                || "8859_1".equalsIgnoreCase (encoding);
 
268
    }
 
269
 
 
270
    private void setASCII ()
 
271
    {
 
272
        encodingAssigned = "US-ASCII";
 
273
        isASCII = true;
 
274
        isLatin1 = false;
 
275
        offset = 0;
 
276
    }
 
277
 
 
278
    private void setLatin1 ()
 
279
    {
 
280
        encodingAssigned = "ISO-8859-1";
 
281
        isASCII = false;
 
282
        isLatin1 = true;
 
283
        offset = 0;
 
284
    }
 
285
 
 
286
    private void setUTF8 ()
 
287
    {
 
288
        encodingAssigned = "UTF-8";
 
289
        isASCII = false;
 
290
        isLatin1 = false;
 
291
        offset = 0;
 
292
    }
 
293
 
 
294
    /** Returns the (non)standard name of the encoding in use */
 
295
    public String getEncoding ()
 
296
    {
 
297
        return encodingAssigned;
 
298
    }
 
299
 
 
300
    private XmlReader (InputStream in) throws IOException
 
301
    {
 
302
        int                 c;
 
303
 
 
304
        //
 
305
        // Set up buffering ... we buffer at least the XML text
 
306
        // declaration (if it's there), and for some encodings we
 
307
        // manage bulk character I/O.  We can reset within this
 
308
        // buffer so long as we've not assigned the encoding.
 
309
        //
 
310
        raw = in;
 
311
        switchover = -1;
 
312
        buffer = new byte [8 * 1024];
 
313
        offset = length = 0;
 
314
        isLatin1 = true;
 
315
 
 
316
        //
 
317
        // See if we can figure out the character encoding used
 
318
        // in this file by peeking at the first few bytes.  If not,
 
319
        // we may be able to look at the whole XML declaration.
 
320
        //
 
321
        switch ((c = read ())) {
 
322
            case 0:
 
323
              // 00 3c 00 3f == illegal UTF-16 big-endian
 
324
              if ((c = read ()) == 0x3c
 
325
                      && (c = read ()) == 0x00
 
326
                      && (c = read ()) == 0x3f) {
 
327
                  setSwitchover ("UnicodeBig");
 
328
                  // no BOM to ignore
 
329
                  return;
 
330
              }
 
331
 
 
332
              // 00 00 00 3c == UCS-4 big endian
 
333
              // 00 00 3c 00 == UCS-4 unusual "2143" order
 
334
              // 00 3c 00 00 == UCS-4 unusual "3412" order
 
335
              // ... or something else!  note that only some parts
 
336
              // of UCS-4 work with UNICODE based systems.
 
337
              throw new UnsupportedEncodingException ("UCS-4 (?)");
 
338
 
 
339
            case '<':      // 0x3c: the most common cases!
 
340
              switch ((c = read ())) {
 
341
                // First character is '<'; could be XML without
 
342
                // an XML directive such as "<hello>", "<!-- ...",
 
343
                // and so on.  Default intelligently; the byte we
 
344
                // just read could be _part_ of a UTF-8 character!
 
345
                default:
 
346
                  break;
 
347
 
 
348
                // 3c 00 3f 00 == illegal UTF-16 little endian
 
349
                // 3c 00 00 00 == UCS-4 little endian
 
350
                case 0x00:
 
351
                  if (read () == 0x3f && read () == 0x00) {
 
352
                      setSwitchover ("UnicodeLittle");
 
353
                      // no BOM to ignore
 
354
                      return;
 
355
                  }
 
356
                  throw new UnsupportedEncodingException ("UCS-4");
 
357
 
 
358
                // 3c 3f 78 6d == ASCII and supersets '<?xm'
 
359
                case '?': 
 
360
                  if (read () != 'x' || read () != 'm'
 
361
                          || read () != 'l' || read () != ' ')
 
362
                      break;
 
363
                  //
 
364
                  // One of several encodings could be used:
 
365
                  // Shift-JIS, ASCII, UTF-8, ISO-8859-*, etc
 
366
                  //
 
367
                  guessEncoding ();
 
368
                  return;
 
369
              }
 
370
              break;
 
371
 
 
372
            // UTF-16 big-endian
 
373
            case 0xfe:
 
374
              if ((c = read ()) != 0xff)
 
375
                  break;
 
376
              setSwitchover ("UnicodeBig");
 
377
              offset = 2;       // skip BOM
 
378
              return;
 
379
 
 
380
            // UTF-16 little-endian
 
381
            case 0xff:
 
382
              if ((c = read ()) != 0xfe)
 
383
                  break;
 
384
              setSwitchover ("UnicodeLittle");
 
385
              offset = 2;       // skip BOM
 
386
              return;
 
387
 
 
388
            // EOF
 
389
            case -1:
 
390
              return;
 
391
 
 
392
            // default ... no XML declaration
 
393
            default:
 
394
              break;
 
395
        }
 
396
 
 
397
        //
 
398
        // If all else fails, assume XML without a declaration, and
 
399
        // using UTF-8 encoding.  We must be prepared to re-interpret
 
400
        // bytes we've already read as parts of UTF-8 characters, so
 
401
        // we can't use the "switchover" technique (which works only
 
402
        // "between" characters).
 
403
        //
 
404
        setUTF8 ();
 
405
    }
 
406
 
 
407
    // When the buffered input is done, switch to a reader
 
408
    // that can decode data in that encoding.  Only call this
 
409
    // routine after entire characters have been scanned.
 
410
    private void setSwitchover (String encoding) throws IOException
 
411
    {
 
412
        switchover = offset;
 
413
        encodingAssigned = encoding;
 
414
        offset = 0;
 
415
    }
 
416
 
 
417
    // we've consumed our buffer, now switch over to a reader
 
418
    // which will decode the rest of the (non-ASCII) input
 
419
    private void doSwitchover () throws IOException
 
420
    {
 
421
        if (offset != switchover)
 
422
            throw new InternalError ();
 
423
        in = new InputStreamReader (raw, encodingAssigned);
 
424
        buffer = null;
 
425
        switchover = -1;
 
426
    }
 
427
 
 
428
 
 
429
    /*
 
430
     * Used for ASCII supersets ... including the default UTF-8 encoding.
 
431
     */
 
432
    private void guessEncoding () throws IOException
 
433
    {
 
434
        //
 
435
        // We know that "<?xml " has been seen; so we'll skip any
 
436
        //      S? version="..."        [or single quotes]
 
437
        // bit and get the 
 
438
        //      S? encoding="..."       [or single quotes]
 
439
        // parts.  We place an arbitrary limit on the amount of text we
 
440
        // expect to find in the XML declarations; excessive whitespace
 
441
        // will cause this to guess "UTF-8".
 
442
        //
 
443
        int             c;
 
444
        StringBuffer    buf = new StringBuffer ();
 
445
        StringBuffer    keyBuf = null;
 
446
        String          key = null;
 
447
        boolean         sawEq = false;
 
448
        char            quoteChar = 0;
 
449
        boolean         sawQuestion = false;
 
450
 
 
451
    XmlDecl:
 
452
        for (int i = 0; i < 100; ++i) {
 
453
            if ((c = read ()) == -1) {
 
454
                setASCII ();
 
455
                return;
 
456
            }
 
457
 
 
458
            // ignore whitespace before/between "key = 'value'"
 
459
            if (Character.isWhitespace ((char) c))
 
460
                continue;
 
461
            
 
462
            // terminate the loop ASAP
 
463
            if (c == '?')
 
464
                sawQuestion = true;
 
465
            else if (sawQuestion) {
 
466
                if (c == '>')
 
467
                    break;
 
468
                sawQuestion = false;
 
469
            }
 
470
            
 
471
            // did we get the "key =" bit yet?
 
472
            if (key == null || !sawEq) {
 
473
                if (keyBuf == null) {
 
474
                    if (Character.isWhitespace ((char) c))
 
475
                        continue;
 
476
                    keyBuf = buf;
 
477
                    buf.setLength (0);
 
478
                    buf.append ((char)c);
 
479
                    sawEq = false;
 
480
                } else if (Character.isWhitespace ((char) c)) {
 
481
                    key = keyBuf.toString ();
 
482
                } else if (c == '=') {
 
483
                    if (key == null)
 
484
                        key = keyBuf.toString ();
 
485
                    sawEq = true;
 
486
                    keyBuf = null;
 
487
                    quoteChar = 0;
 
488
                } else
 
489
                    keyBuf.append ((char)c);
 
490
                continue;
 
491
            }
 
492
 
 
493
            // space before quoted value
 
494
            if (Character.isWhitespace ((char) c))
 
495
                continue;
 
496
            if (c == '"' || c == '\'') {
 
497
                if (quoteChar == 0) {
 
498
                    quoteChar = (char) c;
 
499
                    buf.setLength (0);
 
500
                    continue;
 
501
                } else if (c == quoteChar) {
 
502
                    if ("encoding".equals (key)) {
 
503
                        String  encoding = buf.toString ();
 
504
 
 
505
                        // [81] Encname ::= [A-Za-z] ([A-Za-z0-9._]|'-')*
 
506
                        for (i = 0; i < encoding.length(); i++) {
 
507
                            c = encoding.charAt (i);
 
508
                            if ((c >= 'A' && c <= 'Z')
 
509
                                    || (c >= 'a' && c <= 'z'))
 
510
                                continue;
 
511
                            if (i > 0 && (c == '-'
 
512
                                    || (c >= '0' && c <= '9')
 
513
                                    || c == '.' || c == '_'))
 
514
                                continue;
 
515
                            // map errors to UTF-8 default
 
516
                            break XmlDecl;
 
517
                        }
 
518
 
 
519
                        // we handle ASCII directly
 
520
                        if (isAsciiName (encoding)) {
 
521
                            setASCII ();
 
522
                            return;
 
523
                        }
 
524
 
 
525
                        // ditto ISO 8859/1
 
526
                        if (isLatinName (encoding)) {
 
527
                            setLatin1 ();
 
528
                            return;
 
529
                        }
 
530
 
 
531
                        // and UTF-8
 
532
                        if ("UTF-8".equalsIgnoreCase (encoding)
 
533
                                || "UTF8".equalsIgnoreCase (encoding)
 
534
                                )
 
535
                            break XmlDecl;
 
536
 
 
537
                        // JDK uses nonstandard names internally
 
538
                        if ("EUC-JP".equalsIgnoreCase (encoding))
 
539
                            encoding = "EUCJIS";
 
540
 
 
541
                        // other encodings ... use a reader.
 
542
                        setSwitchover (encoding);
 
543
                        return;
 
544
                    } else {
 
545
                        key = null;
 
546
                        continue;
 
547
                    }
 
548
                }
 
549
            }
 
550
            buf.append ((char) c);
 
551
        }
 
552
 
 
553
        setUTF8 ();
 
554
    }
 
555
 
 
556
    /*
 
557
     * Converts a UTF-8 character from the input buffer, optionally
 
558
     * restricting to the US-ASCII subset of UTF-8.
 
559
     */
 
560
    private char        utf8char () throws IOException
 
561
    {
 
562
        char            retval;
 
563
        int             character;
 
564
        
 
565
        // return second half of a surrogate pair
 
566
        if (nextChar != 0) {
 
567
            retval = nextChar;
 
568
            nextChar = 0;
 
569
            return retval;
 
570
        }
 
571
        
 
572
        //
 
573
        // Excerpted from RFC 2279:
 
574
        //
 
575
        // UCS-4 range (hex.)    UTF-8 octet sequence (binary)
 
576
        // 0000 0000-0000 007F   0xxxxxxx
 
577
        // 0000 0080-0000 07FF   110xxxxx 10xxxxxx
 
578
        // 0000 0800-0000 FFFF   1110xxxx 10xxxxxx 10xxxxxx
 
579
        // 0001 0000-001F FFFF   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
 
580
        // 0020 0000-03FF FFFF   111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
 
581
        // 0400 0000-7FFF FFFF   1111110x 10xxxxxx ... 10xxxxxx
 
582
        //
 
583
        // The last two encodings (5 and 6 bytes per char) aren't allowed
 
584
        // in XML documents since the characters are out of range.
 
585
        //
 
586
        retval = (char) buffer [offset];
 
587
        if ((retval & 0x80) == 0x00) {                  // 1 byte
 
588
            offset++;
 
589
            return retval;
 
590
        }
 
591
        if (isASCII)
 
592
            throw new CharConversionException ("Not US-ASCII:  0x"
 
593
                        + Integer.toHexString (retval & 0xff));
 
594
        
 
595
        //
 
596
        // Multibyte sequences -- check offsets optimistically,
 
597
        // ditto the "10xx xxxx" format for non-initial bytes
 
598
        //
 
599
        int             off = offset;
 
600
        
 
601
        try {
 
602
            if ((buffer [off] & 0x0E0) == 0x0C0) {              // 2 bytes
 
603
                character  = (buffer [off++] & 0x1f) << 6;
 
604
                character +=  buffer [off++] & 0x3f;
 
605
                retval = (char) character;
 
606
                character = 0;
 
607
            } else if ((buffer [off] & 0x0F0) == 0x0E0) {       // 3 bytes
 
608
                character  = (buffer [off++] & 0x0f) << 12;
 
609
                character += (buffer [off++] & 0x3f) << 6;
 
610
                character +=  buffer [off++] & 0x3f;
 
611
                retval = (char) character;
 
612
                character = 0;
 
613
            } else if ((buffer [off] & 0x0f8) == 0x0F0) {       // 4 bytes
 
614
                character  = (buffer [off++] & 0x07) << 18;
 
615
                character += (buffer [off++] & 0x3f) << 12;
 
616
                character += (buffer [off++] & 0x3f) << 6;
 
617
                character +=  buffer [off++] & 0x3f;
 
618
                // Convert UCS-4 char to a Unicode surrogate pair
 
619
                character -= 0x10000;
 
620
                retval = (char) (0xD800 + (character >> 10));
 
621
                character = 0xDC00 + (character & 0x03ff);
 
622
            } else
 
623
                // XXX actually a WF error ...
 
624
                throw new CharConversionException ("Illegal XML character"
 
625
                    + " 0x" + Integer.toHexString (buffer [offset] & 0xff)
 
626
                );
 
627
 
 
628
        } catch (ArrayIndexOutOfBoundsException e) {
 
629
            // that is, off > length && length >= buffer.length
 
630
            retval = (char) 0;
 
631
            character = 0;
 
632
        }
 
633
 
 
634
        //
 
635
        // if the buffer held only a partial character, compact it
 
636
        // and try to read the rest of the character.  worst case
 
637
        // involves three single-byte reads.
 
638
        //
 
639
        if (off > length) {
 
640
            System.arraycopy (buffer, offset, buffer, 0, length - offset);
 
641
            length -= offset;
 
642
            offset = 0;
 
643
            off = raw.read (buffer, length, buffer.length - length);
 
644
            if (off < 0)
 
645
                throw new CharConversionException ("Partial UTF-8 char");
 
646
            length += off;
 
647
            return utf8char ();
 
648
        }
 
649
 
 
650
        //
 
651
        // check the format of the non-initial bytes, and return
 
652
        //
 
653
        for (offset++; offset < off; offset++)
 
654
            if ((buffer [offset] & 0xC0) != 0x80)
 
655
                throw new CharConversionException ("Malformed UTF-8 char");
 
656
        nextChar = (char) character;
 
657
        return retval;
 
658
    }
 
659
 
 
660
    /**
 
661
     * Reads the number of characters read into the buffer, or -1 on EOF.
 
662
     */
 
663
    public int read (char buf [], int off, int len) throws IOException
 
664
    {
 
665
        int     i;
 
666
 
 
667
        if (closed)
 
668
            return -1;
 
669
        if (switchover > 0 && offset == switchover)
 
670
            doSwitchover ();
 
671
        if (in != null)
 
672
            return in.read (buf, off, len);
 
673
 
 
674
        if (offset >= length) {
 
675
            offset = 0;
 
676
            length = raw.read (buffer, 0, buffer.length);
 
677
        }
 
678
        if (length <= 0)
 
679
            return -1;
 
680
        if (encodingAssigned == null || isLatin1)
 
681
            for (i = 0; i < len && offset < length; i++)
 
682
                buf [off++] = (char) (buffer [offset++] & 0xff);
 
683
        else
 
684
            for (i = 0; i < len && offset < length; i++)
 
685
                buf [off++] = utf8char ();
 
686
        return i;
 
687
    }
 
688
 
 
689
    /**
 
690
     * Reads a single character.
 
691
     */
 
692
    public int read () throws IOException
 
693
    {
 
694
        if (closed)
 
695
            return -1;
 
696
        if (switchover > 0 && offset == switchover)
 
697
            doSwitchover ();
 
698
        if (in != null)
 
699
            return in.read ();
 
700
 
 
701
        if (offset >= length) {
 
702
            if (encodingAssigned == null) {
 
703
                // minimize readahead we might regret...
 
704
                if (length == buffer.length)
 
705
                    throw new InternalError ("too much peekahead");
 
706
                int len = raw.read (buffer, offset, 1);
 
707
                if (len <= 0)
 
708
                    return -1;
 
709
                length += len;
 
710
            } else {
 
711
                offset = 0;
 
712
                length = raw.read (buffer, 0, buffer.length);
 
713
                if (length <= 0)
 
714
                    return -1;
 
715
            }
 
716
        }
 
717
 
 
718
        if (isLatin1 || encodingAssigned == null)
 
719
            return buffer [offset++] & 0x0ff;
 
720
        else
 
721
            return utf8char ();
 
722
    }
 
723
 
 
724
    /**
 
725
     * Returns true iff the reader supports mark/reset.
 
726
     */
 
727
    public boolean markSupported ()
 
728
    {
 
729
        return in != null && in.markSupported ();
 
730
    }
 
731
 
 
732
    /**
 
733
     * Sets a mark allowing a limited number of characters to
 
734
     * be "peeked", by reading and then resetting.
 
735
     * @param value how many characters may be "peeked".
 
736
     */
 
737
    public void mark (int value) throws IOException
 
738
    {
 
739
        if (in != null)
 
740
            in.mark (value);
 
741
    }
 
742
 
 
743
    /**
 
744
     * Resets the current position to the last marked position.
 
745
     */
 
746
    public void reset () throws IOException
 
747
    {
 
748
        if (in != null)
 
749
            in.reset ();
 
750
    }
 
751
 
 
752
    /**
 
753
     * Skips a specified number of characters.
 
754
     */
 
755
    public long skip (long value) throws IOException
 
756
    {
 
757
        if (value < 0)
 
758
            return 0;
 
759
        if (in != null)
 
760
            return in.skip (value);
 
761
        long avail = length - offset;
 
762
        if (avail >= value) {
 
763
            offset += (int) value;
 
764
            return value;
 
765
        }
 
766
        offset += avail;
 
767
        return avail + raw.skip (value - avail);
 
768
    }
 
769
 
 
770
    /**
 
771
     * Returns true iff input characters are known to be ready.
 
772
     */
 
773
    public boolean ready () throws IOException
 
774
    {
 
775
        if (in != null)
 
776
            return in.ready ();
 
777
        return (length > offset) || raw.available () != 0;
 
778
    }
 
779
 
 
780
    /**
 
781
     * Closes the reader.
 
782
     */
 
783
    public void close () throws IOException
 
784
    {
 
785
        if (closed)
 
786
            return;
 
787
        if (in != null)
 
788
            in.close ();
 
789
        else
 
790
            raw.close ();
 
791
        closed = true;
 
792
    }
 
793
 
 
794
    /**
 
795
     * For printf debugging.
 
796
     */
 
797
    private static boolean debug = false;
 
798
    private static void debug(String str) {
 
799
        if (debug) {
 
800
            System.out.println("XmlReader: " + str);
 
801
        }
 
802
    }
 
803
}