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