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