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