View Javadoc
1   /*
2    * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
3    */
4   
5   /*
6    * Copyright 2005 The Apache Software Foundation.
7    *
8    * Licensed under the Apache License, Version 2.0 (the "License");
9    * you may not use this file except in compliance with the License.
10   * You may obtain a copy of the License at
11   *
12   *      http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package com.sun.org.apache.xerces.internal.impl ;
22  
23  import com.sun.org.apache.xerces.internal.impl.Constants;
24  import com.sun.org.apache.xerces.internal.impl.io.ASCIIReader;
25  import com.sun.org.apache.xerces.internal.impl.io.UCSReader;
26  import com.sun.org.apache.xerces.internal.impl.io.UTF8Reader;
27  import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter;
28  import com.sun.org.apache.xerces.internal.impl.XMLEntityHandler;
29  import com.sun.org.apache.xerces.internal.impl.validation.ValidationManager;
30  import com.sun.org.apache.xerces.internal.util.*;
31  import com.sun.org.apache.xerces.internal.util.URI;
32  import com.sun.org.apache.xerces.internal.utils.SecuritySupport;
33  import com.sun.org.apache.xerces.internal.utils.XMLLimitAnalyzer;
34  import com.sun.org.apache.xerces.internal.utils.XMLSecurityManager;
35  import com.sun.org.apache.xerces.internal.utils.XMLSecurityPropertyManager;
36  import com.sun.org.apache.xerces.internal.xni.Augmentations;
37  import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
38  import com.sun.org.apache.xerces.internal.xni.XNIException;
39  import com.sun.org.apache.xerces.internal.xni.parser.*;
40  import com.sun.xml.internal.stream.Entity;
41  import com.sun.xml.internal.stream.StaxEntityResolverWrapper;
42  import com.sun.xml.internal.stream.StaxXMLInputSource;
43  import com.sun.xml.internal.stream.XMLEntityStorage;
44  import java.io.*;
45  import java.lang.reflect.Method;
46  import java.net.HttpURLConnection;
47  import java.net.URISyntaxException;
48  import java.net.URL;
49  import java.net.URLConnection;
50  import java.util.Hashtable;
51  import java.util.Iterator;
52  import java.util.Locale;
53  import java.util.Map;
54  import java.util.Stack;
55  import javax.xml.XMLConstants;
56  
57  
58  /**
59   * Will keep track of current entity.
60   *
61   * The entity manager handles the registration of general and parameter
62   * entities; resolves entities; and starts entities. The entity manager
63   * is a central component in a standard parser configuration and this
64   * class works directly with the entity scanner to manage the underlying
65   * xni.
66   * <p>
67   * This component requires the following features and properties from the
68   * component manager that uses it:
69   * <ul>
70   *  <li>http://xml.org/sax/features/validation</li>;
71   *  <li>http://xml.org/sax/features/external-general-entities</li>;
72   *  <li>http://xml.org/sax/features/external-parameter-entities</li>;
73   *  <li>http://apache.org/xml/features/allow-java-encodings</li>;
74   *  <li>http://apache.org/xml/properties/internal/symbol-table</li>;
75   *  <li>http://apache.org/xml/properties/internal/error-reporter</li>;
76   *  <li>http://apache.org/xml/properties/internal/entity-resolver</li>;
77   * </ul>
78   *
79   *
80   * @author Andy Clark, IBM
81   * @author Arnaud  Le Hors, IBM
82   * @author K.Venugopal SUN Microsystems
83   * @author Neeraj Bajaj SUN Microsystems
84   * @author Sunitha Reddy SUN Microsystems
85   * @version $Id: XMLEntityManager.java,v 1.17 2010-11-01 04:39:41 joehw Exp $
86   */
87  public class XMLEntityManager implements XMLComponent, XMLEntityResolver {
88  
89      //
90      // Constants
91      //
92  
93      /** Default buffer size (2048). */
94      public static final int DEFAULT_BUFFER_SIZE = 8192;
95  
96      /** Default buffer size before we've finished with the XMLDecl:  */
97      public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64;
98  
99      /** Default internal entity buffer size (1024). */
100     public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 1024;
101 
102     // feature identifiers
103 
104     /** Feature identifier: validation. */
105     protected static final String VALIDATION =
106             Constants.SAX_FEATURE_PREFIX + Constants.VALIDATION_FEATURE;
107 
108     /**
109      * standard uri conformant (strict uri).
110      * http://apache.org/xml/features/standard-uri-conformant
111      */
112     protected boolean fStrictURI;
113 
114 
115     /** Feature identifier: external general entities. */
116     protected static final String EXTERNAL_GENERAL_ENTITIES =
117             Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE;
118 
119     /** Feature identifier: external parameter entities. */
120     protected static final String EXTERNAL_PARAMETER_ENTITIES =
121             Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE;
122 
123     /** Feature identifier: allow Java encodings. */
124     protected static final String ALLOW_JAVA_ENCODINGS =
125             Constants.XERCES_FEATURE_PREFIX + Constants.ALLOW_JAVA_ENCODINGS_FEATURE;
126 
127     /** Feature identifier: warn on duplicate EntityDef */
128     protected static final String WARN_ON_DUPLICATE_ENTITYDEF =
129             Constants.XERCES_FEATURE_PREFIX +Constants.WARN_ON_DUPLICATE_ENTITYDEF_FEATURE;
130 
131     /** Feature identifier: load external DTD. */
132     protected static final String LOAD_EXTERNAL_DTD =
133             Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE;
134 
135     // property identifiers
136 
137     /** Property identifier: symbol table. */
138     protected static final String SYMBOL_TABLE =
139             Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
140 
141     /** Property identifier: error reporter. */
142     protected static final String ERROR_REPORTER =
143             Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
144 
145     /** Feature identifier: standard uri conformant */
146     protected static final String STANDARD_URI_CONFORMANT =
147             Constants.XERCES_FEATURE_PREFIX +Constants.STANDARD_URI_CONFORMANT_FEATURE;
148 
149     /** Property identifier: entity resolver. */
150     protected static final String ENTITY_RESOLVER =
151             Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_RESOLVER_PROPERTY;
152 
153     protected static final String STAX_ENTITY_RESOLVER =
154             Constants.XERCES_PROPERTY_PREFIX + Constants.STAX_ENTITY_RESOLVER_PROPERTY;
155 
156     // property identifier:  ValidationManager
157     protected static final String VALIDATION_MANAGER =
158             Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY;
159 
160     /** property identifier: buffer size. */
161     protected static final String BUFFER_SIZE =
162             Constants.XERCES_PROPERTY_PREFIX + Constants.BUFFER_SIZE_PROPERTY;
163 
164     /** property identifier: security manager. */
165     protected static final String SECURITY_MANAGER =
166         Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY;
167 
168     protected static final String PARSER_SETTINGS =
169         Constants.XERCES_FEATURE_PREFIX + Constants.PARSER_SETTINGS;
170 
171     /** Property identifier: Security property manager. */
172     private static final String XML_SECURITY_PROPERTY_MANAGER =
173             Constants.XML_SECURITY_PROPERTY_MANAGER;
174 
175     /** access external dtd: file protocol */
176     static final String EXTERNAL_ACCESS_DEFAULT = Constants.EXTERNAL_ACCESS_DEFAULT;
177 
178     // recognized features and properties
179 
180     /** Recognized features. */
181     private static final String[] RECOGNIZED_FEATURES = {
182                 VALIDATION,
183                 EXTERNAL_GENERAL_ENTITIES,
184                 EXTERNAL_PARAMETER_ENTITIES,
185                 ALLOW_JAVA_ENCODINGS,
186                 WARN_ON_DUPLICATE_ENTITYDEF,
187                 STANDARD_URI_CONFORMANT
188     };
189 
190     /** Feature defaults. */
191     private static final Boolean[] FEATURE_DEFAULTS = {
192                 null,
193                 Boolean.TRUE,
194                 Boolean.TRUE,
195                 Boolean.TRUE,
196                 Boolean.FALSE,
197                 Boolean.FALSE
198     };
199 
200     /** Recognized properties. */
201     private static final String[] RECOGNIZED_PROPERTIES = {
202                 SYMBOL_TABLE,
203                 ERROR_REPORTER,
204                 ENTITY_RESOLVER,
205                 VALIDATION_MANAGER,
206                 BUFFER_SIZE,
207                 SECURITY_MANAGER,
208                 XML_SECURITY_PROPERTY_MANAGER
209     };
210 
211     /** Property defaults. */
212     private static final Object[] PROPERTY_DEFAULTS = {
213                 null,
214                 null,
215                 null,
216                 null,
217                 new Integer(DEFAULT_BUFFER_SIZE),
218                 null,
219                 null
220     };
221 
222     private static final String XMLEntity = "[xml]".intern();
223     private static final String DTDEntity = "[dtd]".intern();
224 
225     // debugging
226 
227     /**
228      * Debug printing of buffer. This debugging flag works best when you
229      * resize the DEFAULT_BUFFER_SIZE down to something reasonable like
230      * 64 characters.
231      */
232     private static final boolean DEBUG_BUFFER = false;
233 
234     /** warn on duplicate Entity declaration.
235      *  http://apache.org/xml/features/warn-on-duplicate-entitydef
236      */
237     protected boolean fWarnDuplicateEntityDef;
238 
239     /** Debug some basic entities. */
240     private static final boolean DEBUG_ENTITIES = false;
241 
242     /** Debug switching readers for encodings. */
243     private static final boolean DEBUG_ENCODINGS = false;
244 
245     // should be diplayed trace resolving messages
246     private static final boolean DEBUG_RESOLVER = false ;
247 
248     //
249     // Data
250     //
251 
252     // features
253 
254     /**
255      * Validation. This feature identifier is:
256      * http://xml.org/sax/features/validation
257      */
258     protected boolean fValidation;
259 
260     /**
261      * External general entities. This feature identifier is:
262      * http://xml.org/sax/features/external-general-entities
263      */
264     protected boolean fExternalGeneralEntities;
265 
266     /**
267      * External parameter entities. This feature identifier is:
268      * http://xml.org/sax/features/external-parameter-entities
269      */
270     protected boolean fExternalParameterEntities;
271 
272     /**
273      * Allow Java encoding names. This feature identifier is:
274      * http://apache.org/xml/features/allow-java-encodings
275      */
276     protected boolean fAllowJavaEncodings = true ;
277 
278     /** Load external DTD. */
279     protected boolean fLoadExternalDTD = true;
280 
281     // properties
282 
283     /**
284      * Symbol table. This property identifier is:
285      * http://apache.org/xml/properties/internal/symbol-table
286      */
287     protected SymbolTable fSymbolTable;
288 
289     /**
290      * Error reporter. This property identifier is:
291      * http://apache.org/xml/properties/internal/error-reporter
292      */
293     protected XMLErrorReporter fErrorReporter;
294 
295     /**
296      * Entity resolver. This property identifier is:
297      * http://apache.org/xml/properties/internal/entity-resolver
298      */
299     protected XMLEntityResolver fEntityResolver;
300 
301     /** Stax Entity Resolver. This property identifier is XMLInputFactory.ENTITY_RESOLVER */
302 
303     protected StaxEntityResolverWrapper fStaxEntityResolver;
304 
305     /** Property Manager. This is used from Stax */
306     protected PropertyManager fPropertyManager ;
307 
308     /** used to restrict external access */
309     protected String fAccessExternalDTD = EXTERNAL_ACCESS_DEFAULT;
310 
311     // settings
312 
313     /**
314      * Validation manager. This property identifier is:
315      * http://apache.org/xml/properties/internal/validation-manager
316      */
317     protected ValidationManager fValidationManager;
318 
319     // settings
320 
321     /**
322      * Buffer size. We get this value from a property. The default size
323      * is used if the input buffer size property is not specified.
324      * REVISIT: do we need a property for internal entity buffer size?
325      */
326     protected int fBufferSize = DEFAULT_BUFFER_SIZE;
327 
328     /** Security Manager */
329     protected XMLSecurityManager fSecurityManager = null;
330 
331     protected XMLLimitAnalyzer fLimitAnalyzer = null;
332 
333     protected int entityExpansionIndex;
334 
335     /**
336      * True if the document entity is standalone. This should really
337      * only be set by the document source (e.g. XMLDocumentScanner).
338      */
339     protected boolean fStandalone;
340 
341     // are the entities being parsed in the external subset?
342     // NOTE:  this *is not* the same as whether they're external entities!
343     protected boolean fInExternalSubset = false;
344 
345 
346     // handlers
347     /** Entity handler. */
348     protected XMLEntityHandler fEntityHandler;
349 
350     /** Current entity scanner */
351     protected XMLEntityScanner fEntityScanner ;
352 
353     /** XML 1.0 entity scanner. */
354     protected XMLEntityScanner fXML10EntityScanner;
355 
356     /** XML 1.1 entity scanner. */
357     protected XMLEntityScanner fXML11EntityScanner;
358 
359     /** count of entities expanded: */
360     protected int fEntityExpansionCount = 0;
361 
362     // entities
363 
364     /** Entities. */
365     protected Hashtable fEntities = new Hashtable();
366 
367     /** Entity stack. */
368     protected Stack fEntityStack = new Stack();
369 
370     /** Current entity. */
371     protected Entity.ScannedEntity fCurrentEntity = null;
372 
373     /** identify if the InputSource is created by a resolver */
374     boolean fISCreatedByResolver = false;
375 
376     // shared context
377 
378     protected XMLEntityStorage fEntityStorage ;
379 
380     protected final Object [] defaultEncoding = new Object[]{"UTF-8", null};
381 
382 
383     // temp vars
384 
385     /** Resource identifer. */
386     private final XMLResourceIdentifierImpl fResourceIdentifier = new XMLResourceIdentifierImpl();
387 
388     /** Augmentations for entities. */
389     private final Augmentations fEntityAugs = new AugmentationsImpl();
390 
391     /** Pool of character buffers. */
392     private CharacterBufferPool fBufferPool = new CharacterBufferPool(fBufferSize, DEFAULT_INTERNAL_BUFFER_SIZE);
393 
394     //
395     // Constructors
396     //
397 
398     /**
399      * If this constructor is used to create the object, reset() should be invoked on this object
400      */
401     public XMLEntityManager() {
402         fEntityStorage = new XMLEntityStorage(this) ;
403         setScannerVersion(Constants.XML_VERSION_1_0);
404     } // <init>()
405 
406     /** Default constructor. */
407     public XMLEntityManager(PropertyManager propertyManager) {
408         fPropertyManager = propertyManager ;
409         //pass a reference to current entity being scanned
410         //fEntityStorage = new XMLEntityStorage(fCurrentEntity) ;
411         fEntityStorage = new XMLEntityStorage(this) ;
412         fEntityScanner = new XMLEntityScanner(propertyManager, this) ;
413         reset(propertyManager);
414     } // <init>()
415 
416     /**
417      * Adds an internal entity declaration.
418      * <p>
419      * <strong>Note:</strong> This method ignores subsequent entity
420      * declarations.
421      * <p>
422      * <strong>Note:</strong> The name should be a unique symbol. The
423      * SymbolTable can be used for this purpose.
424      *
425      * @param name The name of the entity.
426      * @param text The text of the entity.
427      *
428      * @see SymbolTable
429      */
430     public void addInternalEntity(String name, String text) {
431         if (!fEntities.containsKey(name)) {
432             Entity entity = new Entity.InternalEntity(name, text, fInExternalSubset);
433             fEntities.put(name, entity);
434         } else{
435             if(fWarnDuplicateEntityDef){
436                 fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
437                         "MSG_DUPLICATE_ENTITY_DEFINITION",
438                         new Object[]{ name },
439                         XMLErrorReporter.SEVERITY_WARNING );
440             }
441         }
442 
443     } // addInternalEntity(String,String)
444 
445     /**
446      * Adds an external entity declaration.
447      * <p>
448      * <strong>Note:</strong> This method ignores subsequent entity
449      * declarations.
450      * <p>
451      * <strong>Note:</strong> The name should be a unique symbol. The
452      * SymbolTable can be used for this purpose.
453      *
454      * @param name         The name of the entity.
455      * @param publicId     The public identifier of the entity.
456      * @param literalSystemId     The system identifier of the entity.
457      * @param baseSystemId The base system identifier of the entity.
458      *                     This is the system identifier of the entity
459      *                     where <em>the entity being added</em> and
460      *                     is used to expand the system identifier when
461      *                     the system identifier is a relative URI.
462      *                     When null the system identifier of the first
463      *                     external entity on the stack is used instead.
464      *
465      * @see SymbolTable
466      */
467     public void addExternalEntity(String name,
468             String publicId, String literalSystemId,
469             String baseSystemId) throws IOException {
470         if (!fEntities.containsKey(name)) {
471             if (baseSystemId == null) {
472                 // search for the first external entity on the stack
473                 int size = fEntityStack.size();
474                 if (size == 0 && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
475                     baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
476                 }
477                 for (int i = size - 1; i >= 0 ; i--) {
478                     Entity.ScannedEntity externalEntity =
479                             (Entity.ScannedEntity)fEntityStack.elementAt(i);
480                     if (externalEntity.entityLocation != null && externalEntity.entityLocation.getExpandedSystemId() != null) {
481                         baseSystemId = externalEntity.entityLocation.getExpandedSystemId();
482                         break;
483                     }
484                 }
485             }
486             Entity entity = new Entity.ExternalEntity(name,
487                     new XMLEntityDescriptionImpl(name, publicId, literalSystemId, baseSystemId,
488                     expandSystemId(literalSystemId, baseSystemId, false)), null, fInExternalSubset);
489             fEntities.put(name, entity);
490         } else{
491             if(fWarnDuplicateEntityDef){
492                 fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
493                         "MSG_DUPLICATE_ENTITY_DEFINITION",
494                         new Object[]{ name },
495                         XMLErrorReporter.SEVERITY_WARNING );
496             }
497         }
498 
499     } // addExternalEntity(String,String,String,String)
500 
501 
502     /**
503      * Adds an unparsed entity declaration.
504      * <p>
505      * <strong>Note:</strong> This method ignores subsequent entity
506      * declarations.
507      * <p>
508      * <strong>Note:</strong> The name should be a unique symbol. The
509      * SymbolTable can be used for this purpose.
510      *
511      * @param name     The name of the entity.
512      * @param publicId The public identifier of the entity.
513      * @param systemId The system identifier of the entity.
514      * @param notation The name of the notation.
515      *
516      * @see SymbolTable
517      */
518     public void addUnparsedEntity(String name,
519             String publicId, String systemId,
520             String baseSystemId, String notation) {
521         if (!fEntities.containsKey(name)) {
522             Entity.ExternalEntity entity = new Entity.ExternalEntity(name,
523                     new XMLEntityDescriptionImpl(name, publicId, systemId, baseSystemId, null),
524                     notation, fInExternalSubset);
525             fEntities.put(name, entity);
526         } else{
527             if(fWarnDuplicateEntityDef){
528                 fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
529                         "MSG_DUPLICATE_ENTITY_DEFINITION",
530                         new Object[]{ name },
531                         XMLErrorReporter.SEVERITY_WARNING );
532             }
533         }
534     } // addUnparsedEntity(String,String,String,String)
535 
536 
537     /** get the entity storage object from entity manager */
538     public XMLEntityStorage getEntityStore(){
539         return fEntityStorage ;
540     }
541 
542     /** return the entity responsible for reading the entity */
543     public XMLEntityScanner getEntityScanner(){
544         if(fEntityScanner == null) {
545             // default to 1.0
546             if(fXML10EntityScanner == null) {
547                 fXML10EntityScanner = new XMLEntityScanner();
548             }
549             fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
550             fEntityScanner = fXML10EntityScanner;
551         }
552         return fEntityScanner;
553 
554     }
555 
556     public void setScannerVersion(short version) {
557 
558         if(version == Constants.XML_VERSION_1_0) {
559             if(fXML10EntityScanner == null) {
560                 fXML10EntityScanner = new XMLEntityScanner();
561             }
562             fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
563             fEntityScanner = fXML10EntityScanner;
564             fEntityScanner.setCurrentEntity(fCurrentEntity);
565         } else {
566             if(fXML11EntityScanner == null) {
567                 fXML11EntityScanner = new XML11EntityScanner();
568             }
569             fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter);
570             fEntityScanner = fXML11EntityScanner;
571             fEntityScanner.setCurrentEntity(fCurrentEntity);
572         }
573 
574     }
575 
576     /**
577      * This method uses the passed-in XMLInputSource to make
578      * fCurrentEntity usable for reading.
579      * @param name  name of the entity (XML is it's the document entity)
580      * @param xmlInputSource    the input source, with sufficient information
581      *      to begin scanning characters.
582      * @param literal        True if this entity is started within a
583      *                       literal value.
584      * @param isExternal    whether this entity should be treated as an internal or external entity.
585      * @throws IOException  if anything can't be read
586      *  XNIException    If any parser-specific goes wrong.
587      * @return the encoding of the new entity or null if a character stream was employed
588      */
589     public String setupCurrentEntity(String name, XMLInputSource xmlInputSource,
590             boolean literal, boolean isExternal)
591             throws IOException, XNIException {
592         // get information
593 
594         final String publicId = xmlInputSource.getPublicId();
595         String literalSystemId = xmlInputSource.getSystemId();
596         String baseSystemId = xmlInputSource.getBaseSystemId();
597         String encoding = xmlInputSource.getEncoding();
598         final boolean encodingExternallySpecified = (encoding != null);
599         Boolean isBigEndian = null;
600 
601         // create reader
602         InputStream stream = null;
603         Reader reader = xmlInputSource.getCharacterStream();
604 
605         // First chance checking strict URI
606         String expandedSystemId = expandSystemId(literalSystemId, baseSystemId, fStrictURI);
607         if (baseSystemId == null) {
608             baseSystemId = expandedSystemId;
609         }
610         if (reader == null) {
611             stream = xmlInputSource.getByteStream();
612             if (stream == null) {
613                 URL location = new URL(expandedSystemId);
614                 URLConnection connect = location.openConnection();
615                 if (!(connect instanceof HttpURLConnection)) {
616                     stream = connect.getInputStream();
617                 }
618                 else {
619                     boolean followRedirects = true;
620 
621                     // setup URLConnection if we have an HTTPInputSource
622                     if (xmlInputSource instanceof HTTPInputSource) {
623                         final HttpURLConnection urlConnection = (HttpURLConnection) connect;
624                         final HTTPInputSource httpInputSource = (HTTPInputSource) xmlInputSource;
625 
626                         // set request properties
627                         Iterator propIter = httpInputSource.getHTTPRequestProperties();
628                         while (propIter.hasNext()) {
629                             Map.Entry entry = (Map.Entry) propIter.next();
630                             urlConnection.setRequestProperty((String) entry.getKey(), (String) entry.getValue());
631                         }
632 
633                         // set preference for redirection
634                         followRedirects = httpInputSource.getFollowHTTPRedirects();
635                         if (!followRedirects) {
636                             setInstanceFollowRedirects(urlConnection, followRedirects);
637                         }
638                     }
639 
640                     stream = connect.getInputStream();
641 
642                     // REVISIT: If the URLConnection has external encoding
643                     // information, we should be reading it here. It's located
644                     // in the charset parameter of Content-Type. -- mrglavas
645 
646                     if (followRedirects) {
647                         String redirect = connect.getURL().toString();
648                         // E43: Check if the URL was redirected, and then
649                         // update literal and expanded system IDs if needed.
650                         if (!redirect.equals(expandedSystemId)) {
651                             literalSystemId = redirect;
652                             expandedSystemId = redirect;
653                         }
654                     }
655                 }
656             }
657 
658             // wrap this stream in RewindableInputStream
659             stream = new RewindableInputStream(stream);
660 
661             // perform auto-detect of encoding if necessary
662             if (encoding == null) {
663                 // read first four bytes and determine encoding
664                 final byte[] b4 = new byte[4];
665                 int count = 0;
666                 for (; count<4; count++ ) {
667                     b4[count] = (byte)stream.read();
668                 }
669                 if (count == 4) {
670                     Object [] encodingDesc = getEncodingName(b4, count);
671                     encoding = (String)(encodingDesc[0]);
672                     isBigEndian = (Boolean)(encodingDesc[1]);
673 
674                     stream.reset();
675                     // Special case UTF-8 files with BOM created by Microsoft
676                     // tools. It's more efficient to consume the BOM than make
677                     // the reader perform extra checks. -Ac
678                     if (count > 2 && encoding.equals("UTF-8")) {
679                         int b0 = b4[0] & 0xFF;
680                         int b1 = b4[1] & 0xFF;
681                         int b2 = b4[2] & 0xFF;
682                         if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
683                             // ignore first three bytes...
684                             stream.skip(3);
685                         }
686                     }
687                     reader = createReader(stream, encoding, isBigEndian);
688                 } else {
689                     reader = createReader(stream, encoding, isBigEndian);
690                 }
691             }
692 
693             // use specified encoding
694             else {
695                 encoding = encoding.toUpperCase(Locale.ENGLISH);
696 
697                 // If encoding is UTF-8, consume BOM if one is present.
698                 if (encoding.equals("UTF-8")) {
699                     final int[] b3 = new int[3];
700                     int count = 0;
701                     for (; count < 3; ++count) {
702                         b3[count] = stream.read();
703                         if (b3[count] == -1)
704                             break;
705                     }
706                     if (count == 3) {
707                         if (b3[0] != 0xEF || b3[1] != 0xBB || b3[2] != 0xBF) {
708                             // First three bytes are not BOM, so reset.
709                             stream.reset();
710                         }
711                     } else {
712                         stream.reset();
713                     }
714                 }
715                 // If encoding is UTF-16, we still need to read the first four bytes
716                 // in order to discover the byte order.
717                 else if (encoding.equals("UTF-16")) {
718                     final int[] b4 = new int[4];
719                     int count = 0;
720                     for (; count < 4; ++count) {
721                         b4[count] = stream.read();
722                         if (b4[count] == -1)
723                             break;
724                     }
725                     stream.reset();
726 
727                     String utf16Encoding = "UTF-16";
728                     if (count >= 2) {
729                         final int b0 = b4[0];
730                         final int b1 = b4[1];
731                         if (b0 == 0xFE && b1 == 0xFF) {
732                             // UTF-16, big-endian
733                             utf16Encoding = "UTF-16BE";
734                             isBigEndian = Boolean.TRUE;
735                         }
736                         else if (b0 == 0xFF && b1 == 0xFE) {
737                             // UTF-16, little-endian
738                             utf16Encoding = "UTF-16LE";
739                             isBigEndian = Boolean.FALSE;
740                         }
741                         else if (count == 4) {
742                             final int b2 = b4[2];
743                             final int b3 = b4[3];
744                             if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
745                                 // UTF-16, big-endian, no BOM
746                                 utf16Encoding = "UTF-16BE";
747                                 isBigEndian = Boolean.TRUE;
748                             }
749                             if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
750                                 // UTF-16, little-endian, no BOM
751                                 utf16Encoding = "UTF-16LE";
752                                 isBigEndian = Boolean.FALSE;
753                             }
754                         }
755                     }
756                     reader = createReader(stream, utf16Encoding, isBigEndian);
757                 }
758                 // If encoding is UCS-4, we still need to read the first four bytes
759                 // in order to discover the byte order.
760                 else if (encoding.equals("ISO-10646-UCS-4")) {
761                     final int[] b4 = new int[4];
762                     int count = 0;
763                     for (; count < 4; ++count) {
764                         b4[count] = stream.read();
765                         if (b4[count] == -1)
766                             break;
767                     }
768                     stream.reset();
769 
770                     // Ignore unusual octet order for now.
771                     if (count == 4) {
772                         // UCS-4, big endian (1234)
773                         if (b4[0] == 0x00 && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x3C) {
774                             isBigEndian = Boolean.TRUE;
775                         }
776                         // UCS-4, little endian (1234)
777                         else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x00) {
778                             isBigEndian = Boolean.FALSE;
779                         }
780                     }
781                 }
782                 // If encoding is UCS-2, we still need to read the first four bytes
783                 // in order to discover the byte order.
784                 else if (encoding.equals("ISO-10646-UCS-2")) {
785                     final int[] b4 = new int[4];
786                     int count = 0;
787                     for (; count < 4; ++count) {
788                         b4[count] = stream.read();
789                         if (b4[count] == -1)
790                             break;
791                     }
792                     stream.reset();
793 
794                     if (count == 4) {
795                         // UCS-2, big endian
796                         if (b4[0] == 0x00 && b4[1] == 0x3C && b4[2] == 0x00 && b4[3] == 0x3F) {
797                             isBigEndian = Boolean.TRUE;
798                         }
799                         // UCS-2, little endian
800                         else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x3F && b4[3] == 0x00) {
801                             isBigEndian = Boolean.FALSE;
802                         }
803                     }
804                 }
805 
806                 reader = createReader(stream, encoding, isBigEndian);
807             }
808 
809             // read one character at a time so we don't jump too far
810             // ahead, converting characters from the byte stream in
811             // the wrong encoding
812             if (DEBUG_ENCODINGS) {
813                 System.out.println("$$$ no longer wrapping reader in OneCharReader");
814             }
815             //reader = new OneCharReader(reader);
816         }
817 
818         // We've seen a new Reader.
819         // Push it on the stack so we can close it later.
820         //fOwnReaders.add(reader);
821 
822         // push entity on stack
823         if (fCurrentEntity != null) {
824             fEntityStack.push(fCurrentEntity);
825         }
826 
827         // create entity
828         /* if encoding is specified externally, 'encoding' information present
829          * in the prolog of the XML document is not considered. Hence, prolog can
830          * be read in Chunks of data instead of byte by byte.
831          */
832         fCurrentEntity = new com.sun.xml.internal.stream.Entity.ScannedEntity(name,new XMLResourceIdentifierImpl(publicId, literalSystemId, baseSystemId, expandedSystemId),stream, reader, encoding, literal, encodingExternallySpecified, isExternal);
833         fCurrentEntity.setEncodingExternallySpecified(encodingExternallySpecified);
834         fEntityScanner.setCurrentEntity(fCurrentEntity);
835         fResourceIdentifier.setValues(publicId, literalSystemId, baseSystemId, expandedSystemId);
836         if (fLimitAnalyzer != null) {
837             fLimitAnalyzer.startEntity(name);
838         }
839         return encoding;
840     } //setupCurrentEntity(String, XMLInputSource, boolean, boolean):  String
841 
842 
843     /**
844      * Checks whether an entity given by name is external.
845      *
846      * @param entityName The name of the entity to check.
847      * @return True if the entity is external, false otherwise
848      * (including when the entity is not declared).
849      */
850     public boolean isExternalEntity(String entityName) {
851 
852         Entity entity = (Entity)fEntities.get(entityName);
853         if (entity == null) {
854             return false;
855         }
856         return entity.isExternal();
857     }
858 
859     /**
860      * Checks whether the declaration of an entity given by name is
861      * // in the external subset.
862      *
863      * @param entityName The name of the entity to check.
864      * @return True if the entity was declared in the external subset, false otherwise
865      *           (including when the entity is not declared).
866      */
867     public boolean isEntityDeclInExternalSubset(String entityName) {
868 
869         Entity entity = (Entity)fEntities.get(entityName);
870         if (entity == null) {
871             return false;
872         }
873         return entity.isEntityDeclInExternalSubset();
874     }
875 
876 
877 
878     //
879     // Public methods
880     //
881 
882     /**
883      * Sets whether the document entity is standalone.
884      *
885      * @param standalone True if document entity is standalone.
886      */
887     public void setStandalone(boolean standalone) {
888         fStandalone = standalone;
889     }
890     // setStandalone(boolean)
891 
892     /** Returns true if the document entity is standalone. */
893     public boolean isStandalone() {
894         return fStandalone;
895     }  //isStandalone():boolean
896 
897     public boolean isDeclaredEntity(String entityName) {
898 
899         Entity entity = (Entity)fEntities.get(entityName);
900         return entity != null;
901     }
902 
903     public boolean isUnparsedEntity(String entityName) {
904 
905         Entity entity = (Entity)fEntities.get(entityName);
906         if (entity == null) {
907             return false;
908         }
909         return entity.isUnparsed();
910     }
911 
912 
913 
914     // this simply returns the fResourceIdentifier object;
915     // this should only be used with caution by callers that
916     // carefully manage the entity manager's behaviour, so that
917     // this doesn't returning meaningless or misleading data.
918     // @return  a reference to the current fResourceIdentifier object
919     public XMLResourceIdentifier getCurrentResourceIdentifier() {
920         return fResourceIdentifier;
921     }
922 
923     /**
924      * Sets the entity handler. When an entity starts and ends, the
925      * entity handler is notified of the change.
926      *
927      * @param entityHandler The new entity handler.
928      */
929 
930     public void setEntityHandler(com.sun.org.apache.xerces.internal.impl.XMLEntityHandler entityHandler) {
931         fEntityHandler = (XMLEntityHandler) entityHandler;
932     } // setEntityHandler(XMLEntityHandler)
933 
934     //this function returns StaxXMLInputSource
935     public StaxXMLInputSource resolveEntityAsPerStax(XMLResourceIdentifier resourceIdentifier) throws java.io.IOException{
936 
937         if(resourceIdentifier == null ) return null;
938 
939         String publicId = resourceIdentifier.getPublicId();
940         String literalSystemId = resourceIdentifier.getLiteralSystemId();
941         String baseSystemId = resourceIdentifier.getBaseSystemId();
942         String expandedSystemId = resourceIdentifier.getExpandedSystemId();
943         // if no base systemId given, assume that it's relative
944         // to the systemId of the current scanned entity
945         // Sometimes the system id is not (properly) expanded.
946         // We need to expand the system id if:
947         // a. the expanded one was null; or
948         // b. the base system id was null, but becomes non-null from the current entity.
949         boolean needExpand = (expandedSystemId == null);
950         // REVISIT:  why would the baseSystemId ever be null?  if we
951         // didn't have to make this check we wouldn't have to reuse the
952         // fXMLResourceIdentifier object...
953         if (baseSystemId == null && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
954             baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
955             if (baseSystemId != null)
956                 needExpand = true;
957         }
958         if (needExpand)
959             expandedSystemId = expandSystemId(literalSystemId, baseSystemId,false);
960 
961         // give the entity resolver a chance
962         StaxXMLInputSource staxInputSource = null;
963         XMLInputSource xmlInputSource = null;
964 
965         XMLResourceIdentifierImpl ri = null;
966 
967         if (resourceIdentifier instanceof XMLResourceIdentifierImpl) {
968             ri = (XMLResourceIdentifierImpl)resourceIdentifier;
969         } else {
970             fResourceIdentifier.clear();
971             ri = fResourceIdentifier;
972         }
973         ri.setValues(publicId, literalSystemId, baseSystemId, expandedSystemId);
974         if(DEBUG_RESOLVER){
975             System.out.println("BEFORE Calling resolveEntity") ;
976         }
977 
978         fISCreatedByResolver = false;
979         //either of Stax or Xerces would be null
980         if(fStaxEntityResolver != null){
981             staxInputSource = fStaxEntityResolver.resolveEntity(ri);
982             if(staxInputSource != null) {
983                 fISCreatedByResolver = true;
984             }
985         }
986 
987         if(fEntityResolver != null){
988             xmlInputSource = fEntityResolver.resolveEntity(ri);
989             if(xmlInputSource != null) {
990                 fISCreatedByResolver = true;
991             }
992         }
993 
994         if(xmlInputSource != null){
995             //wrap this XMLInputSource to StaxInputSource
996             staxInputSource = new StaxXMLInputSource(xmlInputSource, fISCreatedByResolver);
997         }
998 
999         // do default resolution
1000         //this works for both stax & Xerces, if staxInputSource is null, it means parser need to revert to default resolution
1001         if (staxInputSource == null) {
1002             // REVISIT: when systemId is null, I think we should return null.
1003             //          is this the right solution? -SG
1004             //if (systemId != null)
1005             staxInputSource = new StaxXMLInputSource(new XMLInputSource(publicId, literalSystemId, baseSystemId));
1006         }else if(staxInputSource.hasXMLStreamOrXMLEventReader()){
1007             //Waiting for the clarification from EG. - nb
1008         }
1009 
1010         if (DEBUG_RESOLVER) {
1011             System.err.println("XMLEntityManager.resolveEntity(" + publicId + ")");
1012             System.err.println(" = " + xmlInputSource);
1013         }
1014 
1015         return staxInputSource;
1016 
1017     }
1018 
1019     /**
1020      * Resolves the specified public and system identifiers. This
1021      * method first attempts to resolve the entity based on the
1022      * EntityResolver registered by the application. If no entity
1023      * resolver is registered or if the registered entity handler
1024      * is unable to resolve the entity, then default entity
1025      * resolution will occur.
1026      *
1027      * @param publicId     The public identifier of the entity.
1028      * @param systemId     The system identifier of the entity.
1029      * @param baseSystemId The base system identifier of the entity.
1030      *                     This is the system identifier of the current
1031      *                     entity and is used to expand the system
1032      *                     identifier when the system identifier is a
1033      *                     relative URI.
1034      *
1035      * @return Returns an input source that wraps the resolved entity.
1036      *         This method will never return null.
1037      *
1038      * @throws IOException  Thrown on i/o error.
1039      * @throws XNIException Thrown by entity resolver to signal an error.
1040      */
1041     public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) throws IOException, XNIException {
1042         if(resourceIdentifier == null ) return null;
1043         String publicId = resourceIdentifier.getPublicId();
1044         String literalSystemId = resourceIdentifier.getLiteralSystemId();
1045         String baseSystemId = resourceIdentifier.getBaseSystemId();
1046         String expandedSystemId = resourceIdentifier.getExpandedSystemId();
1047         String namespace = resourceIdentifier.getNamespace();
1048 
1049         // if no base systemId given, assume that it's relative
1050         // to the systemId of the current scanned entity
1051         // Sometimes the system id is not (properly) expanded.
1052         // We need to expand the system id if:
1053         // a. the expanded one was null; or
1054         // b. the base system id was null, but becomes non-null from the current entity.
1055         boolean needExpand = (expandedSystemId == null);
1056         // REVISIT:  why would the baseSystemId ever be null?  if we
1057         // didn't have to make this check we wouldn't have to reuse the
1058         // fXMLResourceIdentifier object...
1059         if (baseSystemId == null && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
1060             baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
1061             if (baseSystemId != null)
1062                 needExpand = true;
1063         }
1064         if (needExpand)
1065             expandedSystemId = expandSystemId(literalSystemId, baseSystemId,false);
1066 
1067         // give the entity resolver a chance
1068         XMLInputSource xmlInputSource = null;
1069 
1070         if (fEntityResolver != null) {
1071             resourceIdentifier.setBaseSystemId(baseSystemId);
1072             resourceIdentifier.setExpandedSystemId(expandedSystemId);
1073             xmlInputSource = fEntityResolver.resolveEntity(resourceIdentifier);
1074         }
1075 
1076         // do default resolution
1077         // REVISIT: what's the correct behavior if the user provided an entity
1078         // resolver (fEntityResolver != null), but resolveEntity doesn't return
1079         // an input source (xmlInputSource == null)?
1080         // do we do default resolution, or do we just return null? -SG
1081         if (xmlInputSource == null) {
1082             // REVISIT: when systemId is null, I think we should return null.
1083             //          is this the right solution? -SG
1084             //if (systemId != null)
1085             xmlInputSource = new XMLInputSource(publicId, literalSystemId, baseSystemId);
1086         }
1087 
1088         if (DEBUG_RESOLVER) {
1089             System.err.println("XMLEntityManager.resolveEntity(" + publicId + ")");
1090             System.err.println(" = " + xmlInputSource);
1091         }
1092 
1093         return xmlInputSource;
1094 
1095     } // resolveEntity(XMLResourceIdentifier):XMLInputSource
1096 
1097     /**
1098      * Starts a named entity.
1099      *
1100      * @param entityName The name of the entity to start.
1101      * @param literal    True if this entity is started within a literal
1102      *                   value.
1103      *
1104      * @throws IOException  Thrown on i/o error.
1105      * @throws XNIException Thrown by entity handler to signal an error.
1106      */
1107     public void startEntity(String entityName, boolean literal)
1108     throws IOException, XNIException {
1109 
1110         // was entity declared?
1111         Entity entity = (Entity)fEntityStorage.getEntity(entityName);
1112         if (entity == null) {
1113             if (fEntityHandler != null) {
1114                 String encoding = null;
1115                 fResourceIdentifier.clear();
1116                 fEntityAugs.removeAllItems();
1117                 fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1118                 fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
1119                 fEntityAugs.removeAllItems();
1120                 fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1121                 fEntityHandler.endEntity(entityName, fEntityAugs);
1122             }
1123             return;
1124         }
1125 
1126         // should we skip external entities?
1127         boolean external = entity.isExternal();
1128         Entity.ExternalEntity externalEntity = null;
1129         String extLitSysId = null, extBaseSysId = null, expandedSystemId = null;
1130         if (external) {
1131             externalEntity = (Entity.ExternalEntity)entity;
1132             extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getLiteralSystemId() : null);
1133             extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getBaseSystemId() : null);
1134             expandedSystemId = expandSystemId(extLitSysId, extBaseSysId);
1135             boolean unparsed = entity.isUnparsed();
1136             boolean parameter = entityName.startsWith("%");
1137             boolean general = !parameter;
1138             if (unparsed || (general && !fExternalGeneralEntities) ||
1139                     (parameter && !fExternalParameterEntities)) {
1140 
1141                 if (fEntityHandler != null) {
1142                     fResourceIdentifier.clear();
1143                     final String encoding = null;
1144                     fResourceIdentifier.setValues(
1145                             (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null),
1146                             extLitSysId, extBaseSysId, expandedSystemId);
1147                     fEntityAugs.removeAllItems();
1148                     fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1149                     fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
1150                     fEntityAugs.removeAllItems();
1151                     fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1152                     fEntityHandler.endEntity(entityName, fEntityAugs);
1153                 }
1154                 return;
1155             }
1156         }
1157 
1158         // is entity recursive?
1159         int size = fEntityStack.size();
1160         for (int i = size; i >= 0; i--) {
1161             Entity activeEntity = i == size
1162                     ? fCurrentEntity
1163                     : (Entity)fEntityStack.elementAt(i);
1164             if (activeEntity.name == entityName) {
1165                 String path = entityName;
1166                 for (int j = i + 1; j < size; j++) {
1167                     activeEntity = (Entity)fEntityStack.elementAt(j);
1168                     path = path + " -> " + activeEntity.name;
1169                 }
1170                 path = path + " -> " + fCurrentEntity.name;
1171                 path = path + " -> " + entityName;
1172                 fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
1173                         "RecursiveReference",
1174                         new Object[] { entityName, path },
1175                         XMLErrorReporter.SEVERITY_FATAL_ERROR);
1176 
1177                         if (fEntityHandler != null) {
1178                             fResourceIdentifier.clear();
1179                             final String encoding = null;
1180                             if (external) {
1181                                 fResourceIdentifier.setValues(
1182                                         (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null),
1183                                         extLitSysId, extBaseSysId, expandedSystemId);
1184                             }
1185                             fEntityAugs.removeAllItems();
1186                             fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1187                             fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
1188                             fEntityAugs.removeAllItems();
1189                             fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1190                             fEntityHandler.endEntity(entityName, fEntityAugs);
1191                         }
1192 
1193                         return;
1194             }
1195         }
1196 
1197         // resolve external entity
1198         StaxXMLInputSource staxInputSource = null;
1199         XMLInputSource xmlInputSource = null ;
1200 
1201         if (external) {
1202             staxInputSource = resolveEntityAsPerStax(externalEntity.entityLocation);
1203             /** xxx:  Waiting from the EG
1204              * //simply return if there was entity resolver registered and application
1205              * //returns either XMLStreamReader or XMLEventReader.
1206              * if(staxInputSource.hasXMLStreamOrXMLEventReader()) return ;
1207              */
1208             xmlInputSource = staxInputSource.getXMLInputSource() ;
1209             if (!fISCreatedByResolver) {
1210                 //let the not-LoadExternalDTD or not-SupportDTD process to handle the situation
1211                 if (fLoadExternalDTD) {
1212                     String accessError = SecuritySupport.checkAccess(expandedSystemId, fAccessExternalDTD, Constants.ACCESS_EXTERNAL_ALL);
1213                     if (accessError != null) {
1214                         fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
1215                                 "AccessExternalEntity",
1216                                 new Object[] { SecuritySupport.sanitizePath(expandedSystemId), accessError },
1217                                 XMLErrorReporter.SEVERITY_FATAL_ERROR);
1218                     }
1219                 }
1220             }
1221         }
1222         // wrap internal entity
1223         else {
1224             Entity.InternalEntity internalEntity = (Entity.InternalEntity)entity;
1225             Reader reader = new StringReader(internalEntity.text);
1226             xmlInputSource = new XMLInputSource(null, null, null, reader, null);
1227         }
1228 
1229         // start the entity
1230         startEntity(entityName, xmlInputSource, literal, external);
1231 
1232     } // startEntity(String,boolean)
1233 
1234     /**
1235      * Starts the document entity. The document entity has the "[xml]"
1236      * pseudo-name.
1237      *
1238      * @param xmlInputSource The input source of the document entity.
1239      *
1240      * @throws IOException  Thrown on i/o error.
1241      * @throws XNIException Thrown by entity handler to signal an error.
1242      */
1243     public void startDocumentEntity(XMLInputSource xmlInputSource)
1244     throws IOException, XNIException {
1245         startEntity(XMLEntity, xmlInputSource, false, true);
1246     } // startDocumentEntity(XMLInputSource)
1247 
1248     //xxx these methods are not required.
1249     /**
1250      * Starts the DTD entity. The DTD entity has the "[dtd]"
1251      * pseudo-name.
1252      *
1253      * @param xmlInputSource The input source of the DTD entity.
1254      *
1255      * @throws IOException  Thrown on i/o error.
1256      * @throws XNIException Thrown by entity handler to signal an error.
1257      */
1258     public void startDTDEntity(XMLInputSource xmlInputSource)
1259     throws IOException, XNIException {
1260         startEntity(DTDEntity, xmlInputSource, false, true);
1261     } // startDTDEntity(XMLInputSource)
1262 
1263     // indicate start of external subset so that
1264     // location of entity decls can be tracked
1265     public void startExternalSubset() {
1266         fInExternalSubset = true;
1267     }
1268 
1269     public void endExternalSubset() {
1270         fInExternalSubset = false;
1271     }
1272 
1273     /**
1274      * Starts an entity.
1275      * <p>
1276      * This method can be used to insert an application defined XML
1277      * entity stream into the parsing stream.
1278      *
1279      * @param name           The name of the entity.
1280      * @param xmlInputSource The input source of the entity.
1281      * @param literal        True if this entity is started within a
1282      *                       literal value.
1283      * @param isExternal    whether this entity should be treated as an internal or external entity.
1284      *
1285      * @throws IOException  Thrown on i/o error.
1286      * @throws XNIException Thrown by entity handler to signal an error.
1287      */
1288     public void startEntity(String name,
1289             XMLInputSource xmlInputSource,
1290             boolean literal, boolean isExternal)
1291             throws IOException, XNIException {
1292 
1293         String encoding = setupCurrentEntity(name, xmlInputSource, literal, isExternal);
1294 
1295         //when entity expansion limit is set by the Application, we need to
1296         //check for the entity expansion limit set by the parser, if number of entity
1297         //expansions exceeds the entity expansion limit, parser will throw fatal error.
1298         // Note that this represents the nesting level of open entities.
1299         fEntityExpansionCount++;
1300         if(fLimitAnalyzer != null) {
1301            fLimitAnalyzer.addValue(entityExpansionIndex, name, 1);
1302         }
1303         if( fSecurityManager != null && fSecurityManager.isOverLimit(entityExpansionIndex, fLimitAnalyzer)){
1304             fSecurityManager.debugPrint(fLimitAnalyzer);
1305             fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,"EntityExpansionLimitExceeded",
1306                     new Object[]{fSecurityManager.getLimitValueByIndex(entityExpansionIndex)},
1307                                              XMLErrorReporter.SEVERITY_FATAL_ERROR );
1308             // is there anything better to do than reset the counter?
1309             // at least one can envision debugging applications where this might
1310             // be useful...
1311             fEntityExpansionCount = 0;
1312         }
1313 
1314         // call handler
1315         if (fEntityHandler != null) {
1316             fEntityHandler.startEntity(name, fResourceIdentifier, encoding, null);
1317         }
1318 
1319     } // startEntity(String,XMLInputSource)
1320 
1321     /**
1322      * Return the current entity being scanned. Current entity is SET using startEntity function.
1323      * @return Entity.ScannedEntity
1324      */
1325 
1326     public Entity.ScannedEntity getCurrentEntity(){
1327         return fCurrentEntity ;
1328     }
1329 
1330     /**
1331      * Return the top level entity handled by this manager, or null
1332      * if no entity was added.
1333      */
1334     public Entity.ScannedEntity getTopLevelEntity() {
1335         return (Entity.ScannedEntity)
1336             (fEntityStack.empty() ? null : fEntityStack.elementAt(0));
1337     }
1338 
1339 
1340     /**
1341      * Close all opened InputStreams and Readers opened by this parser.
1342      */
1343     public void closeReaders() {
1344         /** this call actually does nothing, readers are closed in the endEntity method
1345          * through the current entity.
1346          * The change seems to have happened during the jdk6 development with the
1347          * addition of StAX
1348         **/
1349     }
1350 
1351     public void endEntity() throws IOException, XNIException {
1352 
1353         // call handler
1354         if (DEBUG_BUFFER) {
1355             System.out.print("(endEntity: ");
1356             print();
1357             System.out.println();
1358         }
1359         //pop the entity from the stack
1360         Entity.ScannedEntity entity = fEntityStack.size() > 0 ? (Entity.ScannedEntity)fEntityStack.pop() : null ;
1361 
1362         /** need to close the reader first since the program can end
1363          *  prematurely (e.g. fEntityHandler.endEntity may throw exception)
1364          *  leaving the reader open
1365          */
1366         //close the reader
1367         if(fCurrentEntity != null){
1368             //close the reader
1369             try{
1370                 if (fLimitAnalyzer != null) {
1371                     fLimitAnalyzer.endEntity(XMLSecurityManager.Limit.GENERAL_ENTITY_SIZE_LIMIT, fCurrentEntity.name);
1372                     if (fCurrentEntity.name.equals("[xml]")) {
1373                         fSecurityManager.debugPrint(fLimitAnalyzer);
1374                     }
1375                 }
1376                 fCurrentEntity.close();
1377             }catch(IOException ex){
1378                 throw new XNIException(ex);
1379             }
1380         }
1381 
1382         if (fEntityHandler != null) {
1383             //so this is the last opened entity, signal it to current fEntityHandler using Augmentation
1384             if(entity == null){
1385                 fEntityAugs.removeAllItems();
1386                 fEntityAugs.putItem(Constants.LAST_ENTITY, Boolean.TRUE);
1387                 fEntityHandler.endEntity(fCurrentEntity.name, fEntityAugs);
1388                 fEntityAugs.removeAllItems();
1389             }else{
1390                 fEntityHandler.endEntity(fCurrentEntity.name, null);
1391             }
1392         }
1393         //check if it is a document entity
1394         boolean documentEntity = fCurrentEntity.name == XMLEntity;
1395 
1396         //set popped entity as current entity
1397         fCurrentEntity = entity;
1398         fEntityScanner.setCurrentEntity(fCurrentEntity);
1399 
1400         //check if there are any entity left in the stack -- if there are
1401         //no entries EOF has been reached.
1402         // throw exception when it is the last entity but it is not a document entity
1403 
1404         if(fCurrentEntity == null & !documentEntity){
1405             throw new EOFException() ;
1406         }
1407 
1408         if (DEBUG_BUFFER) {
1409             System.out.print(")endEntity: ");
1410             print();
1411             System.out.println();
1412         }
1413 
1414     } // endEntity()
1415 
1416 
1417     //
1418     // XMLComponent methods
1419     //
1420     public void reset(PropertyManager propertyManager){
1421         //reset fEntityStorage
1422         fEntityStorage.reset(propertyManager);
1423         //reset XMLEntityReaderImpl
1424         fEntityScanner.reset(propertyManager);
1425         // xerces properties
1426         fSymbolTable = (SymbolTable)propertyManager.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY);
1427         fErrorReporter = (XMLErrorReporter)propertyManager.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY);
1428         try {
1429             fStaxEntityResolver = (StaxEntityResolverWrapper)propertyManager.getProperty(STAX_ENTITY_RESOLVER);
1430         } catch (XMLConfigurationException e) {
1431             fStaxEntityResolver = null;
1432         }
1433 
1434         // Zephyr feature ignore-external-dtd is the opposite of Xerces' load-external-dtd
1435         fLoadExternalDTD = !((Boolean)propertyManager.getProperty(Constants.ZEPHYR_PROPERTY_PREFIX + Constants.IGNORE_EXTERNAL_DTD)).booleanValue();
1436 
1437         // JAXP 1.5 feature
1438         XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager) propertyManager.getProperty(XML_SECURITY_PROPERTY_MANAGER);
1439         fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
1440 
1441         fSecurityManager = (XMLSecurityManager)propertyManager.getProperty(SECURITY_MANAGER);
1442 
1443         // initialize state
1444         //fStandalone = false;
1445         fEntities.clear();
1446         fEntityStack.removeAllElements();
1447         fCurrentEntity = null;
1448         fValidation = false;
1449         fExternalGeneralEntities = true;
1450         fExternalParameterEntities = true;
1451         fAllowJavaEncodings = true ;
1452     }
1453 
1454     /**
1455      * Resets the component. The component can query the component manager
1456      * about any features and properties that affect the operation of the
1457      * component.
1458      *
1459      * @param componentManager The component manager.
1460      *
1461      * @throws SAXException Thrown by component on initialization error.
1462      *                      For example, if a feature or property is
1463      *                      required for the operation of the component, the
1464      *                      component manager may throw a
1465      *                      SAXNotRecognizedException or a
1466      *                      SAXNotSupportedException.
1467      */
1468     public void reset(XMLComponentManager componentManager)
1469     throws XMLConfigurationException {
1470 
1471         boolean parser_settings = componentManager.getFeature(PARSER_SETTINGS, true);
1472 
1473         if (!parser_settings) {
1474             // parser settings have not been changed
1475             reset();
1476             if(fEntityScanner != null){
1477                 fEntityScanner.reset(componentManager);
1478             }
1479             if(fEntityStorage != null){
1480                 fEntityStorage.reset(componentManager);
1481             }
1482             return;
1483         }
1484 
1485         // sax features
1486         fValidation = componentManager.getFeature(VALIDATION, false);
1487         fExternalGeneralEntities = componentManager.getFeature(EXTERNAL_GENERAL_ENTITIES, true);
1488         fExternalParameterEntities = componentManager.getFeature(EXTERNAL_PARAMETER_ENTITIES, true);
1489 
1490         // xerces features
1491         fAllowJavaEncodings = componentManager.getFeature(ALLOW_JAVA_ENCODINGS, false);
1492         fWarnDuplicateEntityDef = componentManager.getFeature(WARN_ON_DUPLICATE_ENTITYDEF, false);
1493         fStrictURI = componentManager.getFeature(STANDARD_URI_CONFORMANT, false);
1494         fLoadExternalDTD = componentManager.getFeature(LOAD_EXTERNAL_DTD, true);
1495 
1496         // xerces properties
1497         fSymbolTable = (SymbolTable)componentManager.getProperty(SYMBOL_TABLE);
1498         fErrorReporter = (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER);
1499         fEntityResolver = (XMLEntityResolver)componentManager.getProperty(ENTITY_RESOLVER, null);
1500         fStaxEntityResolver = (StaxEntityResolverWrapper)componentManager.getProperty(STAX_ENTITY_RESOLVER, null);
1501         fValidationManager = (ValidationManager)componentManager.getProperty(VALIDATION_MANAGER, null);
1502         fSecurityManager = (XMLSecurityManager)componentManager.getProperty(SECURITY_MANAGER, null);
1503         entityExpansionIndex = fSecurityManager.getIndex(Constants.JDK_ENTITY_EXPANSION_LIMIT);
1504 
1505         // JAXP 1.5 feature
1506         XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager) componentManager.getProperty(XML_SECURITY_PROPERTY_MANAGER, null);
1507         if (spm == null) {
1508             spm = new XMLSecurityPropertyManager();
1509         }
1510         fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
1511 
1512         //reset general state
1513         reset();
1514 
1515         fEntityScanner.reset(componentManager);
1516         fEntityStorage.reset(componentManager);
1517 
1518     } // reset(XMLComponentManager)
1519 
1520     // reset general state.  Should not be called other than by
1521     // a class acting as a component manager but not
1522     // implementing that interface for whatever reason.
1523     public void reset() {
1524 
1525         // initialize state
1526         fStandalone = false;
1527         fEntities.clear();
1528         fEntityStack.removeAllElements();
1529         fEntityExpansionCount = 0;
1530 
1531         fCurrentEntity = null;
1532         // reset scanner
1533         if(fXML10EntityScanner != null){
1534             fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
1535         }
1536         if(fXML11EntityScanner != null) {
1537             fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter);
1538         }
1539 
1540         // DEBUG
1541         if (DEBUG_ENTITIES) {
1542             addInternalEntity("text", "Hello, World.");
1543             addInternalEntity("empty-element", "<foo/>");
1544             addInternalEntity("balanced-element", "<foo></foo>");
1545             addInternalEntity("balanced-element-with-text", "<foo>Hello, World</foo>");
1546             addInternalEntity("balanced-element-with-entity", "<foo>&text;</foo>");
1547             addInternalEntity("unbalanced-entity", "<foo>");
1548             addInternalEntity("recursive-entity", "<foo>&recursive-entity2;</foo>");
1549             addInternalEntity("recursive-entity2", "<bar>&recursive-entity3;</bar>");
1550             addInternalEntity("recursive-entity3", "<baz>&recursive-entity;</baz>");
1551             try {
1552                 addExternalEntity("external-text", null, "external-text.ent", "test/external-text.xml");
1553                 addExternalEntity("external-balanced-element", null, "external-balanced-element.ent", "test/external-balanced-element.xml");
1554                 addExternalEntity("one", null, "ent/one.ent", "test/external-entity.xml");
1555                 addExternalEntity("two", null, "ent/two.ent", "test/ent/one.xml");
1556             }
1557             catch (IOException ex) {
1558                 // should never happen
1559             }
1560         }
1561 
1562         fEntityHandler = null;
1563 
1564         // reset scanner
1565         //if(fEntityScanner!=null)
1566           //  fEntityScanner.reset(fSymbolTable, this,fErrorReporter);
1567 
1568     }
1569     /**
1570      * Returns a list of feature identifiers that are recognized by
1571      * this component. This method may return null if no features
1572      * are recognized by this component.
1573      */
1574     public String[] getRecognizedFeatures() {
1575         return (String[])(RECOGNIZED_FEATURES.clone());
1576     } // getRecognizedFeatures():String[]
1577 
1578     /**
1579      * Sets the state of a feature. This method is called by the component
1580      * manager any time after reset when a feature changes state.
1581      * <p>
1582      * <strong>Note:</strong> Components should silently ignore features
1583      * that do not affect the operation of the component.
1584      *
1585      * @param featureId The feature identifier.
1586      * @param state     The state of the feature.
1587      *
1588      * @throws SAXNotRecognizedException The component should not throw
1589      *                                   this exception.
1590      * @throws SAXNotSupportedException The component should not throw
1591      *                                  this exception.
1592      */
1593     public void setFeature(String featureId, boolean state)
1594     throws XMLConfigurationException {
1595 
1596         // xerces features
1597         if (featureId.startsWith(Constants.XERCES_FEATURE_PREFIX)) {
1598             final int suffixLength = featureId.length() - Constants.XERCES_FEATURE_PREFIX.length();
1599             if (suffixLength == Constants.ALLOW_JAVA_ENCODINGS_FEATURE.length() &&
1600                 featureId.endsWith(Constants.ALLOW_JAVA_ENCODINGS_FEATURE)) {
1601                 fAllowJavaEncodings = state;
1602             }
1603             if (suffixLength == Constants.LOAD_EXTERNAL_DTD_FEATURE.length() &&
1604                 featureId.endsWith(Constants.LOAD_EXTERNAL_DTD_FEATURE)) {
1605                 fLoadExternalDTD = state;
1606                 return;
1607             }
1608         }
1609 
1610     } // setFeature(String,boolean)
1611 
1612     /**
1613      * Sets the value of a property. This method is called by the component
1614      * manager any time after reset when a property changes value.
1615      * <p>
1616      * <strong>Note:</strong> Components should silently ignore properties
1617      * that do not affect the operation of the component.
1618      *
1619      * @param propertyId The property identifier.
1620      * @param value      The value of the property.
1621      *
1622      * @throws SAXNotRecognizedException The component should not throw
1623      *                                   this exception.
1624      * @throws SAXNotSupportedException The component should not throw
1625      *                                  this exception.
1626      */
1627     public void setProperty(String propertyId, Object value){
1628         // Xerces properties
1629         if (propertyId.startsWith(Constants.XERCES_PROPERTY_PREFIX)) {
1630             final int suffixLength = propertyId.length() - Constants.XERCES_PROPERTY_PREFIX.length();
1631 
1632             if (suffixLength == Constants.SYMBOL_TABLE_PROPERTY.length() &&
1633                 propertyId.endsWith(Constants.SYMBOL_TABLE_PROPERTY)) {
1634                 fSymbolTable = (SymbolTable)value;
1635                 return;
1636             }
1637             if (suffixLength == Constants.ERROR_REPORTER_PROPERTY.length() &&
1638                 propertyId.endsWith(Constants.ERROR_REPORTER_PROPERTY)) {
1639                 fErrorReporter = (XMLErrorReporter)value;
1640                 return;
1641             }
1642             if (suffixLength == Constants.ENTITY_RESOLVER_PROPERTY.length() &&
1643                 propertyId.endsWith(Constants.ENTITY_RESOLVER_PROPERTY)) {
1644                 fEntityResolver = (XMLEntityResolver)value;
1645                 return;
1646             }
1647             if (suffixLength == Constants.BUFFER_SIZE_PROPERTY.length() &&
1648                 propertyId.endsWith(Constants.BUFFER_SIZE_PROPERTY)) {
1649                 Integer bufferSize = (Integer)value;
1650                 if (bufferSize != null &&
1651                     bufferSize.intValue() > DEFAULT_XMLDECL_BUFFER_SIZE) {
1652                     fBufferSize = bufferSize.intValue();
1653                     fEntityScanner.setBufferSize(fBufferSize);
1654                     fBufferPool.setExternalBufferSize(fBufferSize);
1655                 }
1656             }
1657             if (suffixLength == Constants.SECURITY_MANAGER_PROPERTY.length() &&
1658                 propertyId.endsWith(Constants.SECURITY_MANAGER_PROPERTY)) {
1659                 fSecurityManager = (XMLSecurityManager)value;
1660             }
1661         }
1662 
1663         //JAXP 1.5 properties
1664         if (propertyId.equals(XML_SECURITY_PROPERTY_MANAGER))
1665         {
1666             XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager)value;
1667             fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
1668         }
1669     }
1670 
1671     public void setLimitAnalyzer(XMLLimitAnalyzer fLimitAnalyzer) {
1672         this.fLimitAnalyzer = fLimitAnalyzer;
1673     }
1674 
1675     /**
1676      * Returns a list of property identifiers that are recognized by
1677      * this component. This method may return null if no properties
1678      * are recognized by this component.
1679      */
1680     public String[] getRecognizedProperties() {
1681         return (String[])(RECOGNIZED_PROPERTIES.clone());
1682     } // getRecognizedProperties():String[]
1683     /**
1684      * Returns the default state for a feature, or null if this
1685      * component does not want to report a default value for this
1686      * feature.
1687      *
1688      * @param featureId The feature identifier.
1689      *
1690      * @since Xerces 2.2.0
1691      */
1692     public Boolean getFeatureDefault(String featureId) {
1693         for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
1694             if (RECOGNIZED_FEATURES[i].equals(featureId)) {
1695                 return FEATURE_DEFAULTS[i];
1696             }
1697         }
1698         return null;
1699     } // getFeatureDefault(String):Boolean
1700 
1701     /**
1702      * Returns the default state for a property, or null if this
1703      * component does not want to report a default value for this
1704      * property.
1705      *
1706      * @param propertyId The property identifier.
1707      *
1708      * @since Xerces 2.2.0
1709      */
1710     public Object getPropertyDefault(String propertyId) {
1711         for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
1712             if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
1713                 return PROPERTY_DEFAULTS[i];
1714             }
1715         }
1716         return null;
1717     } // getPropertyDefault(String):Object
1718 
1719     //
1720     // Public static methods
1721     //
1722 
1723     /**
1724      * Expands a system id and returns the system id as a URI, if
1725      * it can be expanded. A return value of null means that the
1726      * identifier is already expanded. An exception thrown
1727      * indicates a failure to expand the id.
1728      *
1729      * @param systemId The systemId to be expanded.
1730      *
1731      * @return Returns the URI string representing the expanded system
1732      *         identifier. A null value indicates that the given
1733      *         system identifier is already expanded.
1734      *
1735      */
1736     public static String expandSystemId(String systemId) {
1737         return expandSystemId(systemId, null);
1738     } // expandSystemId(String):String
1739 
1740     //
1741     // Public static methods
1742     //
1743 
1744     // current value of the "user.dir" property
1745     private static String gUserDir;
1746     // cached URI object for the current value of the escaped "user.dir" property stored as a URI
1747     private static URI gUserDirURI;
1748     // which ASCII characters need to be escaped
1749     private static boolean gNeedEscaping[] = new boolean[128];
1750     // the first hex character if a character needs to be escaped
1751     private static char gAfterEscaping1[] = new char[128];
1752     // the second hex character if a character needs to be escaped
1753     private static char gAfterEscaping2[] = new char[128];
1754     private static char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7',
1755                                      '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
1756     // initialize the above 3 arrays
1757     static {
1758         for (int i = 0; i <= 0x1f; i++) {
1759             gNeedEscaping[i] = true;
1760             gAfterEscaping1[i] = gHexChs[i >> 4];
1761             gAfterEscaping2[i] = gHexChs[i & 0xf];
1762         }
1763         gNeedEscaping[0x7f] = true;
1764         gAfterEscaping1[0x7f] = '7';
1765         gAfterEscaping2[0x7f] = 'F';
1766         char[] escChs = {' ', '<', '>', '#', '%', '"', '{', '}',
1767                          '|', '\\', '^', '~', '[', ']', '`'};
1768         int len = escChs.length;
1769         char ch;
1770         for (int i = 0; i < len; i++) {
1771             ch = escChs[i];
1772             gNeedEscaping[ch] = true;
1773             gAfterEscaping1[ch] = gHexChs[ch >> 4];
1774             gAfterEscaping2[ch] = gHexChs[ch & 0xf];
1775         }
1776     }
1777 
1778     // To escape the "user.dir" system property, by using %HH to represent
1779     // special ASCII characters: 0x00~0x1F, 0x7F, ' ', '<', '>', '#', '%'
1780     // and '"'. It's a static method, so needs to be synchronized.
1781     // this method looks heavy, but since the system property isn't expected
1782     // to change often, so in most cases, we only need to return the URI
1783     // that was escaped before.
1784     // According to the URI spec, non-ASCII characters (whose value >= 128)
1785     // need to be escaped too.
1786     // REVISIT: don't know how to escape non-ASCII characters, especially
1787     // which encoding to use. Leave them for now.
1788     private static synchronized URI getUserDir() throws URI.MalformedURIException {
1789         // get the user.dir property
1790         String userDir = "";
1791         try {
1792             userDir = SecuritySupport.getSystemProperty("user.dir");
1793         }
1794         catch (SecurityException se) {
1795         }
1796 
1797         // return empty string if property value is empty string.
1798         if (userDir.length() == 0)
1799             return new URI("file", "", "", null, null);
1800         // compute the new escaped value if the new property value doesn't
1801         // match the previous one
1802         if (gUserDirURI != null && userDir.equals(gUserDir)) {
1803             return gUserDirURI;
1804         }
1805 
1806         // record the new value as the global property value
1807         gUserDir = userDir;
1808 
1809         char separator = java.io.File.separatorChar;
1810         userDir = userDir.replace(separator, '/');
1811 
1812         int len = userDir.length(), ch;
1813         StringBuffer buffer = new StringBuffer(len*3);
1814         // change C:/blah to /C:/blah
1815         if (len >= 2 && userDir.charAt(1) == ':') {
1816             ch = Character.toUpperCase(userDir.charAt(0));
1817             if (ch >= 'A' && ch <= 'Z') {
1818                 buffer.append('/');
1819             }
1820         }
1821 
1822         // for each character in the path
1823         int i = 0;
1824         for (; i < len; i++) {
1825             ch = userDir.charAt(i);
1826             // if it's not an ASCII character, break here, and use UTF-8 encoding
1827             if (ch >= 128)
1828                 break;
1829             if (gNeedEscaping[ch]) {
1830                 buffer.append('%');
1831                 buffer.append(gAfterEscaping1[ch]);
1832                 buffer.append(gAfterEscaping2[ch]);
1833                 // record the fact that it's escaped
1834             }
1835             else {
1836                 buffer.append((char)ch);
1837             }
1838         }
1839 
1840         // we saw some non-ascii character
1841         if (i < len) {
1842             // get UTF-8 bytes for the remaining sub-string
1843             byte[] bytes = null;
1844             byte b;
1845             try {
1846                 bytes = userDir.substring(i).getBytes("UTF-8");
1847             } catch (java.io.UnsupportedEncodingException e) {
1848                 // should never happen
1849                 return new URI("file", "", userDir, null, null);
1850             }
1851             len = bytes.length;
1852 
1853             // for each byte
1854             for (i = 0; i < len; i++) {
1855                 b = bytes[i];
1856                 // for non-ascii character: make it positive, then escape
1857                 if (b < 0) {
1858                     ch = b + 256;
1859                     buffer.append('%');
1860                     buffer.append(gHexChs[ch >> 4]);
1861                     buffer.append(gHexChs[ch & 0xf]);
1862                 }
1863                 else if (gNeedEscaping[b]) {
1864                     buffer.append('%');
1865                     buffer.append(gAfterEscaping1[b]);
1866                     buffer.append(gAfterEscaping2[b]);
1867                 }
1868                 else {
1869                     buffer.append((char)b);
1870                 }
1871             }
1872         }
1873 
1874         // change blah/blah to blah/blah/
1875         if (!userDir.endsWith("/"))
1876             buffer.append('/');
1877 
1878         gUserDirURI = new URI("file", "", buffer.toString(), null, null);
1879 
1880         return gUserDirURI;
1881     }
1882 
1883     /**
1884      * Absolutizes a URI using the current value
1885      * of the "user.dir" property as the base URI. If
1886      * the URI is already absolute, this is a no-op.
1887      *
1888      * @param uri the URI to absolutize
1889      */
1890     public static void absolutizeAgainstUserDir(URI uri)
1891         throws URI.MalformedURIException {
1892         uri.absolutize(getUserDir());
1893     }
1894 
1895     /**
1896      * Expands a system id and returns the system id as a URI, if
1897      * it can be expanded. A return value of null means that the
1898      * identifier is already expanded. An exception thrown
1899      * indicates a failure to expand the id.
1900      *
1901      * @param systemId The systemId to be expanded.
1902      *
1903      * @return Returns the URI string representing the expanded system
1904      *         identifier. A null value indicates that the given
1905      *         system identifier is already expanded.
1906      *
1907      */
1908     public static String expandSystemId(String systemId, String baseSystemId) {
1909 
1910         // check for bad parameters id
1911         if (systemId == null || systemId.length() == 0) {
1912             return systemId;
1913         }
1914         // if id already expanded, return
1915         try {
1916             URI uri = new URI(systemId);
1917             if (uri != null) {
1918                 return systemId;
1919             }
1920         } catch (URI.MalformedURIException e) {
1921             // continue on...
1922         }
1923         // normalize id
1924         String id = fixURI(systemId);
1925 
1926         // normalize base
1927         URI base = null;
1928         URI uri = null;
1929         try {
1930             if (baseSystemId == null || baseSystemId.length() == 0 ||
1931                     baseSystemId.equals(systemId)) {
1932                 String dir = getUserDir().toString();
1933                 base = new URI("file", "", dir, null, null);
1934             } else {
1935                 try {
1936                     base = new URI(fixURI(baseSystemId));
1937                 } catch (URI.MalformedURIException e) {
1938                     if (baseSystemId.indexOf(':') != -1) {
1939                         // for xml schemas we might have baseURI with
1940                         // a specified drive
1941                         base = new URI("file", "", fixURI(baseSystemId), null, null);
1942                     } else {
1943                         String dir = getUserDir().toString();
1944                         dir = dir + fixURI(baseSystemId);
1945                         base = new URI("file", "", dir, null, null);
1946                     }
1947                 }
1948             }
1949             // expand id
1950             uri = new URI(base, id);
1951         } catch (Exception e) {
1952             // let it go through
1953 
1954         }
1955 
1956         if (uri == null) {
1957             return systemId;
1958         }
1959         return uri.toString();
1960 
1961     } // expandSystemId(String,String):String
1962 
1963     /**
1964      * Expands a system id and returns the system id as a URI, if
1965      * it can be expanded. A return value of null means that the
1966      * identifier is already expanded. An exception thrown
1967      * indicates a failure to expand the id.
1968      *
1969      * @param systemId The systemId to be expanded.
1970      *
1971      * @return Returns the URI string representing the expanded system
1972      *         identifier. A null value indicates that the given
1973      *         system identifier is already expanded.
1974      *
1975      */
1976     public static String expandSystemId(String systemId, String baseSystemId,
1977                                         boolean strict)
1978             throws URI.MalformedURIException {
1979 
1980         // check if there is a system id before
1981         // trying to expand it.
1982         if (systemId == null) {
1983             return null;
1984         }
1985 
1986         // system id has to be a valid URI
1987         if (strict) {
1988 
1989 
1990             // check if there is a system id before
1991             // trying to expand it.
1992             if (systemId == null) {
1993                 return null;
1994             }
1995 
1996             try {
1997                 // if it's already an absolute one, return it
1998                 new URI(systemId);
1999                 return systemId;
2000             }
2001             catch (URI.MalformedURIException ex) {
2002             }
2003             URI base = null;
2004             // if there isn't a base uri, use the working directory
2005             if (baseSystemId == null || baseSystemId.length() == 0) {
2006                 base = new URI("file", "", getUserDir().toString(), null, null);
2007             }
2008             // otherwise, use the base uri
2009             else {
2010                 try {
2011                     base = new URI(baseSystemId);
2012                 }
2013                 catch (URI.MalformedURIException e) {
2014                     // assume "base" is also a relative uri
2015                     String dir = getUserDir().toString();
2016                     dir = dir + baseSystemId;
2017                     base = new URI("file", "", dir, null, null);
2018                 }
2019             }
2020             // absolutize the system id using the base
2021             URI uri = new URI(base, systemId);
2022             // return the string rep of the new uri (an absolute one)
2023             return uri.toString();
2024 
2025             // if any exception is thrown, it'll get thrown to the caller.
2026         }
2027 
2028         // Assume the URIs are well-formed. If it turns out they're not, try fixing them up.
2029         try {
2030              return expandSystemIdStrictOff(systemId, baseSystemId);
2031         }
2032         catch (URI.MalformedURIException e) {
2033             /** Xerces URI rejects unicode, try java.net.URI
2034              * this is not ideal solution, but it covers known cases which either
2035              * Xerces URI or java.net.URI can handle alone
2036              * will file bug against java.net.URI
2037              */
2038             try {
2039                 return expandSystemIdStrictOff1(systemId, baseSystemId);
2040             } catch (URISyntaxException ex) {
2041                 // continue on...
2042             }
2043         }
2044         // check for bad parameters id
2045         if (systemId.length() == 0) {
2046             return systemId;
2047         }
2048 
2049         // normalize id
2050         String id = fixURI(systemId);
2051 
2052         // normalize base
2053         URI base = null;
2054         URI uri = null;
2055         try {
2056             if (baseSystemId == null || baseSystemId.length() == 0 ||
2057                 baseSystemId.equals(systemId)) {
2058                 base = getUserDir();
2059             }
2060             else {
2061                 try {
2062                     base = new URI(fixURI(baseSystemId).trim());
2063                 }
2064                 catch (URI.MalformedURIException e) {
2065                     if (baseSystemId.indexOf(':') != -1) {
2066                         // for xml schemas we might have baseURI with
2067                         // a specified drive
2068                         base = new URI("file", "", fixURI(baseSystemId).trim(), null, null);
2069                     }
2070                     else {
2071                         base = new URI(getUserDir(), fixURI(baseSystemId));
2072                     }
2073                 }
2074              }
2075              // expand id
2076              uri = new URI(base, id.trim());
2077         }
2078         catch (Exception e) {
2079             // let it go through
2080 
2081         }
2082 
2083         if (uri == null) {
2084             return systemId;
2085         }
2086         return uri.toString();
2087 
2088     } // expandSystemId(String,String,boolean):String
2089 
2090     /**
2091      * Helper method for expandSystemId(String,String,boolean):String
2092      */
2093     private static String expandSystemIdStrictOn(String systemId, String baseSystemId)
2094         throws URI.MalformedURIException {
2095 
2096         URI systemURI = new URI(systemId, true);
2097         // If it's already an absolute one, return it
2098         if (systemURI.isAbsoluteURI()) {
2099             return systemId;
2100         }
2101 
2102         // If there isn't a base URI, use the working directory
2103         URI baseURI = null;
2104         if (baseSystemId == null || baseSystemId.length() == 0) {
2105             baseURI = getUserDir();
2106         }
2107         else {
2108             baseURI = new URI(baseSystemId, true);
2109             if (!baseURI.isAbsoluteURI()) {
2110                 // assume "base" is also a relative uri
2111                 baseURI.absolutize(getUserDir());
2112             }
2113         }
2114 
2115         // absolutize the system identifier using the base URI
2116         systemURI.absolutize(baseURI);
2117 
2118         // return the string rep of the new uri (an absolute one)
2119         return systemURI.toString();
2120 
2121         // if any exception is thrown, it'll get thrown to the caller.
2122 
2123     } // expandSystemIdStrictOn(String,String):String
2124 
2125     /**
2126      * Attempt to set whether redirects will be followed for an <code>HttpURLConnection</code>.
2127      * This may fail on earlier JDKs which do not support setting this preference.
2128      */
2129     public static void setInstanceFollowRedirects(HttpURLConnection urlCon, boolean followRedirects) {
2130         try {
2131             Method method = HttpURLConnection.class.getMethod("setInstanceFollowRedirects", new Class[] {Boolean.TYPE});
2132             method.invoke(urlCon, new Object[] {followRedirects ? Boolean.TRUE : Boolean.FALSE});
2133         }
2134         // setInstanceFollowRedirects doesn't exist.
2135         catch (Exception exc) {}
2136     }
2137 
2138 
2139     /**
2140      * Helper method for expandSystemId(String,String,boolean):String
2141      */
2142     private static String expandSystemIdStrictOff(String systemId, String baseSystemId)
2143         throws URI.MalformedURIException {
2144 
2145         URI systemURI = new URI(systemId, true);
2146         // If it's already an absolute one, return it
2147         if (systemURI.isAbsoluteURI()) {
2148             if (systemURI.getScheme().length() > 1) {
2149                 return systemId;
2150             }
2151             /**
2152              * If the scheme's length is only one character,
2153              * it's likely that this was intended as a file
2154              * path. Fixing this up in expandSystemId to
2155              * maintain backwards compatibility.
2156              */
2157             throw new URI.MalformedURIException();
2158         }
2159 
2160         // If there isn't a base URI, use the working directory
2161         URI baseURI = null;
2162         if (baseSystemId == null || baseSystemId.length() == 0) {
2163             baseURI = getUserDir();
2164         }
2165         else {
2166             baseURI = new URI(baseSystemId, true);
2167             if (!baseURI.isAbsoluteURI()) {
2168                 // assume "base" is also a relative uri
2169                 baseURI.absolutize(getUserDir());
2170             }
2171         }
2172 
2173         // absolutize the system identifier using the base URI
2174         systemURI.absolutize(baseURI);
2175 
2176         // return the string rep of the new uri (an absolute one)
2177         return systemURI.toString();
2178 
2179         // if any exception is thrown, it'll get thrown to the caller.
2180 
2181     } // expandSystemIdStrictOff(String,String):String
2182 
2183     private static String expandSystemIdStrictOff1(String systemId, String baseSystemId)
2184         throws URISyntaxException, URI.MalformedURIException {
2185 
2186             java.net.URI systemURI = new java.net.URI(systemId);
2187         // If it's already an absolute one, return it
2188         if (systemURI.isAbsolute()) {
2189             if (systemURI.getScheme().length() > 1) {
2190                 return systemId;
2191             }
2192             /**
2193              * If the scheme's length is only one character,
2194              * it's likely that this was intended as a file
2195              * path. Fixing this up in expandSystemId to
2196              * maintain backwards compatibility.
2197              */
2198             throw new URISyntaxException(systemId, "the scheme's length is only one character");
2199         }
2200 
2201         // If there isn't a base URI, use the working directory
2202         URI baseURI = null;
2203         if (baseSystemId == null || baseSystemId.length() == 0) {
2204             baseURI = getUserDir();
2205         }
2206         else {
2207             baseURI = new URI(baseSystemId, true);
2208             if (!baseURI.isAbsoluteURI()) {
2209                 // assume "base" is also a relative uri
2210                 baseURI.absolutize(getUserDir());
2211             }
2212         }
2213 
2214         // absolutize the system identifier using the base URI
2215 //        systemURI.absolutize(baseURI);
2216         systemURI = (new java.net.URI(baseURI.toString())).resolve(systemURI);
2217 
2218         // return the string rep of the new uri (an absolute one)
2219         return systemURI.toString();
2220 
2221         // if any exception is thrown, it'll get thrown to the caller.
2222 
2223     } // expandSystemIdStrictOff(String,String):String
2224 
2225     //
2226     // Protected methods
2227     //
2228 
2229 
2230     /**
2231      * Returns the IANA encoding name that is auto-detected from
2232      * the bytes specified, with the endian-ness of that encoding where appropriate.
2233      *
2234      * @param b4    The first four bytes of the input.
2235      * @param count The number of bytes actually read.
2236      * @return a 2-element array:  the first element, an IANA-encoding string,
2237      *  the second element a Boolean which is true iff the document is big endian, false
2238      *  if it's little-endian, and null if the distinction isn't relevant.
2239      */
2240     protected Object[] getEncodingName(byte[] b4, int count) {
2241 
2242         if (count < 2) {
2243             return defaultEncoding;
2244         }
2245 
2246         // UTF-16, with BOM
2247         int b0 = b4[0] & 0xFF;
2248         int b1 = b4[1] & 0xFF;
2249         if (b0 == 0xFE && b1 == 0xFF) {
2250             // UTF-16, big-endian
2251             return new Object [] {"UTF-16BE", new Boolean(true)};
2252         }
2253         if (b0 == 0xFF && b1 == 0xFE) {
2254             // UTF-16, little-endian
2255             return new Object [] {"UTF-16LE", new Boolean(false)};
2256         }
2257 
2258         // default to UTF-8 if we don't have enough bytes to make a
2259         // good determination of the encoding
2260         if (count < 3) {
2261             return defaultEncoding;
2262         }
2263 
2264         // UTF-8 with a BOM
2265         int b2 = b4[2] & 0xFF;
2266         if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
2267             return defaultEncoding;
2268         }
2269 
2270         // default to UTF-8 if we don't have enough bytes to make a
2271         // good determination of the encoding
2272         if (count < 4) {
2273             return defaultEncoding;
2274         }
2275 
2276         // other encodings
2277         int b3 = b4[3] & 0xFF;
2278         if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) {
2279             // UCS-4, big endian (1234)
2280             return new Object [] {"ISO-10646-UCS-4", new Boolean(true)};
2281         }
2282         if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) {
2283             // UCS-4, little endian (4321)
2284             return new Object [] {"ISO-10646-UCS-4", new Boolean(false)};
2285         }
2286         if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) {
2287             // UCS-4, unusual octet order (2143)
2288             // REVISIT: What should this be?
2289             return new Object [] {"ISO-10646-UCS-4", null};
2290         }
2291         if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) {
2292             // UCS-4, unusual octect order (3412)
2293             // REVISIT: What should this be?
2294             return new Object [] {"ISO-10646-UCS-4", null};
2295         }
2296         if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
2297             // UTF-16, big-endian, no BOM
2298             // (or could turn out to be UCS-2...
2299             // REVISIT: What should this be?
2300             return new Object [] {"UTF-16BE", new Boolean(true)};
2301         }
2302         if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
2303             // UTF-16, little-endian, no BOM
2304             // (or could turn out to be UCS-2...
2305             return new Object [] {"UTF-16LE", new Boolean(false)};
2306         }
2307         if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) {
2308             // EBCDIC
2309             // a la xerces1, return CP037 instead of EBCDIC here
2310             return new Object [] {"CP037", null};
2311         }
2312 
2313         return defaultEncoding;
2314 
2315     } // getEncodingName(byte[],int):Object[]
2316 
2317     /**
2318      * Creates a reader capable of reading the given input stream in
2319      * the specified encoding.
2320      *
2321      * @param inputStream  The input stream.
2322      * @param encoding     The encoding name that the input stream is
2323      *                     encoded using. If the user has specified that
2324      *                     Java encoding names are allowed, then the
2325      *                     encoding name may be a Java encoding name;
2326      *                     otherwise, it is an ianaEncoding name.
2327      * @param isBigEndian   For encodings (like uCS-4), whose names cannot
2328      *                      specify a byte order, this tells whether the order is bigEndian.  null menas
2329      *                      unknown or not relevant.
2330      *
2331      * @return Returns a reader.
2332      */
2333     protected Reader createReader(InputStream inputStream, String encoding, Boolean isBigEndian)
2334     throws IOException {
2335 
2336         // normalize encoding name
2337         if (encoding == null) {
2338             encoding = "UTF-8";
2339         }
2340 
2341         // try to use an optimized reader
2342         String ENCODING = encoding.toUpperCase(Locale.ENGLISH);
2343         if (ENCODING.equals("UTF-8")) {
2344             if (DEBUG_ENCODINGS) {
2345                 System.out.println("$$$ creating UTF8Reader");
2346             }
2347             return new UTF8Reader(inputStream, fBufferSize, fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN), fErrorReporter.getLocale() );
2348         }
2349         if (ENCODING.equals("US-ASCII")) {
2350             if (DEBUG_ENCODINGS) {
2351                 System.out.println("$$$ creating ASCIIReader");
2352             }
2353             return new ASCIIReader(inputStream, fBufferSize, fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN), fErrorReporter.getLocale());
2354         }
2355         if(ENCODING.equals("ISO-10646-UCS-4")) {
2356             if(isBigEndian != null) {
2357                 boolean isBE = isBigEndian.booleanValue();
2358                 if(isBE) {
2359                     return new UCSReader(inputStream, UCSReader.UCS4BE);
2360                 } else {
2361                     return new UCSReader(inputStream, UCSReader.UCS4LE);
2362                 }
2363             } else {
2364                 fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
2365                         "EncodingByteOrderUnsupported",
2366                         new Object[] { encoding },
2367                         XMLErrorReporter.SEVERITY_FATAL_ERROR);
2368             }
2369         }
2370         if(ENCODING.equals("ISO-10646-UCS-2")) {
2371             if(isBigEndian != null) { // sould never happen with this encoding...
2372                 boolean isBE = isBigEndian.booleanValue();
2373                 if(isBE) {
2374                     return new UCSReader(inputStream, UCSReader.UCS2BE);
2375                 } else {
2376                     return new UCSReader(inputStream, UCSReader.UCS2LE);
2377                 }
2378             } else {
2379                 fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
2380                         "EncodingByteOrderUnsupported",
2381                         new Object[] { encoding },
2382                         XMLErrorReporter.SEVERITY_FATAL_ERROR);
2383             }
2384         }
2385 
2386         // check for valid name
2387         boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
2388         boolean validJava = XMLChar.isValidJavaEncoding(encoding);
2389         if (!validIANA || (fAllowJavaEncodings && !validJava)) {
2390             fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
2391                     "EncodingDeclInvalid",
2392                     new Object[] { encoding },
2393                     XMLErrorReporter.SEVERITY_FATAL_ERROR);
2394                     // NOTE: AndyH suggested that, on failure, we use ISO Latin 1
2395                     //       because every byte is a valid ISO Latin 1 character.
2396                     //       It may not translate correctly but if we failed on
2397                     //       the encoding anyway, then we're expecting the content
2398                     //       of the document to be bad. This will just prevent an
2399                     //       invalid UTF-8 sequence to be detected. This is only
2400                     //       important when continue-after-fatal-error is turned
2401                     //       on. -Ac
2402                     encoding = "ISO-8859-1";
2403         }
2404 
2405         // try to use a Java reader
2406         String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING);
2407         if (javaEncoding == null) {
2408             if(fAllowJavaEncodings) {
2409                 javaEncoding = encoding;
2410             } else {
2411                 fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
2412                         "EncodingDeclInvalid",
2413                         new Object[] { encoding },
2414                         XMLErrorReporter.SEVERITY_FATAL_ERROR);
2415                         // see comment above.
2416                         javaEncoding = "ISO8859_1";
2417             }
2418         }
2419         if (DEBUG_ENCODINGS) {
2420             System.out.print("$$$ creating Java InputStreamReader: encoding="+javaEncoding);
2421             if (javaEncoding == encoding) {
2422                 System.out.print(" (IANA encoding)");
2423             }
2424             System.out.println();
2425         }
2426         return new BufferedReader( new InputStreamReader(inputStream, javaEncoding));
2427 
2428     } // createReader(InputStream,String, Boolean): Reader
2429 
2430 
2431     /**
2432      * Return the public identifier for the current document event.
2433      * <p>
2434      * The return value is the public identifier of the document
2435      * entity or of the external parsed entity in which the markup
2436      * triggering the event appears.
2437      *
2438      * @return A string containing the public identifier, or
2439      *         null if none is available.
2440      */
2441     public String getPublicId() {
2442         return (fCurrentEntity != null && fCurrentEntity.entityLocation != null) ? fCurrentEntity.entityLocation.getPublicId() : null;
2443     } // getPublicId():String
2444 
2445     /**
2446      * Return the expanded system identifier for the current document event.
2447      * <p>
2448      * The return value is the expanded system identifier of the document
2449      * entity or of the external parsed entity in which the markup
2450      * triggering the event appears.
2451      * <p>
2452      * If the system identifier is a URL, the parser must resolve it
2453      * fully before passing it to the application.
2454      *
2455      * @return A string containing the expanded system identifier, or null
2456      *         if none is available.
2457      */
2458     public String getExpandedSystemId() {
2459         if (fCurrentEntity != null) {
2460             if (fCurrentEntity.entityLocation != null &&
2461                     fCurrentEntity.entityLocation.getExpandedSystemId() != null ) {
2462                 return fCurrentEntity.entityLocation.getExpandedSystemId();
2463             } else {
2464                 // search for the first external entity on the stack
2465                 int size = fEntityStack.size();
2466                 for (int i = size - 1; i >= 0 ; i--) {
2467                     Entity.ScannedEntity externalEntity =
2468                             (Entity.ScannedEntity)fEntityStack.elementAt(i);
2469 
2470                     if (externalEntity.entityLocation != null &&
2471                             externalEntity.entityLocation.getExpandedSystemId() != null) {
2472                         return externalEntity.entityLocation.getExpandedSystemId();
2473                     }
2474                 }
2475             }
2476         }
2477         return null;
2478     } // getExpandedSystemId():String
2479 
2480     /**
2481      * Return the literal system identifier for the current document event.
2482      * <p>
2483      * The return value is the literal system identifier of the document
2484      * entity or of the external parsed entity in which the markup
2485      * triggering the event appears.
2486      * <p>
2487      * @return A string containing the literal system identifier, or null
2488      *         if none is available.
2489      */
2490     public String getLiteralSystemId() {
2491         if (fCurrentEntity != null) {
2492             if (fCurrentEntity.entityLocation != null &&
2493                     fCurrentEntity.entityLocation.getLiteralSystemId() != null ) {
2494                 return fCurrentEntity.entityLocation.getLiteralSystemId();
2495             } else {
2496                 // search for the first external entity on the stack
2497                 int size = fEntityStack.size();
2498                 for (int i = size - 1; i >= 0 ; i--) {
2499                     Entity.ScannedEntity externalEntity =
2500                             (Entity.ScannedEntity)fEntityStack.elementAt(i);
2501 
2502                     if (externalEntity.entityLocation != null &&
2503                             externalEntity.entityLocation.getLiteralSystemId() != null) {
2504                         return externalEntity.entityLocation.getLiteralSystemId();
2505                     }
2506                 }
2507             }
2508         }
2509         return null;
2510     } // getLiteralSystemId():String
2511 
2512     /**
2513      * Return the line number where the current document event ends.
2514      * <p>
2515      * <strong>Warning:</strong> The return value from the method
2516      * is intended only as an approximation for the sake of error
2517      * reporting; it is not intended to provide sufficient information
2518      * to edit the character content of the original XML document.
2519      * <p>
2520      * The return value is an approximation of the line number
2521      * in the document entity or external parsed entity where the
2522      * markup triggering the event appears.
2523      * <p>
2524      * If possible, the SAX driver should provide the line position
2525      * of the first character after the text associated with the document
2526      * event.  The first line in the document is line 1.
2527      *
2528      * @return The line number, or -1 if none is available.
2529      */
2530     public int getLineNumber() {
2531         if (fCurrentEntity != null) {
2532             if (fCurrentEntity.isExternal()) {
2533                 return fCurrentEntity.lineNumber;
2534             } else {
2535                 // search for the first external entity on the stack
2536                 int size = fEntityStack.size();
2537                 for (int i=size-1; i>0 ; i--) {
2538                     Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity)fEntityStack.elementAt(i);
2539                     if (firstExternalEntity.isExternal()) {
2540                         return firstExternalEntity.lineNumber;
2541                     }
2542                 }
2543             }
2544         }
2545 
2546         return -1;
2547 
2548     } // getLineNumber():int
2549 
2550     /**
2551      * Return the column number where the current document event ends.
2552      * <p>
2553      * <strong>Warning:</strong> The return value from the method
2554      * is intended only as an approximation for the sake of error
2555      * reporting; it is not intended to provide sufficient information
2556      * to edit the character content of the original XML document.
2557      * <p>
2558      * The return value is an approximation of the column number
2559      * in the document entity or external parsed entity where the
2560      * markup triggering the event appears.
2561      * <p>
2562      * If possible, the SAX driver should provide the line position
2563      * of the first character after the text associated with the document
2564      * event.
2565      * <p>
2566      * If possible, the SAX driver should provide the line position
2567      * of the first character after the text associated with the document
2568      * event.  The first column in each line is column 1.
2569      *
2570      * @return The column number, or -1 if none is available.
2571      */
2572     public int getColumnNumber() {
2573         if (fCurrentEntity != null) {
2574             if (fCurrentEntity.isExternal()) {
2575                 return fCurrentEntity.columnNumber;
2576             } else {
2577                 // search for the first external entity on the stack
2578                 int size = fEntityStack.size();
2579                 for (int i=size-1; i>0 ; i--) {
2580                     Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity)fEntityStack.elementAt(i);
2581                     if (firstExternalEntity.isExternal()) {
2582                         return firstExternalEntity.columnNumber;
2583                     }
2584                 }
2585             }
2586         }
2587 
2588         return -1;
2589     } // getColumnNumber():int
2590 
2591 
2592     //
2593     // Protected static methods
2594     //
2595 
2596     /**
2597      * Fixes a platform dependent filename to standard URI form.
2598      *
2599      * @param str The string to fix.
2600      *
2601      * @return Returns the fixed URI string.
2602      */
2603     protected static String fixURI(String str) {
2604 
2605         // handle platform dependent strings
2606         str = str.replace(java.io.File.separatorChar, '/');
2607 
2608         // Windows fix
2609         if (str.length() >= 2) {
2610             char ch1 = str.charAt(1);
2611             // change "C:blah" to "/C:blah"
2612             if (ch1 == ':') {
2613                 char ch0 = Character.toUpperCase(str.charAt(0));
2614                 if (ch0 >= 'A' && ch0 <= 'Z') {
2615                     str = "/" + str;
2616                 }
2617             }
2618             // change "//blah" to "file://blah"
2619             else if (ch1 == '/' && str.charAt(0) == '/') {
2620                 str = "file:" + str;
2621             }
2622         }
2623 
2624         // replace spaces in file names with %20.
2625         // Original comment from JDK5: the following algorithm might not be
2626         // very performant, but people who want to use invalid URI's have to
2627         // pay the price.
2628         int pos = str.indexOf(' ');
2629         if (pos >= 0) {
2630             StringBuilder sb = new StringBuilder(str.length());
2631             // put characters before ' ' into the string builder
2632             for (int i = 0; i < pos; i++)
2633                 sb.append(str.charAt(i));
2634             // and %20 for the space
2635             sb.append("%20");
2636             // for the remamining part, also convert ' ' to "%20".
2637             for (int i = pos+1; i < str.length(); i++) {
2638                 if (str.charAt(i) == ' ')
2639                     sb.append("%20");
2640                 else
2641                     sb.append(str.charAt(i));
2642             }
2643             str = sb.toString();
2644         }
2645 
2646         // done
2647         return str;
2648 
2649     } // fixURI(String):String
2650 
2651 
2652     //
2653     // Package visible methods
2654     //
2655     /** Prints the contents of the buffer. */
2656     final void print() {
2657         if (DEBUG_BUFFER) {
2658             if (fCurrentEntity != null) {
2659                 System.out.print('[');
2660                 System.out.print(fCurrentEntity.count);
2661                 System.out.print(' ');
2662                 System.out.print(fCurrentEntity.position);
2663                 if (fCurrentEntity.count > 0) {
2664                     System.out.print(" \"");
2665                     for (int i = 0; i < fCurrentEntity.count; i++) {
2666                         if (i == fCurrentEntity.position) {
2667                             System.out.print('^');
2668                         }
2669                         char c = fCurrentEntity.ch[i];
2670                         switch (c) {
2671                             case '\n': {
2672                                 System.out.print("\\n");
2673                                 break;
2674                             }
2675                             case '\r': {
2676                                 System.out.print("\\r");
2677                                 break;
2678                             }
2679                             case '\t': {
2680                                 System.out.print("\\t");
2681                                 break;
2682                             }
2683                             case '\\': {
2684                                 System.out.print("\\\\");
2685                                 break;
2686                             }
2687                             default: {
2688                                 System.out.print(c);
2689                             }
2690                         }
2691                     }
2692                     if (fCurrentEntity.position == fCurrentEntity.count) {
2693                         System.out.print('^');
2694                     }
2695                     System.out.print('"');
2696                 }
2697                 System.out.print(']');
2698                 System.out.print(" @ ");
2699                 System.out.print(fCurrentEntity.lineNumber);
2700                 System.out.print(',');
2701                 System.out.print(fCurrentEntity.columnNumber);
2702             } else {
2703                 System.out.print("*NO CURRENT ENTITY*");
2704             }
2705         }
2706     } // print()
2707 
2708     /**
2709      * Buffer used in entity manager to reuse character arrays instead
2710      * of creating new ones every time.
2711      *
2712      * @xerces.internal
2713      *
2714      * @author Ankit Pasricha, IBM
2715      */
2716     private static class CharacterBuffer {
2717 
2718         /** character buffer */
2719         private char[] ch;
2720 
2721         /** whether the buffer is for an external or internal scanned entity */
2722         private boolean isExternal;
2723 
2724         public CharacterBuffer(boolean isExternal, int size) {
2725             this.isExternal = isExternal;
2726             ch = new char[size];
2727         }
2728     }
2729 
2730 
2731      /**
2732      * Stores a number of character buffers and provides it to the entity
2733      * manager to use when an entity is seen.
2734      *
2735      * @xerces.internal
2736      *
2737      * @author Ankit Pasricha, IBM
2738      */
2739     private static class CharacterBufferPool {
2740 
2741         private static final int DEFAULT_POOL_SIZE = 3;
2742 
2743         private CharacterBuffer[] fInternalBufferPool;
2744         private CharacterBuffer[] fExternalBufferPool;
2745 
2746         private int fExternalBufferSize;
2747         private int fInternalBufferSize;
2748         private int poolSize;
2749 
2750         private int fInternalTop;
2751         private int fExternalTop;
2752 
2753         public CharacterBufferPool(int externalBufferSize, int internalBufferSize) {
2754             this(DEFAULT_POOL_SIZE, externalBufferSize, internalBufferSize);
2755         }
2756 
2757         public CharacterBufferPool(int poolSize, int externalBufferSize, int internalBufferSize) {
2758             fExternalBufferSize = externalBufferSize;
2759             fInternalBufferSize = internalBufferSize;
2760             this.poolSize = poolSize;
2761             init();
2762         }
2763 
2764         /** Initializes buffer pool. **/
2765         private void init() {
2766             fInternalBufferPool = new CharacterBuffer[poolSize];
2767             fExternalBufferPool = new CharacterBuffer[poolSize];
2768             fInternalTop = -1;
2769             fExternalTop = -1;
2770         }
2771 
2772         /** Retrieves buffer from pool. **/
2773         public CharacterBuffer getBuffer(boolean external) {
2774             if (external) {
2775                 if (fExternalTop > -1) {
2776                     return (CharacterBuffer)fExternalBufferPool[fExternalTop--];
2777                 }
2778                 else {
2779                     return new CharacterBuffer(true, fExternalBufferSize);
2780                 }
2781             }
2782             else {
2783                 if (fInternalTop > -1) {
2784                     return (CharacterBuffer)fInternalBufferPool[fInternalTop--];
2785                 }
2786                 else {
2787                     return new CharacterBuffer(false, fInternalBufferSize);
2788                 }
2789             }
2790         }
2791 
2792         /** Returns buffer to pool. **/
2793         public void returnToPool(CharacterBuffer buffer) {
2794             if (buffer.isExternal) {
2795                 if (fExternalTop < fExternalBufferPool.length - 1) {
2796                     fExternalBufferPool[++fExternalTop] = buffer;
2797                 }
2798             }
2799             else if (fInternalTop < fInternalBufferPool.length - 1) {
2800                 fInternalBufferPool[++fInternalTop] = buffer;
2801             }
2802         }
2803 
2804         /** Sets the size of external buffers and dumps the old pool. **/
2805         public void setExternalBufferSize(int bufferSize) {
2806             fExternalBufferSize = bufferSize;
2807             fExternalBufferPool = new CharacterBuffer[poolSize];
2808             fExternalTop = -1;
2809         }
2810     }
2811 
2812     /**
2813     * This class wraps the byte inputstreams we're presented with.
2814     * We need it because java.io.InputStreams don't provide
2815     * functionality to reread processed bytes, and they have a habit
2816     * of reading more than one character when you call their read()
2817     * methods.  This means that, once we discover the true (declared)
2818     * encoding of a document, we can neither backtrack to read the
2819     * whole doc again nor start reading where we are with a new
2820     * reader.
2821     *
2822     * This class allows rewinding an inputStream by allowing a mark
2823     * to be set, and the stream reset to that position.  <strong>The
2824     * class assumes that it needs to read one character per
2825     * invocation when it's read() method is inovked, but uses the
2826     * underlying InputStream's read(char[], offset length) method--it
2827     * won't buffer data read this way!</strong>
2828     *
2829     * @xerces.internal
2830     *
2831     * @author Neil Graham, IBM
2832     * @author Glenn Marcy, IBM
2833     */
2834 
2835     protected final class RewindableInputStream extends InputStream {
2836 
2837         private InputStream fInputStream;
2838         private byte[] fData;
2839         private int fStartOffset;
2840         private int fEndOffset;
2841         private int fOffset;
2842         private int fLength;
2843         private int fMark;
2844 
2845         public RewindableInputStream(InputStream is) {
2846             fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE];
2847             fInputStream = is;
2848             fStartOffset = 0;
2849             fEndOffset = -1;
2850             fOffset = 0;
2851             fLength = 0;
2852             fMark = 0;
2853         }
2854 
2855         public void setStartOffset(int offset) {
2856             fStartOffset = offset;
2857         }
2858 
2859         public void rewind() {
2860             fOffset = fStartOffset;
2861         }
2862 
2863         public int read() throws IOException {
2864             int b = 0;
2865             if (fOffset < fLength) {
2866                 return fData[fOffset++] & 0xff;
2867             }
2868             if (fOffset == fEndOffset) {
2869                 return -1;
2870             }
2871             if (fOffset == fData.length) {
2872                 byte[] newData = new byte[fOffset << 1];
2873                 System.arraycopy(fData, 0, newData, 0, fOffset);
2874                 fData = newData;
2875             }
2876             b = fInputStream.read();
2877             if (b == -1) {
2878                 fEndOffset = fOffset;
2879                 return -1;
2880             }
2881             fData[fLength++] = (byte)b;
2882             fOffset++;
2883             return b & 0xff;
2884         }
2885 
2886         public int read(byte[] b, int off, int len) throws IOException {
2887             int bytesLeft = fLength - fOffset;
2888             if (bytesLeft == 0) {
2889                 if (fOffset == fEndOffset) {
2890                     return -1;
2891                 }
2892 
2893                 /**
2894                  * //System.out.println("fCurrentEntitty = " + fCurrentEntity );
2895                  * //System.out.println("fInputStream = " + fInputStream );
2896                  * // better get some more for the voracious reader... */
2897 
2898                 if(fCurrentEntity.mayReadChunks || !fCurrentEntity.xmlDeclChunkRead) {
2899 
2900                     if (!fCurrentEntity.xmlDeclChunkRead)
2901                     {
2902                         fCurrentEntity.xmlDeclChunkRead = true;
2903                         len = fCurrentEntity.DEFAULT_XMLDECL_BUFFER_SIZE;
2904                     }
2905                     return fInputStream.read(b, off, len);
2906                 }
2907 
2908                 int returnedVal = read();
2909                 if(returnedVal == -1) {
2910                   fEndOffset = fOffset;
2911                   return -1;
2912                 }
2913                 b[off] = (byte)returnedVal;
2914                 return 1;
2915 
2916             }
2917             if (len < bytesLeft) {
2918                 if (len <= 0) {
2919                     return 0;
2920                 }
2921             } else {
2922                 len = bytesLeft;
2923             }
2924             if (b != null) {
2925                 System.arraycopy(fData, fOffset, b, off, len);
2926             }
2927             fOffset += len;
2928             return len;
2929         }
2930 
2931         public long skip(long n)
2932         throws IOException {
2933             int bytesLeft;
2934             if (n <= 0) {
2935                 return 0;
2936             }
2937             bytesLeft = fLength - fOffset;
2938             if (bytesLeft == 0) {
2939                 if (fOffset == fEndOffset) {
2940                     return 0;
2941                 }
2942                 return fInputStream.skip(n);
2943             }
2944             if (n <= bytesLeft) {
2945                 fOffset += n;
2946                 return n;
2947             }
2948             fOffset += bytesLeft;
2949             if (fOffset == fEndOffset) {
2950                 return bytesLeft;
2951             }
2952             n -= bytesLeft;
2953             /*
2954             * In a manner of speaking, when this class isn't permitting more
2955             * than one byte at a time to be read, it is "blocking".  The
2956             * available() method should indicate how much can be read without
2957             * blocking, so while we're in this mode, it should only indicate
2958             * that bytes in its buffer are available; otherwise, the result of
2959             * available() on the underlying InputStream is appropriate.
2960             */
2961             return fInputStream.skip(n) + bytesLeft;
2962         }
2963 
2964         public int available() throws IOException {
2965             int bytesLeft = fLength - fOffset;
2966             if (bytesLeft == 0) {
2967                 if (fOffset == fEndOffset) {
2968                     return -1;
2969                 }
2970                 return fCurrentEntity.mayReadChunks ? fInputStream.available()
2971                 : 0;
2972             }
2973             return bytesLeft;
2974         }
2975 
2976         public void mark(int howMuch) {
2977             fMark = fOffset;
2978         }
2979 
2980         public void reset() {
2981             fOffset = fMark;
2982             //test();
2983         }
2984 
2985         public boolean markSupported() {
2986             return true;
2987         }
2988 
2989         public void close() throws IOException {
2990             if (fInputStream != null) {
2991                 fInputStream.close();
2992                 fInputStream = null;
2993             }
2994         }
2995     } // end of RewindableInputStream class
2996 
2997     public void test(){
2998         //System.out.println("TESTING: Added familytree to entityManager");
2999         //Usecase1
3000         fEntityStorage.addExternalEntity("entityUsecase1",null,
3001                 "/space/home/stax/sun/6thJan2004/zephyr/data/test.txt",
3002                 "/space/home/stax/sun/6thJan2004/zephyr/data/entity.xml");
3003 
3004         //Usecase2
3005         fEntityStorage.addInternalEntity("entityUsecase2","<Test>value</Test>");
3006         fEntityStorage.addInternalEntity("entityUsecase3","value3");
3007         fEntityStorage.addInternalEntity("text", "Hello World.");
3008         fEntityStorage.addInternalEntity("empty-element", "<foo/>");
3009         fEntityStorage.addInternalEntity("balanced-element", "<foo></foo>");
3010         fEntityStorage.addInternalEntity("balanced-element-with-text", "<foo>Hello, World</foo>");
3011         fEntityStorage.addInternalEntity("balanced-element-with-entity", "<foo>&text;</foo>");
3012         fEntityStorage.addInternalEntity("unbalanced-entity", "<foo>");
3013         fEntityStorage.addInternalEntity("recursive-entity", "<foo>&recursive-entity2;</foo>");
3014         fEntityStorage.addInternalEntity("recursive-entity2", "<bar>&recursive-entity3;</bar>");
3015         fEntityStorage.addInternalEntity("recursive-entity3", "<baz>&recursive-entity;</baz>");
3016         fEntityStorage.addInternalEntity("ch","&#x00A9;");
3017         fEntityStorage.addInternalEntity("ch1","&#84;");
3018         fEntityStorage.addInternalEntity("% ch2","param");
3019     }
3020 
3021 } // class XMLEntityManager