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