content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   * 
   *      http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package mf.org.apache.xerces.impl;
  
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.io.Reader;
  import java.io.StringReader;
  import java.net.HttpURLConnection;
  import java.net.URL;
  import java.net.URLConnection;
  import java.security.AccessController;
  import java.security.PrivilegedAction;
  import java.util.Hashtable;
  import java.util.Iterator;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Stack;
  import java.util.StringTokenizer;
  
  import mf.org.apache.xerces.impl.io.ASCIIReader;
  import mf.org.apache.xerces.impl.io.Latin1Reader;
  import mf.org.apache.xerces.impl.io.UCSReader;
  import mf.org.apache.xerces.impl.io.UTF16Reader;
  import mf.org.apache.xerces.impl.io.UTF8Reader;
  import mf.org.apache.xerces.impl.msg.XMLMessageFormatter;
  import mf.org.apache.xerces.impl.validation.ValidationManager;
  import mf.org.apache.xerces.util.AugmentationsImpl;
  import mf.org.apache.xerces.util.EncodingMap;
  import mf.org.apache.xerces.util.HTTPInputSource;
  import mf.org.apache.xerces.util.SecurityManager;
  import mf.org.apache.xerces.util.SymbolTable;
  import mf.org.apache.xerces.util.URI;
  import mf.org.apache.xerces.util.XMLChar;
  import mf.org.apache.xerces.util.XMLEntityDescriptionImpl;
  import mf.org.apache.xerces.util.XMLResourceIdentifierImpl;
  import mf.org.apache.xerces.xni.Augmentations;
  import mf.org.apache.xerces.xni.XMLResourceIdentifier;
  import mf.org.apache.xerces.xni.XNIException;
  import mf.org.apache.xerces.xni.parser.XMLComponent;
  import mf.org.apache.xerces.xni.parser.XMLComponentManager;
  import mf.org.apache.xerces.xni.parser.XMLConfigurationException;
  import mf.org.apache.xerces.xni.parser.XMLEntityResolver;
  import mf.org.apache.xerces.xni.parser.XMLInputSource;
  
  
  /**
   * The entity manager handles the registration of general and parameter
   * entities; resolves entities; and starts entities. The entity manager
   * is a central component in a standard parser configuration and this
   * class works directly with the entity scanner to manage the underlying
   * xni.
   * <p/>
   * This component requires the following features and properties from the
   * component manager that uses it:
   * <ul>
   * <li>http://xml.org/sax/features/validation</li>
   * <li>http://xml.org/sax/features/external-general-entities</li>
   * <li>http://xml.org/sax/features/external-parameter-entities</li>
   * <li>http://apache.org/xml/features/allow-java-encodings</li>
   * <li>http://apache.org/xml/properties/internal/symbol-table</li>
   * <li>http://apache.org/xml/properties/internal/error-reporter</li>
   * <li>http://apache.org/xml/properties/internal/entity-resolver</li>
   * </ul>
   *
   * @author Andy Clark, IBM
   * @author Arnaud  Le Hors, IBM
   * @version $Id: XMLEntityManager.java 944964 2010-05-17 04:36:47Z mrglavas $
   * @xerces.internal
   */
  public class XMLEntityManager
          implements XMLComponent, XMLEntityResolver {
  
      //
      // Constants
      //
  
      /**
       * Default buffer size (2048).
       */
      public static final int DEFAULT_BUFFER_SIZE = 2048;
  
      /**
       * Default buffer size before we've finished with the XMLDecl:
       */
      public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64;
  
      /**
       * Default internal entity buffer size (512).
       */
      public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 512;
  
      // feature identifiers
  
      /**
       * Feature identifier: validation.
       */
      protected static final String VALIDATION =
              Constants.SAX_FEATURE_PREFIX + Constants.VALIDATION_FEATURE;
  
      /**
       * Feature identifier: external general entities.
       */
      protected static final String EXTERNAL_GENERAL_ENTITIES =
              Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE;
  
      /**
       * Feature identifier: external parameter entities.
       */
      protected static final String EXTERNAL_PARAMETER_ENTITIES =
              Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE;
  
      /**
       * Feature identifier: allow Java encodings.
       */
      protected static final String ALLOW_JAVA_ENCODINGS =
              Constants.XERCES_FEATURE_PREFIX + Constants.ALLOW_JAVA_ENCODINGS_FEATURE;
  
      /**
       * Feature identifier: warn on duplicate EntityDef
       */
      protected static final String WARN_ON_DUPLICATE_ENTITYDEF =
              Constants.XERCES_FEATURE_PREFIX + Constants.WARN_ON_DUPLICATE_ENTITYDEF_FEATURE;
  
      /**
       * Feature identifier: standard uri conformant
       */
      protected static final String STANDARD_URI_CONFORMANT =
              Constants.XERCES_FEATURE_PREFIX + Constants.STANDARD_URI_CONFORMANT_FEATURE;
  
      protected static final String PARSER_SETTINGS =
              Constants.XERCES_FEATURE_PREFIX + Constants.PARSER_SETTINGS;
  
      // property identifiers
  
      /**
       * Property identifier: symbol table.
       */
      protected static final String SYMBOL_TABLE =
              Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
  
      /**
       * Property identifier: error reporter.
       */
      protected static final String ERROR_REPORTER =
              Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
  
      /**
       * Property identifier: entity resolver.
       */
      protected static final String ENTITY_RESOLVER =
              Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_RESOLVER_PROPERTY;
  
      // property identifier:  ValidationManager
      protected static final String VALIDATION_MANAGER =
              Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY;
  
      /**
       * property identifier: buffer size.
       */
      protected static final String BUFFER_SIZE =
              Constants.XERCES_PROPERTY_PREFIX + Constants.BUFFER_SIZE_PROPERTY;
  
      /**
       * property identifier: security manager.
       */
      protected static final String SECURITY_MANAGER =
              Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY;
  
      // recognized features and properties
  
      /**
       * Recognized features.
       */
      private static final String[] RECOGNIZED_FEATURES = {
              VALIDATION,
              EXTERNAL_GENERAL_ENTITIES,
              EXTERNAL_PARAMETER_ENTITIES,
              ALLOW_JAVA_ENCODINGS,
              WARN_ON_DUPLICATE_ENTITYDEF,
              STANDARD_URI_CONFORMANT
      };
  
      /**
       * Feature defaults.
       */
      private static final Boolean[] FEATURE_DEFAULTS = {
              null,
              Boolean.TRUE,
              Boolean.TRUE,
              Boolean.FALSE,
              Boolean.FALSE,
              Boolean.FALSE
      };
  
      /**
       * Recognized properties.
       */
      private static final String[] RECOGNIZED_PROPERTIES = {
              SYMBOL_TABLE,
              ERROR_REPORTER,
              ENTITY_RESOLVER,
              VALIDATION_MANAGER,
              BUFFER_SIZE,
              SECURITY_MANAGER,
      };
  
      /**
       * Property defaults.
       */
      private static final Object[] PROPERTY_DEFAULTS = {
              null,
              null,
              null,
              null,
              new Integer(DEFAULT_BUFFER_SIZE),
              null,
      };
  
      private static final String XMLEntity = "[xml]".intern();
      private static final String DTDEntity = "[dtd]".intern();
  
      // debugging
  
      /**
       * Debug printing of buffer. This debugging flag works best when you
       * resize the DEFAULT_BUFFER_SIZE down to something reasonable like
       * 64 characters.
       */
      private static final boolean DEBUG_BUFFER = false;
  
      /**
       * Debug some basic entities.
       */
      private static final boolean DEBUG_ENTITIES = false;
  
      /**
       * Debug switching readers for encodings.
       */
      private static final boolean DEBUG_ENCODINGS = false;
  
      // should be diplayed trace resolving messages
      private static final boolean DEBUG_RESOLVER = false;
  
      //
      // Data
      //
  
      // features
      // which ASCII characters need to be escaped
      private static final boolean gNeedEscaping[] = new boolean[128];
      // the first hex character if a character needs to be escaped
      private static final char gAfterEscaping1[] = new char[128];
      // the second hex character if a character needs to be escaped
      private static final char gAfterEscaping2[] = new char[128];
      private static final char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7',
              '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
      // current value of the "user.dir" property
      private static String gUserDir;
      // cached URI object for the current value of the escaped "user.dir" property stored as a URI
      private static URI gUserDirURI;
  
      // properties
      private static PrivilegedAction GET_USER_DIR_SYSTEM_PROPERTY = new PrivilegedAction() {
          @Override
          public Object run() {
              return System.getProperty("user.dir");
          }
      };
  
      // initialize the above 3 arrays
      static {
          for (int i = 0; i <= 0x1f; i++) {
              gNeedEscaping[i] = true;
              gAfterEscaping1[i] = gHexChs[i >> 4];
              gAfterEscaping2[i] = gHexChs[i & 0xf];
          }
          gNeedEscaping[0x7f] = true;
          gAfterEscaping1[0x7f] = '7';
          gAfterEscaping2[0x7f] = 'F';
          char[] escChs = {' ', '<', '>', '#', '%', '"', '{', '}',
                  '|', '\\', '^', '~', '[', ']', '`'};
          int len = escChs.length;
          char ch;
          for (int i = 0; i < len; i++) {
              ch = escChs[i];
              gNeedEscaping[ch] = true;
              gAfterEscaping1[ch] = gHexChs[ch >> 4];
              gAfterEscaping2[ch] = gHexChs[ch & 0xf];
          }
      }
  
      /**
       * Entities.
       */
      protected final Hashtable fEntities = new Hashtable();
      /**
       * Entity stack.
       */
      protected final Stack fEntityStack = new Stack();
  
      // settings
      /**
       * Resource identifier.
       */
      private final XMLResourceIdentifierImpl fResourceIdentifier = new XMLResourceIdentifierImpl();
      /**
       * Augmentations for entities.
       */
      private final Augmentations fEntityAugs = new AugmentationsImpl();
      /**
       * Validation. This feature identifier is:
       * http://xml.org/sax/features/validation
       */
      protected boolean fValidation;
      /**
       * External general entities. This feature identifier is:
       * http://xml.org/sax/features/external-general-entities
       */
      protected boolean fExternalGeneralEntities = true;
      /**
       * External parameter entities. This feature identifier is:
       * http://xml.org/sax/features/external-parameter-entities
       */
      protected boolean fExternalParameterEntities = true;
  
      // handlers
      /**
       * Allow Java encoding names. This feature identifier is:
       * http://apache.org/xml/features/allow-java-encodings
       */
      protected boolean fAllowJavaEncodings;
  
      // scanner
      /**
       * warn on duplicate Entity declaration.
       * http://apache.org/xml/features/warn-on-duplicate-entitydef
       */
      protected boolean fWarnDuplicateEntityDef;
      /**
       * standard uri conformant (strict uri).
       * http://apache.org/xml/features/standard-uri-conformant
       */
      protected boolean fStrictURI;
      /**
       * Symbol table. This property identifier is:
       * http://apache.org/xml/properties/internal/symbol-table
       */
      protected SymbolTable fSymbolTable;
      /**
       * Error reporter. This property identifier is:
       * http://apache.org/xml/properties/internal/error-reporter
       */
      protected XMLErrorReporter fErrorReporter;
      /**
       * Entity resolver. This property identifier is:
       * http://apache.org/xml/properties/internal/entity-resolver
       */
      protected XMLEntityResolver fEntityResolver;
  
      // entities
      /**
       * Validation manager. This property identifier is:
       * http://apache.org/xml/properties/internal/validation-manager
       */
      protected ValidationManager fValidationManager;
      /**
       * Buffer size. We get this value from a property. The default size
       * is used if the input buffer size property is not specified.
       * REVISIT: do we need a property for internal entity buffer size?
       */
      protected int fBufferSize = DEFAULT_BUFFER_SIZE;
      /**
       * Pool of byte buffers for single byte and variable width encodings, such as US-ASCII and UTF-8.
       */
      private final ByteBufferPool fSmallByteBufferPool = new ByteBufferPool(fBufferSize);
  
      // shared context
      /**
       * Pool of byte buffers for 2-byte encodings, such as UTF-16.
       **/
      private final ByteBufferPool fLargeByteBufferPool = new ByteBufferPool(fBufferSize << 1);
  
      // temp vars
      /**
       * Pool of character buffers.
       */
      private final CharacterBufferPool fCharacterBufferPool = new CharacterBufferPool(fBufferSize, DEFAULT_INTERNAL_BUFFER_SIZE);
      // stores defaults for entity expansion limit if it has
      // been set on the configuration.
      protected SecurityManager fSecurityManager = null;
      /**
       * True if the document entity is standalone. This should really
       * only be set by the document source (e.g. XMLDocumentScanner).
       */
      protected boolean fStandalone;
      /**
       * True if the current document contains parameter entity references.
       */
      protected boolean fHasPEReferences;
      // are the entities being parsed in the external subset?
      // NOTE:  this *is not* the same as whether they're external entities!
      protected boolean fInExternalSubset = false;
      /**
       * Entity handler.
       */
      protected XMLEntityHandler fEntityHandler;
  
      //
      // Constructors
      //
      /**
       * Current entity scanner.
       */
      protected XMLEntityScanner fEntityScanner;
      /**
       * XML 1.0 entity scanner.
       */
      protected XMLEntityScanner fXML10EntityScanner;
  
      //
      // Public methods
      //
      /**
       * XML 1.1 entity scanner.
       */
      protected XMLEntityScanner fXML11EntityScanner;
      // entity expansion limit (contains useful data if and only if
      // fSecurityManager is non-null)
      protected int fEntityExpansionLimit = 0;
      // entity currently being expanded:
      protected int fEntityExpansionCount = 0;
      /**
       * Current entity.
       */
      protected ScannedEntity fCurrentEntity;
      /**
       * Shared declared entities.
       */
      protected Hashtable fDeclaredEntities;
      // A stack containing all the open readers
      protected Stack fReaderStack = new Stack();
      /**
       * Temporary storage for the current entity's byte buffer.
       */
      private byte[] fTempByteBuffer = null;
  
      /**
       * Default constructor.
       */
      public XMLEntityManager() {
          this(null);
      } // <init>()
  
      /**
       * Constructs an entity manager that shares the specified entity
       * declarations during each parse.
       * <p/>
       * <strong>REVISIT:</strong> We might want to think about the "right"
       * way to expose the list of declared entities. For now, the knowledge
       * how to access the entity declarations is implicit.
       */
      public XMLEntityManager(XMLEntityManager entityManager) {
  
          // save shared entity declarations
          fDeclaredEntities = entityManager != null
                  ? entityManager.getDeclaredEntities() : null;
  
          setScannerVersion(Constants.XML_VERSION_1_0);
      } // <init>(XMLEntityManager)
  
      // To escape the "user.dir" system property, by using %HH to represent
      // special ASCII characters: 0x00~0x1F, 0x7F, ' ', '<', '>', '#', '%'
      // and '"'. It's a static method, so needs to be synchronized.
      // this method looks heavy, but since the system property isn't expected
      // to change often, so in most cases, we only need to return the URI
      // that was escaped before.
      // According to the URI spec, non-ASCII characters (whose value >= 128)
      // need to be escaped too.
      // REVISIT: don't know how to escape non-ASCII characters, especially
      // which encoding to use. Leave them for now.
      private static synchronized URI getUserDir() throws URI.MalformedURIException {
          // get the user.dir property
          String userDir = "";
          try {
              userDir = (String) AccessController.doPrivileged(GET_USER_DIR_SYSTEM_PROPERTY);
          } catch (SecurityException se) {
          }
  
          // return empty string if property value is empty string.
          if (userDir.length() == 0)
              return new URI("file", "", "", null, null);
  
          // compute the new escaped value if the new property value doesn't
          // match the previous one
          if (gUserDirURI != null && userDir.equals(gUserDir)) {
              return gUserDirURI;
          }
  
          // record the new value as the global property value
          gUserDir = userDir;
  
          char separator = java.io.File.separatorChar;
          userDir = userDir.replace(separator, '/');
  
          int len = userDir.length(), ch;
          StringBuffer buffer = new StringBuffer(len * 3);
          // change C:/blah to /C:/blah
          if (len >= 2 && userDir.charAt(1) == ':') {
              ch = Character.toUpperCase(userDir.charAt(0));
              if (ch >= 'A' && ch <= 'Z') {
                  buffer.append('/');
              }
          }
  
          // for each character in the path
          int i = 0;
          for (; i < len; i++) {
              ch = userDir.charAt(i);
              // if it's not an ASCII character, break here, and use UTF-8 encoding
              if (ch >= 128)
                  break;
              if (gNeedEscaping[ch]) {
                  buffer.append('%');
                  buffer.append(gAfterEscaping1[ch]);
                  buffer.append(gAfterEscaping2[ch]);
                  // record the fact that it's escaped
              } else {
                  buffer.append((char) ch);
              }
          }
  
          // we saw some non-ascii character
          if (i < len) {
              // get UTF-8 bytes for the remaining sub-string
              byte[] bytes = null;
              byte b;
              try {
                  bytes = userDir.substring(i).getBytes("UTF-8");
              } catch (java.io.UnsupportedEncodingException e) {
                  // should never happen
                  return new URI("file", "", userDir, null, null);
              }
              len = bytes.length;
  
              // for each byte
              for (i = 0; i < len; i++) {
                  b = bytes[i];
                  // for non-ascii character: make it positive, then escape
                  if (b < 0) {
                      ch = b + 256;
                      buffer.append('%');
                      buffer.append(gHexChs[ch >> 4]);
                      buffer.append(gHexChs[ch & 0xf]);
                  } else if (gNeedEscaping[b]) {
                      buffer.append('%');
                      buffer.append(gAfterEscaping1[b]);
                      buffer.append(gAfterEscaping2[b]);
                  } else {
                      buffer.append((char) b);
                  }
              }
          }
  
          // change blah/blah to blah/blah/
          if (!userDir.endsWith("/"))
              buffer.append('/');
  
          gUserDirURI = new URI("file", "", buffer.toString(), null, null);
  
          return gUserDirURI;
      }
  
      /**
       * Absolutizes a URI using the current value
       * of the "user.dir" property as the base URI. If
       * the URI is already absolute, this is a no-op.
       *
       * @param uri the URI to absolutize
       */
      public static void absolutizeAgainstUserDir(URI uri)
              throws URI.MalformedURIException {
          uri.absolutize(getUserDir());
      }
  
      /**
       * Expands a system id and returns the system id as a URI, if
       * it can be expanded. A return value of null means that the
       * identifier is already expanded. An exception thrown
       * indicates a failure to expand the id.
       *
       * @param systemId The systemId to be expanded.
       * @return Returns the URI string representing the expanded system
       * identifier. A null value indicates that the given
       * system identifier is already expanded.
       */
      public static String expandSystemId(String systemId, String baseSystemId,
                                          boolean strict)
              throws URI.MalformedURIException {
  
          // check if there is a system id before
          // trying to expand it.
          if (systemId == null) {
              return null;
          }
  
          // system id has to be a valid URI
          if (strict) {
              return expandSystemIdStrictOn(systemId, baseSystemId);
          }
  
          // Assume the URIs are well-formed. If it turns out they're not, try fixing them up.
          try {
              return expandSystemIdStrictOff(systemId, baseSystemId);
          } catch (URI.MalformedURIException e) {
              // continue on...
          }
  
          // check for bad parameters id
          if (systemId.length() == 0) {
              return systemId;
          }
  
          // normalize id
          String id = fixURI(systemId);
  
          // normalize base
          URI base = null;
          URI uri = null;
          try {
              if (baseSystemId == null || baseSystemId.length() == 0 ||
                      baseSystemId.equals(systemId)) {
                  base = getUserDir();
              } else {
                  try {
                      base = new URI(fixURI(baseSystemId).trim());
                  } catch (URI.MalformedURIException e) {
                      if (baseSystemId.indexOf(':') != -1) {
                          // for xml schemas we might have baseURI with
                          // a specified drive
                          base = new URI("file", "", fixURI(baseSystemId).trim(), null, null);
                      } else {
                          base = new URI(getUserDir(), fixURI(baseSystemId));
                      }
                  }
              }
              // expand id
              uri = new URI(base, id.trim());
          } catch (Exception e) {
              // let it go through
  
          }
  
          if (uri == null) {
              return systemId;
          }
          return uri.toString();
  
      } // expandSystemId(String,String,boolean):String
  
      /**
       * Helper method for expandSystemId(String,String,boolean):String
       */
      private static String expandSystemIdStrictOn(String systemId, String baseSystemId)
              throws URI.MalformedURIException {
  
          URI systemURI = new URI(systemId, true);
          // If it's already an absolute one, return it
          if (systemURI.isAbsoluteURI()) {
              return systemId;
          }
  
          // If there isn't a base URI, use the working directory
          URI baseURI = null;
          if (baseSystemId == null || baseSystemId.length() == 0) {
              baseURI = getUserDir();
          } else {
              baseURI = new URI(baseSystemId, true);
              if (!baseURI.isAbsoluteURI()) {
                  // assume "base" is also a relative uri
                  baseURI.absolutize(getUserDir());
              }
          }
  
          // absolutize the system identifier using the base URI
          systemURI.absolutize(baseURI);
  
          // return the string rep of the new uri (an absolute one)
          return systemURI.toString();
  
          // if any exception is thrown, it'll get thrown to the caller.
  
      } // expandSystemIdStrictOn(String,String):String
  
      /**
       * Helper method for expandSystemId(String,String,boolean):String
       */
      private static String expandSystemIdStrictOff(String systemId, String baseSystemId)
              throws URI.MalformedURIException {
  
          URI systemURI = new URI(systemId, true);
          // If it's already an absolute one, return it
          if (systemURI.isAbsoluteURI()) {
              if (systemURI.getScheme().length() > 1) {
                  return systemId;
              }
              /**
               * If the scheme's length is only one character,
               * it's likely that this was intended as a file
               * path. Fixing this up in expandSystemId to
               * maintain backwards compatibility.
               */
              throw new URI.MalformedURIException();
          }
  
          // If there isn't a base URI, use the working directory
          URI baseURI = null;
          if (baseSystemId == null || baseSystemId.length() == 0) {
              baseURI = getUserDir();
          } else {
              baseURI = new URI(baseSystemId, true);
              if (!baseURI.isAbsoluteURI()) {
                  // assume "base" is also a relative uri
                  baseURI.absolutize(getUserDir());
              }
          }
  
          // absolutize the system identifier using the base URI
          systemURI.absolutize(baseURI);
  
          // return the string rep of the new uri (an absolute one)
          return systemURI.toString();
  
          // if any exception is thrown, it'll get thrown to the caller.
  
      } // expandSystemIdStrictOff(String,String):String
  
      public static OutputStream createOutputStream(String uri) throws IOException {
          // URI was specified. Handle relative URIs.
          final String expanded = XMLEntityManager.expandSystemId(uri, null, true);
          final URL url = new URL(expanded != null ? expanded : uri);
          OutputStream out = null;
          String protocol = url.getProtocol();
          String host = url.getHost();
          // Use FileOutputStream if this URI is for a local file.
          if (protocol.equals("file")
                  && (host == null || host.length() == 0 || host.equals("localhost"))) {
              File file = new File(getPathWithoutEscapes(url.getPath()));
              if (!file.exists()) {
                  File parent = file.getParentFile();
                  if (parent != null && !parent.exists()) {
                      parent.mkdirs();
                  }
              }
              out = new FileOutputStream(file);
          }
          // Try to write to some other kind of URI. Some protocols
          // won't support this, though HTTP should work.
          else {
              URLConnection urlCon = url.openConnection();
              urlCon.setDoInput(false);
              urlCon.setDoOutput(true);
              urlCon.setUseCaches(false); // Enable tunneling.
              if (urlCon instanceof HttpURLConnection) {
                  // The DOM L3 REC says if we are writing to an HTTP URI
                  // it is to be done with an HTTP PUT.
                  HttpURLConnection httpCon = (HttpURLConnection) urlCon;
                  httpCon.setRequestMethod("PUT");
              }
              out = urlCon.getOutputStream();
          }
          return out;
      }
  
      private static String getPathWithoutEscapes(String origPath) {
          if (origPath != null && origPath.length() != 0 && origPath.indexOf('%') != -1) {
              // Locate the escape characters
              StringTokenizer tokenizer = new StringTokenizer(origPath, "%");
              StringBuffer result = new StringBuffer(origPath.length());
              int size = tokenizer.countTokens();
              result.append(tokenizer.nextToken());
              for (int i = 1; i < size; ++i) {
                  String token = tokenizer.nextToken();
                  // Decode the 2 digit hexadecimal number following % in '%nn'
                  result.append((char) Integer.valueOf(token.substring(0, 2), 16).intValue());
                  result.append(token.substring(2));
              }
              return result.toString();
          }
          return origPath;
      }
  
      /**
       * Fixes a platform dependent filename to standard URI form.
       *
       * @param str The string to fix.
       * @return Returns the fixed URI string.
       */
      protected static String fixURI(String str) {
  
          // handle platform dependent strings
          str = str.replace(java.io.File.separatorChar, '/');
  
          StringBuffer sb = null;
  
          // Windows fix
          if (str.length() >= 2) {
              char ch1 = str.charAt(1);
              // change "C:blah" to "file:///C:blah"
              if (ch1 == ':') {
                  char ch0 = Character.toUpperCase(str.charAt(0));
                  if (ch0 >= 'A' && ch0 <= 'Z') {
                      sb = new StringBuffer(str.length() + 8);
                      sb.append("file:///");
                  }
              }
              // change "//blah" to "file://blah"
              else if (ch1 == '/' && str.charAt(0) == '/') {
                  sb = new StringBuffer(str.length() + 5);
                  sb.append("file:");
              }
          }
  
          int pos = str.indexOf(' ');
          // there is no space in the string
          // we just append "str" to the end of sb
          if (pos < 0) {
              if (sb != null) {
                  sb.append(str);
                  str = sb.toString();
              }
          }
          // otherwise, convert all ' ' to "%20".
          // Note: the following algorithm might not be very performant,
          // but people who want to use invalid URI's have to pay the price.
          else {
              if (sb == null)
                  sb = new StringBuffer(str.length());
              // put characters before ' ' into the string buffer
              for (int i = 0; i < pos; i++)
                  sb.append(str.charAt(i));
              // and %20 for the space
              sb.append("%20");
              // for the remamining part, also convert ' ' to "%20".
              for (int i = pos + 1; i < str.length(); i++) {
                  if (str.charAt(i) == ' ')
                      sb.append("%20");
                  else
                      sb.append(str.charAt(i));
              }
              str = sb.toString();
          }
  
          // done
          return str;
  
      } // fixURI(String):String
  
      /**
       * Prints the contents of the buffer.
       */
      static final void print(ScannedEntity currentEntity) {
          if (DEBUG_BUFFER) {
              if (currentEntity != null) {
                  System.out.print('[');
                  System.out.print(currentEntity.count);
                  System.out.print(' ');
                  System.out.print(currentEntity.position);
                  if (currentEntity.count > 0) {
                      System.out.print(" \"");
                      for (int i = 0; i < currentEntity.count; i++) {
                          if (i == currentEntity.position) {
                              System.out.print('^');
                          }
                          char c = currentEntity.ch[i];
                          switch (c) {
                              case '\n': {
                                  System.out.print("\\n");
                                  break;
                              }
                              case '\r': {
                                  System.out.print("\\r");
                                  break;
                              }
                              case '\t': {
                                  System.out.print("\\t");
                                  break;
                              }
                              case '\\': {
                                  System.out.print("\\\\");
                                  break;
                              }
                              default: {
                                  System.out.print(c);
                              }
                          }
                      }
                      if (currentEntity.position == currentEntity.count) {
                          System.out.print('^');
                      }
                      System.out.print('"');
                  }
                  System.out.print(']');
                  System.out.print(" @ ");
                  System.out.print(currentEntity.lineNumber);
                  System.out.print(',');
                  System.out.print(currentEntity.columnNumber);
              } else {
                  System.out.print("*NO CURRENT ENTITY*");
              }
          }
      } // print(ScannedEntity)
  
      /**
       * Returns true if the document entity is standalone.
       */
      public boolean isStandalone() {
          return fStandalone;
      } // isStandalone():boolean
  
      /**
       * Sets whether the document entity is standalone.
       *
       * @param standalone True if document entity is standalone.
       */
      public void setStandalone(boolean standalone) {
          fStandalone = standalone;
      } // setStandalone(boolean)
  
      /**
       * Notifies the entity manager that the current document
       * being processed contains parameter entity references.
       */
      final void notifyHasPEReferences() {
          fHasPEReferences = true;
      } // notifyHasPEReferences
  
      /**
       * Returns true if the document contains parameter entity references.
       */
      final boolean hasPEReferences() {
          return fHasPEReferences;
      } // hasPEReferences():boolean
  
      /**
       * Sets the entity handler. When an entity starts and ends, the
       * entity handler is notified of the change.
       *
       * @param entityHandler The new entity handler.
       */
      public void setEntityHandler(XMLEntityHandler entityHandler) {
          fEntityHandler = entityHandler;
      } // setEntityHandler(XMLEntityHandler)
  
      // this simply returns the fResourceIdentifier object;
      // this should only be used with caution by callers that
      // carefully manage the entity manager's behaviour, so that
      // this doesn't returning meaningless or misleading data.
      // @return  a reference to the current fResourceIdentifier object
      public XMLResourceIdentifier getCurrentResourceIdentifier() {
          return fResourceIdentifier;
      }
  
      // this simply returns the fCurrentEntity object;
      // this should only be used with caution by callers that
      // carefully manage the entity manager's behaviour, so that
      // this doesn't returning meaningless or misleading data.
      // @return  a reference to the current fCurrentEntity object
      public ScannedEntity getCurrentEntity() {
          return fCurrentEntity;
      }
  
      /**
       * Adds an internal entity declaration.
       * <p/>
       * <strong>Note:</strong> This method ignores subsequent entity
       * declarations.
       * <p/>
       * <strong>Note:</strong> The name should be a unique symbol. The
       * SymbolTable can be used for this purpose.
       *
       * @param name The name of the entity.
       * @param text The text of the entity.
       * @see SymbolTable
       */
      public void addInternalEntity(String name, String text) {
          if (!fEntities.containsKey(name)) {
              Entity entity = new InternalEntity(name, text, fInExternalSubset);
              fEntities.put(name, entity);
          } else {
              if (fWarnDuplicateEntityDef) {
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "MSG_DUPLICATE_ENTITY_DEFINITION",
                          new Object[]{name},
                          XMLErrorReporter.SEVERITY_WARNING);
              }
          }
  
      } // addInternalEntity(String,String)
  
      //
      // XMLComponent methods
      //
  
      /**
       * Adds an external entity declaration.
       * <p/>
       * <strong>Note:</strong> This method ignores subsequent entity
       * declarations.
       * <p/>
       * <strong>Note:</strong> The name should be a unique symbol. The
       * SymbolTable can be used for this purpose.
       *
       * @param name            The name of the entity.
       * @param publicId        The public identifier of the entity.
       * @param literalSystemId The system identifier of the entity.
       * @param baseSystemId    The base system identifier of the entity.
       *                        This is the system identifier of the entity
       *                        where <em>the entity being added</em> and
       *                        is used to expand the system identifier when
       *                        the system identifier is a relative URI.
       *                        When null the system identifier of the first
       *                        external entity on the stack is used instead.
       * @see SymbolTable
       */
      public void addExternalEntity(String name,
                                    String publicId, String literalSystemId,
                                    String baseSystemId) throws IOException {
          if (!fEntities.containsKey(name)) {
              if (baseSystemId == null) {
                  // search for the first external entity on the stack
                  int size = fEntityStack.size();
                  if (size == 0 && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
                      baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
                  }
                  for (int i = size - 1; i >= 0; i--) {
                      ScannedEntity externalEntity =
                              (ScannedEntity) fEntityStack.elementAt(i);
                      if (externalEntity.entityLocation != null && externalEntity.entityLocation.getExpandedSystemId() != null) {
                          baseSystemId = externalEntity.entityLocation.getExpandedSystemId();
                          break;
                      }
                  }
              }
              Entity entity = new ExternalEntity(name,
                      new XMLEntityDescriptionImpl(name, publicId, literalSystemId, baseSystemId,
                              expandSystemId(literalSystemId, baseSystemId, false)), null, fInExternalSubset);
              fEntities.put(name, entity);
          } else {
              if (fWarnDuplicateEntityDef) {
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "MSG_DUPLICATE_ENTITY_DEFINITION",
                          new Object[]{name},
                          XMLErrorReporter.SEVERITY_WARNING);
              }
          }
  
      } // addExternalEntity(String,String,String,String)
  
      /**
       * Checks whether an entity given by name is external.
       *
       * @param entityName The name of the entity to check.
       * @return True if the entity is external, false otherwise
       * (including when the entity is not declared).
       */
      public boolean isExternalEntity(String entityName) {
  
          Entity entity = (Entity) fEntities.get(entityName);
          if (entity == null) {
              return false;
          }
          return entity.isExternal();
      }
  
      /**
       * Checks whether the declaration of an entity given by name is
       * // in the external subset.
       *
       * @param entityName The name of the entity to check.
       * @return True if the entity was declared in the external subset, false otherwise
       * (including when the entity is not declared).
       */
      public boolean isEntityDeclInExternalSubset(String entityName) {
  
          Entity entity = (Entity) fEntities.get(entityName);
          if (entity == null) {
              return false;
          }
          return entity.isEntityDeclInExternalSubset();
      }
  
      /**
       * Adds an unparsed entity declaration.
       * <p/>
       * <strong>Note:</strong> This method ignores subsequent entity
       * declarations.
       * <p/>
       * <strong>Note:</strong> The name should be a unique symbol. The
       * SymbolTable can be used for this purpose.
       *
       * @param name     The name of the entity.
       * @param publicId The public identifier of the entity.
       * @param systemId The system identifier of the entity.
       * @param notation The name of the notation.
       * @see SymbolTable
       */
      public void addUnparsedEntity(String name,
                                    String publicId, String systemId,
                                    String baseSystemId, String notation) {
          if (!fEntities.containsKey(name)) {
              Entity entity = new ExternalEntity(name,
                      new XMLEntityDescriptionImpl(name, publicId, systemId, baseSystemId, null),
                      notation, fInExternalSubset);
              fEntities.put(name, entity);
          } else {
              if (fWarnDuplicateEntityDef) {
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "MSG_DUPLICATE_ENTITY_DEFINITION",
                          new Object[]{name},
                          XMLErrorReporter.SEVERITY_WARNING);
              }
          }
      } // addUnparsedEntity(String,String,String,String)
  
      /**
       * Checks whether an entity given by name is unparsed.
       *
       * @param entityName The name of the entity to check.
       * @return True if the entity is unparsed, false otherwise
       * (including when the entity is not declared).
       */
      public boolean isUnparsedEntity(String entityName) {
  
          Entity entity = (Entity) fEntities.get(entityName);
          if (entity == null) {
              return false;
          }
          return entity.isUnparsed();
      }
  
      /**
       * Checks whether an entity given by name is declared.
       *
       * @param entityName The name of the entity to check.
       * @return True if the entity is declared, false otherwise.
       */
      public boolean isDeclaredEntity(String entityName) {
  
          Entity entity = (Entity) fEntities.get(entityName);
          return entity != null;
      }
  
      /**
       * Resolves the specified public and system identifiers. This
       * method first attempts to resolve the entity based on the
       * EntityResolver registered by the application. If no entity
       * resolver is registered or if the registered entity handler
       * is unable to resolve the entity, then default entity
       * resolution will occur.
       *
       * @param resourceIdentifier The XMLResourceIdentifier for the resource to resolve.
       * @return Returns an input source that wraps the resolved entity.
       * This method will never return null.
       * @throws IOException  Thrown on i/o error.
       * @throws XNIException Thrown by entity resolver to signal an error.
       */
      @Override
      public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier)
              throws IOException, XNIException {
          if (resourceIdentifier == null) return null;
          String publicId = resourceIdentifier.getPublicId();
          String literalSystemId = resourceIdentifier.getLiteralSystemId();
          String baseSystemId = resourceIdentifier.getBaseSystemId();
          String expandedSystemId = resourceIdentifier.getExpandedSystemId();
          // if no base systemId given, assume that it's relative
          // to the systemId of the current scanned entity
          // Sometimes the system id is not (properly) expanded.
          // We need to expand the system id if:
          // a. the expanded one was null; or
          // b. the base system id was null, but becomes non-null from the current entity.
          boolean needExpand = (expandedSystemId == null);
          // REVISIT:  why would the baseSystemId ever be null?  if we
          // didn't have to make this check we wouldn't have to reuse the
          // fXMLResourceIdentifier object...
          if (baseSystemId == null && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
              baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
              if (baseSystemId != null)
                  needExpand = true;
          }
  
          // give the entity resolver a chance
          XMLInputSource xmlInputSource = null;
          if (fEntityResolver != null) {
              if (needExpand) {
                  expandedSystemId = expandSystemId(literalSystemId, baseSystemId, false);
              }
              resourceIdentifier.setBaseSystemId(baseSystemId);
              resourceIdentifier.setExpandedSystemId(expandedSystemId);
              xmlInputSource = fEntityResolver.resolveEntity(resourceIdentifier);
          }
  
          // do default resolution
          // REVISIT: what's the correct behavior if the user provided an entity
          // resolver (fEntityResolver != null), but resolveEntity doesn't return
          // an input source (xmlInputSource == null)?
          // do we do default resolution, or do we just return null? -SG
          if (xmlInputSource == null) {
              // REVISIT: when systemId is null, I think we should return null.
              //          is this the right solution? -SG
              //if (systemId != null)
              xmlInputSource = new XMLInputSource(publicId, literalSystemId, baseSystemId);
          }
  
          if (DEBUG_RESOLVER) {
              System.err.println("XMLEntityManager.resolveEntity(" + publicId + ")");
              System.err.println(" = " + xmlInputSource);
          }
  
          return xmlInputSource;
  
      } // resolveEntity(XMLResourceIdentifier):XMLInputSource
  
      /**
       * Starts a named entity.
       *
       * @param entityName The name of the entity to start.
       * @param literal    True if this entity is started within a literal
       *                   value.
       * @throws IOException  Thrown on i/o error.
       * @throws XNIException Thrown by entity handler to signal an error.
       */
      public void startEntity(String entityName, boolean literal)
              throws IOException, XNIException {
  
          // was entity declared?
          Entity entity = (Entity) fEntities.get(entityName);
          if (entity == null) {
              if (fEntityHandler != null) {
                  String encoding = null;
                  fResourceIdentifier.clear();
                  fEntityAugs.removeAllItems();
                  fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
                  fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
                  fEntityAugs.removeAllItems();
                  fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
                  fEntityHandler.endEntity(entityName, fEntityAugs);
              }
              return;
          }
  
          // should we skip external entities?
          boolean external = entity.isExternal();
          if (external && (fValidationManager == null || !fValidationManager.isCachedDTD())) {
              boolean unparsed = entity.isUnparsed();
              boolean parameter = entityName.startsWith("%");
              boolean general = !parameter;
              if (unparsed || (general && !fExternalGeneralEntities) ||
                      (parameter && !fExternalParameterEntities)) {
                  if (fEntityHandler != null) {
                      fResourceIdentifier.clear();
                      final String encoding = null;
                      ExternalEntity externalEntity = (ExternalEntity) entity;
                      //REVISIT:  since we're storing expandedSystemId in the
                      // externalEntity, how could this have got here if it wasn't already
                      // expanded??? - neilg
                      String extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getLiteralSystemId() : null);
                      String extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getBaseSystemId() : null);
                      String expandedSystemId = expandSystemId(extLitSysId, extBaseSysId, false);
                      fResourceIdentifier.setValues(
                              (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null),
                              extLitSysId, extBaseSysId, expandedSystemId);
                      fEntityAugs.removeAllItems();
                      fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
                      fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
                      fEntityAugs.removeAllItems();
                      fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
                      fEntityHandler.endEntity(entityName, fEntityAugs);
                  }
                  return;
              }
          }
  
          // is entity recursive?
          int size = fEntityStack.size();
          for (int i = size; i >= 0; i--) {
              Entity activeEntity = i == size
                      ? fCurrentEntity
                      : (Entity) fEntityStack.elementAt(i);
              if (activeEntity.name == entityName) {
                  StringBuffer path = new StringBuffer(entityName);
                  for (int j = i + 1; j < size; j++) {
                      activeEntity = (Entity) fEntityStack.elementAt(j);
                      path.append(" -> ");
                      path.append(activeEntity.name);
                  }
                  path.append(" -> ");
                  path.append(fCurrentEntity.name);
                  path.append(" -> ");
                  path.append(entityName);
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "RecursiveReference",
                          new Object[]{entityName, path.toString()},
                          XMLErrorReporter.SEVERITY_FATAL_ERROR);
                  if (fEntityHandler != null) {
                      fResourceIdentifier.clear();
                      final String encoding = null;
                      if (external) {
                          ExternalEntity externalEntity = (ExternalEntity) entity;
                          // REVISIT:  for the same reason above...
                          String extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getLiteralSystemId() : null);
                          String extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getBaseSystemId() : null);
                          String expandedSystemId = expandSystemId(extLitSysId, extBaseSysId, false);
                          fResourceIdentifier.setValues(
                                  (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null),
                                  extLitSysId, extBaseSysId, expandedSystemId);
                      }
                      fEntityAugs.removeAllItems();
                      fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
                      fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
                      fEntityAugs.removeAllItems();
                      fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
                      fEntityHandler.endEntity(entityName, fEntityAugs);
                  }
                  return;
              }
          }
  
          // resolve external entity
          XMLInputSource xmlInputSource = null;
          if (external) {
              ExternalEntity externalEntity = (ExternalEntity) entity;
              xmlInputSource = resolveEntity(externalEntity.entityLocation);
          }
  
          // wrap internal entity
          else {
              InternalEntity internalEntity = (InternalEntity) entity;
              Reader reader = new StringReader(internalEntity.text);
              xmlInputSource = new XMLInputSource(null, null, null, reader, null);
          }
  
          // start the entity
          startEntity(entityName, xmlInputSource, literal, external);
  
      } // startEntity(String,boolean)
  
      //
      // Public static methods
      //
  
      /**
       * Starts the document entity. The document entity has the "[xml]"
       * pseudo-name.
       *
       * @param xmlInputSource The input source of the document entity.
       * @throws IOException  Thrown on i/o error.
       * @throws XNIException Thrown by entity handler to signal an error.
       */
      public void startDocumentEntity(XMLInputSource xmlInputSource)
              throws IOException, XNIException {
          startEntity(XMLEntity, xmlInputSource, false, true);
      } // startDocumentEntity(XMLInputSource)
  
      /**
       * Starts the DTD entity. The DTD entity has the "[dtd]"
       * pseudo-name.
       *
       * @param xmlInputSource The input source of the DTD entity.
       * @throws IOException  Thrown on i/o error.
       * @throws XNIException Thrown by entity handler to signal an error.
       */
      public void startDTDEntity(XMLInputSource xmlInputSource)
              throws IOException, XNIException {
          startEntity(DTDEntity, xmlInputSource, false, true);
      } // startDTDEntity(XMLInputSource)
  
      // indicate start of external subset so that
      // location of entity decls can be tracked
      public void startExternalSubset() {
          fInExternalSubset = true;
      }
  
      public void endExternalSubset() {
          fInExternalSubset = false;
      }
  
      /**
       * Starts an entity.
       * <p/>
       * This method can be used to insert an application defined XML
       * entity stream into the parsing stream.
       *
       * @param name           The name of the entity.
       * @param xmlInputSource The input source of the entity.
       * @param literal        True if this entity is started within a
       *                       literal value.
       * @param isExternal     whether this entity should be treated as an internal or external entity.
       * @throws IOException  Thrown on i/o error.
       * @throws XNIException Thrown by entity handler to signal an error.
       */
      public void startEntity(String name,
                              XMLInputSource xmlInputSource,
                              boolean literal, boolean isExternal)
              throws IOException, XNIException {
  
          String encoding = setupCurrentEntity(name, xmlInputSource, literal, isExternal);
  
          //when entity expansion limit is set by the Application, we need to
          //check for the entity expansion limit set by the parser, if number of entity
          //expansions exceeds the entity expansion limit, parser will throw fatal error.
          // Note that this is intentionally unbalanced; it counts
          // the number of expansions *per document*.
          if (fSecurityManager != null && fEntityExpansionCount++ > fEntityExpansionLimit) {
              fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                      "EntityExpansionLimitExceeded",
                      new Object[]{new Integer(fEntityExpansionLimit)},
                      XMLErrorReporter.SEVERITY_FATAL_ERROR);
              // is there anything better to do than reset the counter?
              // at least one can envision debugging applications where this might
              // be useful...
              fEntityExpansionCount = 0;
          }
  
          // call handler
          if (fEntityHandler != null) {
              fEntityHandler.startEntity(name, fResourceIdentifier, encoding, null);
          }
  
      } // startEntity(String,XMLInputSource)
  
      /**
       * This method uses the passed-in XMLInputSource to make
       * fCurrentEntity usable for reading.
       *
       * @param name           name of the entity (XML is it's the document entity)
       * @param xmlInputSource the input source, with sufficient information
       *                       to begin scanning characters.
       * @param literal        True if this entity is started within a
       *                       literal value.
       * @param isExternal     whether this entity should be treated as an internal or external entity.
       * @return the encoding of the new entity or null if a character stream was employed
       * @throws IOException if anything can't be read
       *                     XNIException    If any parser-specific goes wrong.
       */
      public String setupCurrentEntity(String name, XMLInputSource xmlInputSource,
                                       boolean literal, boolean isExternal)
              throws IOException, XNIException {
          // get information
  
          final String publicId = xmlInputSource.getPublicId();
          String literalSystemId = xmlInputSource.getSystemId();
          String baseSystemId = xmlInputSource.getBaseSystemId();
          String encoding = xmlInputSource.getEncoding();
          final boolean encodingExternallySpecified = (encoding != null);
          Boolean isBigEndian = null;
          fTempByteBuffer = null;
  
          // create reader
          InputStream stream = null;
          Reader reader = xmlInputSource.getCharacterStream();
          // First chance checking strict URI
          String expandedSystemId = expandSystemId(literalSystemId, baseSystemId, fStrictURI);
          if (baseSystemId == null) {
              baseSystemId = expandedSystemId;
          }
          if (reader == null) {
              stream = xmlInputSource.getByteStream();
              if (stream == null) {
                  URL location = new URL(expandedSystemId);
                  URLConnection connect = location.openConnection();
                  if (!(connect instanceof HttpURLConnection)) {
                      stream = connect.getInputStream();
                  } else {
                      boolean followRedirects = true;
  
                      // setup URLConnection if we have an HTTPInputSource
                      if (xmlInputSource instanceof HTTPInputSource) {
                          final HttpURLConnection urlConnection = (HttpURLConnection) connect;
                          final HTTPInputSource httpInputSource = (HTTPInputSource) xmlInputSource;
  
                          // set request properties
                          Iterator propIter = httpInputSource.getHTTPRequestProperties();
                          while (propIter.hasNext()) {
                              Map.Entry entry = (Map.Entry) propIter.next();
                              urlConnection.setRequestProperty((String) entry.getKey(), (String) entry.getValue());
                          }
  
                          // set preference for redirection
                          followRedirects = httpInputSource.getFollowHTTPRedirects();
                          if (!followRedirects) {
                              urlConnection.setInstanceFollowRedirects(followRedirects);
                          }
                      }
  
                      stream = connect.getInputStream();
  
                      // REVISIT: If the URLConnection has external encoding
                      // information, we should be reading it here. It's located
                      // in the charset parameter of Content-Type. -- mrglavas
  
                      if (followRedirects) {
                          String redirect = connect.getURL().toString();
                          // E43: Check if the URL was redirected, and then
                          // update literal and expanded system IDs if needed.
                          if (!redirect.equals(expandedSystemId)) {
                              literalSystemId = redirect;
                              expandedSystemId = redirect;
                          }
                      }
                  }
              }
              // wrap this stream in RewindableInputStream
              RewindableInputStream rewindableStream = new RewindableInputStream(stream);
              stream = rewindableStream;
  
              // perform auto-detect of encoding if necessary
              if (encoding == null) {
                  // read first four bytes and determine encoding
                  final byte[] b4 = new byte[4];
                  int count = 0;
                  for (; count < 4; count++) {
                      b4[count] = (byte) rewindableStream.readAndBuffer();
                  }
                  if (count == 4) {
                      EncodingInfo info = getEncodingInfo(b4, count);
                      encoding = info.encoding;
                      isBigEndian = info.isBigEndian;
                      stream.reset();
                      if (info.hasBOM) {
                          // Special case UTF-8 files with BOM created by Microsoft
                          // tools. It's more efficient to consume the BOM than make
                          // the reader perform extra checks. -Ac
                          if (encoding == "UTF-8") {
                              // UTF-8 BOM: 0xEF 0xBB 0xBF
                              stream.skip(3);
                          }
                          // It's also more efficient to consume the UTF-16 BOM.
                          else if (encoding == "UTF-16") {
                              // UTF-16 BE BOM: 0xFE 0xFF
                              // UTF-16 LE BOM: 0xFF 0xFE
                              stream.skip(2);
                          }
                      }
                      reader = createReader(stream, encoding, isBigEndian);
                  } else {
                      reader = createReader(stream, encoding, isBigEndian);
                  }
              }
  
              // use specified encoding
              else {
                  encoding = encoding.toUpperCase(Locale.ENGLISH);
  
                  // If encoding is UTF-8, consume BOM if one is present.
                  if (encoding.equals("UTF-8")) {
                      final int[] b3 = new int[3];
                      int count = 0;
                      for (; count < 3; ++count) {
                          b3[count] = rewindableStream.readAndBuffer();
                          if (b3[count] == -1)
                              break;
                      }
                      if (count == 3) {
                          if (b3[0] != 0xEF || b3[1] != 0xBB || b3[2] != 0xBF) {
                              // First three bytes are not BOM, so reset.
                              stream.reset();
                          }
                      } else {
                          stream.reset();
                      }
                      reader = createReader(stream, "UTF-8", isBigEndian);
                  }
                  // If encoding is UTF-16, we still need to read the first
                  // four bytes, in order to discover the byte order.
                  else if (encoding.equals("UTF-16")) {
                      final int[] b4 = new int[4];
                      int count = 0;
                      for (; count < 4; ++count) {
                          b4[count] = rewindableStream.readAndBuffer();
                          if (b4[count] == -1)
                              break;
                      }
                      stream.reset();
                      if (count >= 2) {
                          final int b0 = b4[0];
                          final int b1 = b4[1];
                          if (b0 == 0xFE && b1 == 0xFF) {
                              // UTF-16, big-endian
                              isBigEndian = Boolean.TRUE;
                              stream.skip(2);
                          } else if (b0 == 0xFF && b1 == 0xFE) {
                              // UTF-16, little-endian
                              isBigEndian = Boolean.FALSE;
                              stream.skip(2);
                          } else if (count == 4) {
                              final int b2 = b4[2];
                              final int b3 = b4[3];
                              if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
                                  // UTF-16, big-endian, no BOM
                                  isBigEndian = Boolean.TRUE;
                              }
                              if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
                                  // UTF-16, little-endian, no BOM
                                  isBigEndian = Boolean.FALSE;
                              }
                          }
                      }
                      reader = createReader(stream, "UTF-16", isBigEndian);
                  }
                  // If encoding is UCS-4, we still need to read the first four bytes
                  // in order to discover the byte order.
                  else if (encoding.equals("ISO-10646-UCS-4")) {
                      final int[] b4 = new int[4];
                      int count = 0;
                      for (; count < 4; ++count) {
                          b4[count] = rewindableStream.readAndBuffer();
                          if (b4[count] == -1)
                              break;
                      }
                      stream.reset();
  
                      // Ignore unusual octet order for now.
                      if (count == 4) {
                          // UCS-4, big endian (1234)
                          if (b4[0] == 0x00 && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x3C) {
                              isBigEndian = Boolean.TRUE;
                          }
                          // UCS-4, little endian (1234)
                          else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x00) {
                              isBigEndian = Boolean.FALSE;
                          }
                      }
                      reader = createReader(stream, encoding, isBigEndian);
                  }
                  // If encoding is UCS-2, we still need to read the first four bytes
                  // in order to discover the byte order.
                  else if (encoding.equals("ISO-10646-UCS-2")) {
                      final int[] b4 = new int[4];
                      int count = 0;
                      for (; count < 4; ++count) {
                          b4[count] = rewindableStream.readAndBuffer();
                          if (b4[count] == -1)
                              break;
                      }
                      stream.reset();
  
                      if (count == 4) {
                          // UCS-2, big endian
                          if (b4[0] == 0x00 && b4[1] == 0x3C && b4[2] == 0x00 && b4[3] == 0x3F) {
                              isBigEndian = Boolean.TRUE;
                          }
                          // UCS-2, little endian
                          else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x3F && b4[3] == 0x00) {
                              isBigEndian = Boolean.FALSE;
                          }
                      }
                      reader = createReader(stream, encoding, isBigEndian);
                  } else {
                      reader = createReader(stream, encoding, isBigEndian);
                  }
              }
  
              // read one character at a time so we don't jump too far
              // ahead, converting characters from the byte stream in
              // the wrong encoding
              if (DEBUG_ENCODINGS) {
                  System.out.println("$$$ no longer wrapping reader in OneCharReader");
              }
              //reader = new OneCharReader(reader);
          }
  
          // We've seen a new Reader.
          // Push it on the stack so we can close it later.
          fReaderStack.push(reader);
  
          // push entity on stack
          if (fCurrentEntity != null) {
              fEntityStack.push(fCurrentEntity);
          }
  
          // create entity
          fCurrentEntity = new ScannedEntity(name,
                  new XMLResourceIdentifierImpl(publicId, literalSystemId, baseSystemId, expandedSystemId),
                  stream, reader, fTempByteBuffer, encoding, literal, false, isExternal);
          fCurrentEntity.setEncodingExternallySpecified(encodingExternallySpecified);
          fEntityScanner.setCurrentEntity(fCurrentEntity);
          fResourceIdentifier.setValues(publicId, literalSystemId, baseSystemId, expandedSystemId);
          return encoding;
      } //setupCurrentEntity(String, XMLInputSource, boolean, boolean):  String
  
      // set version of scanner to use
      public void setScannerVersion(short version) {
          if (version == Constants.XML_VERSION_1_0) {
              if (fXML10EntityScanner == null) {
                  fXML10EntityScanner = new XMLEntityScanner();
              }
              fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
              fEntityScanner = fXML10EntityScanner;
              fEntityScanner.setCurrentEntity(fCurrentEntity);
          } else {
              if (fXML11EntityScanner == null) {
                  fXML11EntityScanner = new XML11EntityScanner();
              }
              fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter);
              fEntityScanner = fXML11EntityScanner;
              fEntityScanner.setCurrentEntity(fCurrentEntity);
          }
      } // setScannerVersion(short)
  
      /**
       * Returns the entity scanner.
       */
      public XMLEntityScanner getEntityScanner() {
          if (fEntityScanner == null) {
              // default to 1.0
              if (fXML10EntityScanner == null) {
                  fXML10EntityScanner = new XMLEntityScanner();
              }
              fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
              fEntityScanner = fXML10EntityScanner;
          }
          return fEntityScanner;
      } // getEntityScanner():XMLEntityScanner
  
      /**
       * Close all opened InputStreams and Readers opened by this parser.
       */
      public void closeReaders() {
          // close all readers
          for (int i = fReaderStack.size() - 1; i >= 0; i--) {
              try {
                  ((Reader) fReaderStack.pop()).close();
              } catch (IOException e) {
                  // ignore
              }
          }
      }
  
      /**
       * Resets the component. The component can query the component manager
       * about any features and properties that affect the operation of the
       * component.
       *
       * @param componentManager The component manager.
       * @throws SAXException Thrown by component on initialization error.
       *                      For example, if a feature or property is
       *                      required for the operation of the component, the
       *                      component manager may throw a
       *                      SAXNotRecognizedException or a
       *                      SAXNotSupportedException.
       */
      @Override
      public void reset(XMLComponentManager componentManager)
              throws XMLConfigurationException {
  
          boolean parser_settings;
          try {
              parser_settings = componentManager.getFeature(PARSER_SETTINGS);
          } catch (XMLConfigurationException e) {
              parser_settings = true;
          }
  
          if (!parser_settings) {
              // parser settings have not been changed
              reset();
              return;
          }
  
          // sax features
          try {
              fValidation = componentManager.getFeature(VALIDATION);
          } catch (XMLConfigurationException e) {
              fValidation = false;
          }
          try {
              fExternalGeneralEntities = componentManager.getFeature(EXTERNAL_GENERAL_ENTITIES);
          } catch (XMLConfigurationException e) {
              fExternalGeneralEntities = true;
          }
          try {
              fExternalParameterEntities = componentManager.getFeature(EXTERNAL_PARAMETER_ENTITIES);
          } catch (XMLConfigurationException e) {
              fExternalParameterEntities = true;
          }
  
          // xerces features
          try {
              fAllowJavaEncodings = componentManager.getFeature(ALLOW_JAVA_ENCODINGS);
          } catch (XMLConfigurationException e) {
              fAllowJavaEncodings = false;
          }
  
          try {
              fWarnDuplicateEntityDef = componentManager.getFeature(WARN_ON_DUPLICATE_ENTITYDEF);
          } catch (XMLConfigurationException e) {
              fWarnDuplicateEntityDef = false;
          }
  
          try {
              fStrictURI = componentManager.getFeature(STANDARD_URI_CONFORMANT);
          } catch (XMLConfigurationException e) {
              fStrictURI = false;
          }
  
          // xerces properties
          fSymbolTable = (SymbolTable) componentManager.getProperty(SYMBOL_TABLE);
          fErrorReporter = (XMLErrorReporter) componentManager.getProperty(ERROR_REPORTER);
          try {
              fEntityResolver = (XMLEntityResolver) componentManager.getProperty(ENTITY_RESOLVER);
          } catch (XMLConfigurationException e) {
              fEntityResolver = null;
          }
          try {
              fValidationManager = (ValidationManager) componentManager.getProperty(VALIDATION_MANAGER);
          } catch (XMLConfigurationException e) {
              fValidationManager = null;
          }
          try {
              fSecurityManager = (SecurityManager) componentManager.getProperty(SECURITY_MANAGER);
          } catch (XMLConfigurationException e) {
              fSecurityManager = null;
          }
  
          // reset general state
          reset();
  
      } // reset(XMLComponentManager)
  
      // reset general state.  Should not be called other than by
      // a class acting as a component manager but not
      // implementing that interface for whatever reason.
      public void reset() {
          fEntityExpansionLimit = (fSecurityManager != null) ? fSecurityManager.getEntityExpansionLimit() : 0;
  
          // initialize state
          fStandalone = false;
          fHasPEReferences = false;
          fEntities.clear();
          fEntityStack.removeAllElements();
          fEntityExpansionCount = 0;
  
          fCurrentEntity = null;
          // reset scanner
          if (fXML10EntityScanner != null) {
              fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
          }
          if (fXML11EntityScanner != null) {
              fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter);
          }
  
          // DEBUG
          if (DEBUG_ENTITIES) {
              addInternalEntity("text", "Hello, World.");
              addInternalEntity("empty-element", "<foo/>");
              addInternalEntity("balanced-element", "<foo></foo>");
              addInternalEntity("balanced-element-with-text", "<foo>Hello, World</foo>");
              addInternalEntity("balanced-element-with-entity", "<foo>&text;</foo>");
              addInternalEntity("unbalanced-entity", "<foo>");
              addInternalEntity("recursive-entity", "<foo>&recursive-entity2;</foo>");
              addInternalEntity("recursive-entity2", "<bar>&recursive-entity3;</bar>");
              addInternalEntity("recursive-entity3", "<baz>&recursive-entity;</baz>");
              try {
                  addExternalEntity("external-text", null, "external-text.ent", "test/external-text.xml");
                  addExternalEntity("external-balanced-element", null, "external-balanced-element.ent", "test/external-balanced-element.xml");
                  addExternalEntity("one", null, "ent/one.ent", "test/external-entity.xml");
                  addExternalEntity("two", null, "ent/two.ent", "test/ent/one.xml");
              } catch (IOException ex) {
                  // should never happen
              }
          }
  
          // copy declared entities
          if (fDeclaredEntities != null) {
              Iterator entries = fDeclaredEntities.entrySet().iterator();
              while (entries.hasNext()) {
                  Map.Entry entry = (Map.Entry) entries.next();
                  Object key = entry.getKey();
                  Object value = entry.getValue();
                  fEntities.put(key, value);
              }
          }
          fEntityHandler = null;
  
      } // reset(XMLComponentManager)
  
      /**
       * Returns a list of feature identifiers that are recognized by
       * this component. This method may return null if no features
       * are recognized by this component.
       */
      @Override
      public String[] getRecognizedFeatures() {
          return (RECOGNIZED_FEATURES.clone());
      } // getRecognizedFeatures():String[]
  
      /**
       * Sets the state of a feature. This method is called by the component
       * manager any time after reset when a feature changes state.
       * <p/>
       * <strong>Note:</strong> Components should silently ignore features
       * that do not affect the operation of the component.
       *
       * @param featureId The feature identifier.
       * @param state     The state of the feature.
       * @throws SAXNotRecognizedException The component should not throw
       *                                   this exception.
       * @throws SAXNotSupportedException  The component should not throw
       *                                   this exception.
       */
      @Override
      public void setFeature(String featureId, boolean state)
              throws XMLConfigurationException {
  
          // xerces features
          if (featureId.startsWith(Constants.XERCES_FEATURE_PREFIX)) {
              final int suffixLength = featureId.length() - Constants.XERCES_FEATURE_PREFIX.length();
              if (suffixLength == Constants.ALLOW_JAVA_ENCODINGS_FEATURE.length() &&
                      featureId.endsWith(Constants.ALLOW_JAVA_ENCODINGS_FEATURE)) {
                  fAllowJavaEncodings = state;
              }
          }
  
      } // setFeature(String,boolean)
  
      /**
       * Returns a list of property identifiers that are recognized by
       * this component. This method may return null if no properties
       * are recognized by this component.
       */
      @Override
      public String[] getRecognizedProperties() {
          return (RECOGNIZED_PROPERTIES.clone());
      } // getRecognizedProperties():String[]
  
      /**
       * Sets the value of a property. This method is called by the component
       * manager any time after reset when a property changes value.
       * <p/>
       * <strong>Note:</strong> Components should silently ignore properties
       * that do not affect the operation of the component.
       *
       * @param propertyId The property identifier.
       * @param value      The value of the property.
       * @throws SAXNotRecognizedException The component should not throw
       *                                   this exception.
       * @throws SAXNotSupportedException  The component should not throw
       *                                   this exception.
       */
      @Override
      public void setProperty(String propertyId, Object value)
              throws XMLConfigurationException {
  
          // Xerces properties
          if (propertyId.startsWith(Constants.XERCES_PROPERTY_PREFIX)) {
              final int suffixLength = propertyId.length() - Constants.XERCES_PROPERTY_PREFIX.length();
  
              if (suffixLength == Constants.SYMBOL_TABLE_PROPERTY.length() &&
                      propertyId.endsWith(Constants.SYMBOL_TABLE_PROPERTY)) {
                  fSymbolTable = (SymbolTable) value;
                  return;
              }
              if (suffixLength == Constants.ERROR_REPORTER_PROPERTY.length() &&
                      propertyId.endsWith(Constants.ERROR_REPORTER_PROPERTY)) {
                  fErrorReporter = (XMLErrorReporter) value;
                  return;
              }
              if (suffixLength == Constants.ENTITY_RESOLVER_PROPERTY.length() &&
                      propertyId.endsWith(Constants.ENTITY_RESOLVER_PROPERTY)) {
                  fEntityResolver = (XMLEntityResolver) value;
                  return;
              }
              if (suffixLength == Constants.BUFFER_SIZE_PROPERTY.length() &&
                      propertyId.endsWith(Constants.BUFFER_SIZE_PROPERTY)) {
                  Integer bufferSize = (Integer) value;
                  if (bufferSize != null &&
                          bufferSize.intValue() > DEFAULT_XMLDECL_BUFFER_SIZE) {
                      fBufferSize = bufferSize.intValue();
                      fEntityScanner.setBufferSize(fBufferSize);
                      fSmallByteBufferPool.setBufferSize(fBufferSize);
                      fLargeByteBufferPool.setBufferSize(fBufferSize << 1);
                      fCharacterBufferPool.setExternalBufferSize(fBufferSize);
                  }
              }
              if (suffixLength == Constants.SECURITY_MANAGER_PROPERTY.length() &&
                      propertyId.endsWith(Constants.SECURITY_MANAGER_PROPERTY)) {
                  fSecurityManager = (SecurityManager) value;
                  fEntityExpansionLimit = (fSecurityManager != null) ? fSecurityManager.getEntityExpansionLimit() : 0;
              }
          }
  
      } // setProperty(String,Object)
  
      //
      // Protected methods
      //
  
      /**
       * Returns the default state for a feature, or null if this
       * component does not want to report a default value for this
       * feature.
       *
       * @param featureId The feature identifier.
       * @since Xerces 2.2.0
       */
      @Override
      public Boolean getFeatureDefault(String featureId) {
          for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
              if (RECOGNIZED_FEATURES[i].equals(featureId)) {
                  return FEATURE_DEFAULTS[i];
              }
          }
          return null;
      } // getFeatureDefault(String):Boolean
  
      /**
       * Returns the default state for a property, or null if this
       * component does not want to report a default value for this
       * property.
       *
       * @param propertyId The property identifier.
       * @since Xerces 2.2.0
       */
      @Override
      public Object getPropertyDefault(String propertyId) {
          for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
              if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
                  return PROPERTY_DEFAULTS[i];
              }
          }
          return null;
      } // getPropertyDefault(String):Object
  
      /**
       * Ends an entity.
       *
       * @throws XNIException Thrown by entity handler to signal an error.
       */
      void endEntity() throws XNIException {
  
          // call handler
          if (DEBUG_BUFFER) {
              System.out.print("(endEntity: ");
              print(fCurrentEntity);
              System.out.println();
          }
          if (fEntityHandler != null) {
              fEntityHandler.endEntity(fCurrentEntity.name, null);
          }
  
          // Close the reader for the current entity once we're
          // done with it, and remove it from our stack. If parsing
          // is halted at some point, the rest of the readers on
          // the stack will be closed during cleanup.
          try {
              fCurrentEntity.reader.close();
          } catch (IOException e) {
              // ignore
          }
          // REVISIT: We should never encounter underflow if the calls
          // to startEntity and endEntity are balanced, but guard
          // against the EmptyStackException for now. -- mrglavas
          if (!fReaderStack.isEmpty()) {
              fReaderStack.pop();
          }
  
          // Release the character buffer back to the pool for reuse
          fCharacterBufferPool.returnBuffer(fCurrentEntity.fCharacterBuffer);
  
          // Release the byte buffer back to the pool for reuse
          if (fCurrentEntity.fByteBuffer != null) {
              if (fCurrentEntity.fByteBuffer.length == fBufferSize) {
                  fSmallByteBufferPool.returnBuffer(fCurrentEntity.fByteBuffer);
              } else {
                  fLargeByteBufferPool.returnBuffer(fCurrentEntity.fByteBuffer);
              }
          }
  
          // Pop entity stack.
          fCurrentEntity = fEntityStack.size() > 0
                  ? (ScannedEntity) fEntityStack.pop() : null;
          fEntityScanner.setCurrentEntity(fCurrentEntity);
          if (DEBUG_BUFFER) {
              System.out.print(")endEntity: ");
              print(fCurrentEntity);
              System.out.println();
          }
  
      } // endEntity()
  
      /**
       * Returns the IANA encoding name that is auto-detected from
       * the bytes specified, with the endian-ness of that encoding where appropriate.
       *
       * @param b4    The first four bytes of the input.
       * @param count The number of bytes actually read.
       * @return an instance of EncodingInfo which represents the auto-detected encoding.
       */
      protected EncodingInfo getEncodingInfo(byte[] b4, int count) {
  
          if (count < 2) {
              return EncodingInfo.UTF_8;
          }
  
          // UTF-16, with BOM
          int b0 = b4[0] & 0xFF;
          int b1 = b4[1] & 0xFF;
          if (b0 == 0xFE && b1 == 0xFF) {
              // UTF-16, big-endian
              return EncodingInfo.UTF_16_BIG_ENDIAN_WITH_BOM;
          }
          if (b0 == 0xFF && b1 == 0xFE) {
              // UTF-16, little-endian
              return EncodingInfo.UTF_16_LITTLE_ENDIAN_WITH_BOM;
          }
  
          // default to UTF-8 if we don't have enough bytes to make a
          // good determination of the encoding
          if (count < 3) {
              return EncodingInfo.UTF_8;
          }
  
          // UTF-8 with a BOM
          int b2 = b4[2] & 0xFF;
          if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
              return EncodingInfo.UTF_8_WITH_BOM;
          }
  
          // default to UTF-8 if we don't have enough bytes to make a
          // good determination of the encoding
          if (count < 4) {
              return EncodingInfo.UTF_8;
          }
  
          // other encodings
          int b3 = b4[3] & 0xFF;
          if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) {
              // UCS-4, big endian (1234)
              return EncodingInfo.UCS_4_BIG_ENDIAN;
          }
          if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) {
              // UCS-4, little endian (4321)
              return EncodingInfo.UCS_4_LITTLE_ENDIAN;
          }
          if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) {
              // UCS-4, unusual octet order (2143)
              // REVISIT: What should this be?
              return EncodingInfo.UCS_4_UNUSUAL_BYTE_ORDER;
          }
          if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) {
              // UCS-4, unusual octect order (3412)
              // REVISIT: What should this be?
              return EncodingInfo.UCS_4_UNUSUAL_BYTE_ORDER;
          }
          if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
              // UTF-16, big-endian, no BOM
              // (or could turn out to be UCS-2...
              // REVISIT: What should this be?
              return EncodingInfo.UTF_16_BIG_ENDIAN;
          }
          if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
              // UTF-16, little-endian, no BOM
              // (or could turn out to be UCS-2...
              return EncodingInfo.UTF_16_LITTLE_ENDIAN;
          }
          if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) {
              // EBCDIC
              // a la xerces1, return CP037 instead of EBCDIC here
              return EncodingInfo.EBCDIC;
          }
  
          // default encoding
          return EncodingInfo.UTF_8;
  
      } // getEncodingName(byte[],int):Object[]
  
      /**
       * Creates a reader capable of reading the given input stream in
       * the specified encoding.
       *
       * @param inputStream The input stream.
       * @param encoding    The encoding name that the input stream is
       *                    encoded using. If the user has specified that
       *                    Java encoding names are allowed, then the
       *                    encoding name may be a Java encoding name;
       *                    otherwise, it is an ianaEncoding name.
       * @param isBigEndian For encodings (like uCS-4), whose names cannot
       *                    specify a byte order, this tells whether the order is bigEndian. Null means
       *                    unknown or not relevant.
       * @return Returns a reader.
       */
      protected Reader createReader(InputStream inputStream, String encoding, Boolean isBigEndian)
              throws IOException {
  
          // if the encoding is UTF-8 use the optimized UTF-8 reader
          if (encoding == "UTF-8" || encoding == null) {
              return createUTF8Reader(inputStream);
          }
  
          // If the encoding is UTF-16 use the optimized UTF-16 reader
          if (encoding == "UTF-16" && isBigEndian != null) {
              return createUTF16Reader(inputStream, isBigEndian.booleanValue());
          }
  
          // try to use an optimized reader
          String ENCODING = encoding.toUpperCase(Locale.ENGLISH);
          if (ENCODING.equals("UTF-8")) {
              return createUTF8Reader(inputStream);
          }
          if (ENCODING.equals("UTF-16BE")) {
              return createUTF16Reader(inputStream, true);
          }
          if (ENCODING.equals("UTF-16LE")) {
              return createUTF16Reader(inputStream, false);
          }
          if (ENCODING.equals("ISO-10646-UCS-4")) {
              if (isBigEndian != null) {
                  boolean isBE = isBigEndian.booleanValue();
                  if (isBE) {
                      return new UCSReader(inputStream, UCSReader.UCS4BE);
                  } else {
                      return new UCSReader(inputStream, UCSReader.UCS4LE);
                  }
              } else {
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "EncodingByteOrderUnsupported",
                          new Object[]{encoding},
                          XMLErrorReporter.SEVERITY_FATAL_ERROR);
              }
          }
          if (ENCODING.equals("ISO-10646-UCS-2")) {
              if (isBigEndian != null) { // should never happen with this encoding...
                  boolean isBE = isBigEndian.booleanValue();
                  if (isBE) {
                      return new UCSReader(inputStream, UCSReader.UCS2BE);
                  } else {
                      return new UCSReader(inputStream, UCSReader.UCS2LE);
                  }
              } else {
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "EncodingByteOrderUnsupported",
                          new Object[]{encoding},
                          XMLErrorReporter.SEVERITY_FATAL_ERROR);
              }
          }
  
          // check for valid name
          boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
          boolean validJava = XMLChar.isValidJavaEncoding(encoding);
          if (!validIANA || (fAllowJavaEncodings && !validJava)) {
              fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                      "EncodingDeclInvalid",
                      new Object[]{encoding},
                      XMLErrorReporter.SEVERITY_FATAL_ERROR);
              // NOTE: AndyH suggested that, on failure, we use ISO Latin 1
              //       because every byte is a valid ISO Latin 1 character.
              //       It may not translate correctly but if we failed on
              //       the encoding anyway, then we're expecting the content
              //       of the document to be bad. This will just prevent an
              //       invalid UTF-8 sequence to be detected. This is only
              //       important when continue-after-fatal-error is turned
              //       on. -Ac
              return createLatin1Reader(inputStream);
          }
  
          // try to use a Java reader
          String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING);
          if (javaEncoding == null) {
              if (fAllowJavaEncodings) {
                  javaEncoding = encoding;
              } else {
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "EncodingDeclInvalid",
                          new Object[]{encoding},
                          XMLErrorReporter.SEVERITY_FATAL_ERROR);
                  // see comment above.
                  return createLatin1Reader(inputStream);
              }
          } else if (javaEncoding.equals("ASCII")) {
              return createASCIIReader(inputStream);
          } else if (javaEncoding.equals("ISO8859_1")) {
              return createLatin1Reader(inputStream);
          }
          if (DEBUG_ENCODINGS) {
              System.out.print("$$$ creating Java InputStreamReader: encoding=" + javaEncoding);
              if (javaEncoding == encoding) {
                  System.out.print(" (IANA encoding)");
              }
              System.out.println();
          }
          return new InputStreamReader(inputStream, javaEncoding);
  
      } // createReader(InputStream,String, Boolean): Reader
  
      /**
       * Create a new UTF-8 reader from the InputStream.
       **/
      private Reader createUTF8Reader(InputStream stream) {
          if (DEBUG_ENCODINGS) {
              System.out.println("$$$ creating UTF8Reader");
          }
          if (fTempByteBuffer == null) {
              fTempByteBuffer = fSmallByteBufferPool.getBuffer();
          }
          return new UTF8Reader(stream,
                  fTempByteBuffer,
                  fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN),
                  fErrorReporter.getLocale());
      } // createUTF8Reader(InputStream):Reader
  
      /**
       * Create a new UTF-16 reader from the InputStream.
       **/
      private Reader createUTF16Reader(InputStream stream, boolean isBigEndian) {
          if (DEBUG_ENCODINGS) {
              System.out.println("$$$ creating UTF16Reader");
          }
          if (fTempByteBuffer == null) {
              fTempByteBuffer = fLargeByteBufferPool.getBuffer();
          }
          // The cached buffer is too small, we need a larger one.
          else if (fTempByteBuffer.length == fBufferSize) {
              fSmallByteBufferPool.returnBuffer(fTempByteBuffer);
              fTempByteBuffer = fLargeByteBufferPool.getBuffer();
          }
          return new UTF16Reader(stream,
                  fTempByteBuffer,
                  isBigEndian,
                  fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN),
                  fErrorReporter.getLocale());
      } // createUTF16Reader(InputStream):Reader
  
      //
      // Protected static methods
      //
  
      /**
       * Create a new ASCII reader from the InputStream.
       **/
      private Reader createASCIIReader(InputStream stream) {
          if (DEBUG_ENCODINGS) {
              System.out.println("$$$ creating ASCIIReader");
          }
          if (fTempByteBuffer == null) {
              fTempByteBuffer = fSmallByteBufferPool.getBuffer();
          }
          return new ASCIIReader(stream,
                  fTempByteBuffer,
                  fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN),
                  fErrorReporter.getLocale());
      } // createASCIIReader(InputStream):Reader
  
      //
      // Package visible methods
      //
  
      /**
       * Create a new ISO-8859-1 reader from the InputStream.
       **/
      private Reader createLatin1Reader(InputStream stream) {
          if (DEBUG_ENCODINGS) {
              System.out.println("$$$ creating Latin1Reader");
          }
          if (fTempByteBuffer == null) {
              fTempByteBuffer = fSmallByteBufferPool.getBuffer();
          }
          return new Latin1Reader(stream, fTempByteBuffer);
      } // createLatin1Reader(InputStream):Reader
  
      /**
       * Returns the hashtable of declared entities.
       * <p/>
       * <strong>REVISIT:</strong>
       * This should be done the "right" way by designing a better way to
       * enumerate the declared entities. For now, this method is needed
       * by the constructor that takes an XMLEntityManager parameter.
       */
      Hashtable getDeclaredEntities() {
          return fEntities;
      } // getDeclaredEntities():Hashtable
  
      //
      // Classes
      //
  
      /**
       * Entity information.
       *
       * @author Andy Clark, IBM
       * @xerces.internal
       */
      public static abstract class Entity {
  
          //
          // Data
          //
  
          /**
           * Entity name.
           */
          public String name;
  
          // whether this entity's declaration was found in the internal
          // or external subset
          public boolean inExternalSubset;
  
          //
          // Constructors
          //
  
          /**
           * Default constructor.
           */
          public Entity() {
              clear();
          } // <init>()
  
          /**
           * Constructs an entity.
           */
          public Entity(String name, boolean inExternalSubset) {
              this.name = name;
              this.inExternalSubset = inExternalSubset;
          } // <init>(String)
  
          //
          // Public methods
          //
  
          /**
           * Returns true if this entity was declared in the external subset.
           */
          public boolean isEntityDeclInExternalSubset() {
              return inExternalSubset;
          }
  
          /**
           * Returns true if this is an external entity.
           */
          public abstract boolean isExternal();
  
          /**
           * Returns true if this is an unparsed entity.
           */
          public abstract boolean isUnparsed();
  
          /**
           * Clears the entity.
           */
          public void clear() {
              name = null;
              inExternalSubset = false;
          } // clear()
  
          /**
           * Sets the values of the entity.
           */
          public void setValues(Entity entity) {
              name = entity.name;
              inExternalSubset = entity.inExternalSubset;
          } // setValues(Entity)
  
      } // class Entity
  
      /**
       * Internal entity.
       *
       * @author Andy Clark, IBM
       * @xerces.internal
       */
      protected static class InternalEntity
              extends Entity {
  
          //
          // Data
          //
  
          /**
           * Text value of entity.
           */
          public String text;
  
          //
          // Constructors
          //
  
          /**
           * Default constructor.
           */
          public InternalEntity() {
              clear();
          } // <init>()
  
          /**
           * Constructs an internal entity.
           */
          public InternalEntity(String name, String text, boolean inExternalSubset) {
              super(name, inExternalSubset);
              this.text = text;
          } // <init>(String,String)
  
          //
          // Entity methods
          //
  
          /**
           * Returns true if this is an external entity.
           */
          @Override
          public final boolean isExternal() {
              return false;
          } // isExternal():boolean
  
          /**
           * Returns true if this is an unparsed entity.
           */
          @Override
          public final boolean isUnparsed() {
              return false;
          } // isUnparsed():boolean
  
          /**
           * Clears the entity.
           */
          @Override
          public void clear() {
              super.clear();
              text = null;
          } // clear()
  
          /**
           * Sets the values of the entity.
           */
          @Override
          public void setValues(Entity entity) {
              super.setValues(entity);
              text = null;
          } // setValues(Entity)
  
          /**
           * Sets the values of the entity.
           */
          public void setValues(InternalEntity entity) {
              super.setValues(entity);
              text = entity.text;
          } // setValues(InternalEntity)
  
      } // class InternalEntity
  
      /**
       * External entity.
       *
       * @author Andy Clark, IBM
       * @xerces.internal
       */
      protected static class ExternalEntity
              extends Entity {
  
          //
          // Data
          //
  
          /**
           * container for all relevant entity location information.
           */
          public XMLResourceIdentifier entityLocation;
  
          /**
           * Notation name for unparsed entity.
           */
          public String notation;
  
          //
          // Constructors
          //
  
          /**
           * Default constructor.
           */
          public ExternalEntity() {
              clear();
          } // <init>()
  
          /**
           * Constructs an internal entity.
           */
          public ExternalEntity(String name, XMLResourceIdentifier entityLocation,
                                String notation, boolean inExternalSubset) {
              super(name, inExternalSubset);
              this.entityLocation = entityLocation;
              this.notation = notation;
          } // <init>(String,XMLResourceIdentifier, String)
  
          //
          // Entity methods
          //
  
          /**
           * Returns true if this is an external entity.
           */
          @Override
          public final boolean isExternal() {
              return true;
          } // isExternal():boolean
  
          /**
           * Returns true if this is an unparsed entity.
           */
          @Override
          public final boolean isUnparsed() {
              return notation != null;
          } // isUnparsed():boolean
  
          /**
           * Clears the entity.
           */
          @Override
          public void clear() {
              super.clear();
              entityLocation = null;
              notation = null;
          } // clear()
  
          /**
           * Sets the values of the entity.
           */
          @Override
          public void setValues(Entity entity) {
              super.setValues(entity);
              entityLocation = null;
              notation = null;
          } // setValues(Entity)
  
          /**
           * Sets the values of the entity.
           */
          public void setValues(ExternalEntity entity) {
              super.setValues(entity);
              entityLocation = entity.entityLocation;
              notation = entity.notation;
          } // setValues(ExternalEntity)
  
      } // class ExternalEntity
  
      /**
       * Information about auto-detectable encodings.
       *
       * @author Michael Glavassevich, IBM
       * @xerces.internal
       */
      private static class EncodingInfo {
  
          /**
           * UTF-8
           **/
          public static final EncodingInfo UTF_8 = new EncodingInfo("UTF-8", null, false);
  
          /**
           * UTF-8, with BOM
           **/
          public static final EncodingInfo UTF_8_WITH_BOM = new EncodingInfo("UTF-8", null, true);
  
          /**
           * UTF-16, big-endian
           **/
          public static final EncodingInfo UTF_16_BIG_ENDIAN = new EncodingInfo("UTF-16", Boolean.TRUE, false);
  
          /**
           * UTF-16, big-endian with BOM
           **/
          public static final EncodingInfo UTF_16_BIG_ENDIAN_WITH_BOM = new EncodingInfo("UTF-16", Boolean.TRUE, true);
  
          /**
           * UTF-16, little-endian
           **/
          public static final EncodingInfo UTF_16_LITTLE_ENDIAN = new EncodingInfo("UTF-16", Boolean.FALSE, false);
  
          /**
           * UTF-16, little-endian with BOM
           **/
          public static final EncodingInfo UTF_16_LITTLE_ENDIAN_WITH_BOM = new EncodingInfo("UTF-16", Boolean.FALSE, true);
  
          /**
           * UCS-4, big-endian
           **/
          public static final EncodingInfo UCS_4_BIG_ENDIAN = new EncodingInfo("ISO-10646-UCS-4", Boolean.TRUE, false);
  
          /**
           * UCS-4, little-endian
           **/
          public static final EncodingInfo UCS_4_LITTLE_ENDIAN = new EncodingInfo("ISO-10646-UCS-4", Boolean.FALSE, false);
  
          /**
           * UCS-4, unusual byte-order (2143) or (3412)
           **/
          public static final EncodingInfo UCS_4_UNUSUAL_BYTE_ORDER = new EncodingInfo("ISO-10646-UCS-4", null, false);
  
          /**
           * EBCDIC
           **/
          public static final EncodingInfo EBCDIC = new EncodingInfo("CP037", null, false);
  
          public final String encoding;
          public final Boolean isBigEndian;
          public final boolean hasBOM;
  
          private EncodingInfo(String encoding, Boolean isBigEndian, boolean hasBOM) {
              this.encoding = encoding;
              this.isBigEndian = isBigEndian;
              this.hasBOM = hasBOM;
          } // <init>(String,Boolean,boolean)
  
      } // class EncodingInfo
  
      /**
       * Pool of byte buffers for the java.io.Readers.
       *
       * @author Michael Glavassevich, IBM
       * @xerces.internal
       */
      private static final class ByteBufferPool {
  
          private static final int DEFAULT_POOL_SIZE = 3;
  
          private int fPoolSize;
          private int fBufferSize;
          private byte[][] fByteBufferPool;
          private int fDepth;
  
          public ByteBufferPool(int bufferSize) {
              this(DEFAULT_POOL_SIZE, bufferSize);
          }
  
          public ByteBufferPool(int poolSize, int bufferSize) {
              fPoolSize = poolSize;
              fBufferSize = bufferSize;
              fByteBufferPool = new byte[fPoolSize][];
              fDepth = 0;
          }
  
          /**
           * Retrieves a byte buffer from the pool.
           **/
          public byte[] getBuffer() {
              return (fDepth > 0) ? fByteBufferPool[--fDepth] : new byte[fBufferSize];
          }
  
          /**
           * Returns byte buffer to pool.
           **/
          public void returnBuffer(byte[] buffer) {
              if (fDepth < fByteBufferPool.length) {
                  fByteBufferPool[fDepth++] = buffer;
              }
          }
  
          /**
           * Sets the size of the buffers and dumps the old pool.
           **/
          public void setBufferSize(int bufferSize) {
              fBufferSize = bufferSize;
              fByteBufferPool = new byte[fPoolSize][];
              fDepth = 0;
          }
      }
  
      /**
       * Buffer used in entity manager to reuse character arrays instead
       * of creating new ones every time.
       *
       * @author Ankit Pasricha, IBM
       * @xerces.internal
       */
      private static final class CharacterBuffer {
  
          /**
           * character buffer
           */
          private final char[] ch;
  
          /**
           * whether the buffer is for an external or internal scanned entity
           */
          private final boolean isExternal;
  
          public CharacterBuffer(boolean isExternal, int size) {
              this.isExternal = isExternal;
              ch = new char[size];
          }
      }
  
      /**
       * Stores a number of character buffers and provides it to the entity
       * manager to use when an entity is seen.
       *
       * @author Ankit Pasricha, IBM
       * @xerces.internal
       */
      private static final class CharacterBufferPool {
  
          private static final int DEFAULT_POOL_SIZE = 3;
  
          private CharacterBuffer[] fInternalBufferPool;
          private CharacterBuffer[] fExternalBufferPool;
  
          private int fExternalBufferSize;
          private int fInternalBufferSize;
          private int fPoolSize;
  
          private int fInternalTop;
          private int fExternalTop;
  
          public CharacterBufferPool(int externalBufferSize, int internalBufferSize) {
              this(DEFAULT_POOL_SIZE, externalBufferSize, internalBufferSize);
          }
  
          public CharacterBufferPool(int poolSize, int externalBufferSize, int internalBufferSize) {
              fExternalBufferSize = externalBufferSize;
              fInternalBufferSize = internalBufferSize;
              fPoolSize = poolSize;
              init();
          }
  
          /**
           * Initializes buffer pool.
           **/
          private void init() {
              fInternalBufferPool = new CharacterBuffer[fPoolSize];
              fExternalBufferPool = new CharacterBuffer[fPoolSize];
              fInternalTop = -1;
              fExternalTop = -1;
          }
  
          /**
           * Retrieves buffer from pool.
           **/
          public CharacterBuffer getBuffer(boolean external) {
              if (external) {
                  if (fExternalTop > -1) {
                      return fExternalBufferPool[fExternalTop--];
                  } else {
                      return new CharacterBuffer(true, fExternalBufferSize);
                  }
              } else {
                  if (fInternalTop > -1) {
                      return fInternalBufferPool[fInternalTop--];
                  } else {
                      return new CharacterBuffer(false, fInternalBufferSize);
                  }
              }
          }
  
          /**
           * Returns buffer to pool.
           **/
          public void returnBuffer(CharacterBuffer buffer) {
              if (buffer.isExternal) {
                  if (fExternalTop < fExternalBufferPool.length - 1) {
                      fExternalBufferPool[++fExternalTop] = buffer;
                  }
              } else if (fInternalTop < fInternalBufferPool.length - 1) {
                  fInternalBufferPool[++fInternalTop] = buffer;
              }
          }
  
          /**
           * Sets the size of external buffers and dumps the old pool.
           **/
          public void setExternalBufferSize(int bufferSize) {
              fExternalBufferSize = bufferSize;
              fExternalBufferPool = new CharacterBuffer[fPoolSize];
              fExternalTop = -1;
          }
      }
  
      /**
       * Entity state.
       *
       * @author Andy Clark, IBM
       * @xerces.internal
       */
      public class ScannedEntity
              extends Entity {
  
          //
          // Data
          //
  
          // i/o
  
          /**
           * Input stream.
           */
          public InputStream stream;
  
          /**
           * Reader.
           */
          public Reader reader;
  
          // locator information
  
          /**
           * entity location information
           */
          public XMLResourceIdentifier entityLocation;
  
          /**
           * Line number.
           */
          public int lineNumber = 1;
  
          /**
           * Column number.
           */
          public int columnNumber = 1;
  
          // encoding
  
          /**
           * Auto-detected encoding.
           */
          public String encoding;
          /**
           * XML version.
           **/
          public String xmlVersion = "1.0";
  
          // version
          /**
           * True if in a literal.
           */
          public boolean literal;
  
          // status
          // whether this is an external or internal scanned entity
          public boolean isExternal;
          /**
           * Character buffer.
           */
          public char[] ch = null;
  
          // buffer
          /**
           * Position in character buffer.
           */
          public int position;
          /**
           * Base character offset for computing absolute character offset.
           */
          public int baseCharOffset;
          /**
           * Start position in character buffer.
           */
          public int startPosition;
          /**
           * Count of characters in buffer.
           */
          public int count;
          // to allow the reader/inputStream to behave efficiently:
          public boolean mayReadChunks;
          /**
           * Encoding has been set externally, for example
           * using a SAX InputSource or a DOM LSInput.
           */
          boolean externallySpecifiedEncoding = false;
          /**
           * Character buffer container.
           */
          private CharacterBuffer fCharacterBuffer;
  
          /**
           * Byte buffer.
           */
          private byte[] fByteBuffer;
  
          //
          // Constructors
          //
  
          /**
           * Constructs a scanned entity.
           */
          public ScannedEntity(String name,
                               XMLResourceIdentifier entityLocation,
                               InputStream stream, Reader reader, byte[] byteBuffer,
                               String encoding, boolean literal, boolean mayReadChunks, boolean isExternal) {
              super(name, XMLEntityManager.this.fInExternalSubset);
              this.entityLocation = entityLocation;
              this.stream = stream;
              this.reader = reader;
              this.encoding = encoding;
              this.literal = literal;
              this.mayReadChunks = mayReadChunks;
              this.isExternal = isExternal;
              this.fCharacterBuffer = fCharacterBufferPool.getBuffer(isExternal);
              this.ch = fCharacterBuffer.ch;
              this.fByteBuffer = byteBuffer;
          } // <init>(StringXMLResourceIdentifier,InputStream,Reader,String,boolean, boolean)
  
          //
          // Entity methods
          //
  
          /**
           * Returns true if this is an external entity.
           */
          @Override
          public final boolean isExternal() {
              return isExternal;
          } // isExternal():boolean
  
          /**
           * Returns true if this is an unparsed entity.
           */
          @Override
          public final boolean isUnparsed() {
              return false;
          } // isUnparsed():boolean
  
          public void setReader(InputStream stream, String encoding, Boolean isBigEndian) throws IOException {
              fTempByteBuffer = fByteBuffer;
              reader = createReader(stream, encoding, isBigEndian);
              fByteBuffer = fTempByteBuffer;
          }
  
          // return the expanded system ID of the
          // first external entity on the stack, null
          // otherwise.
          public String getExpandedSystemId() {
  
              // search for the first external entity on the stack
              int size = fEntityStack.size();
              for (int i = size - 1; i >= 0; --i) {
                  ScannedEntity externalEntity =
                          (ScannedEntity) fEntityStack.elementAt(i);
  
                  if (externalEntity.entityLocation != null &&
                          externalEntity.entityLocation.getExpandedSystemId() != null) {
                      return externalEntity.entityLocation.getExpandedSystemId();
                  }
              }
              return null;
          }
  
          // return literal systemId of
          // nearest external entity
          public String getLiteralSystemId() {
              // search for the first external entity on the stack
              int size = fEntityStack.size();
              for (int i = size - 1; i >= 0; --i) {
                  ScannedEntity externalEntity =
                          (ScannedEntity) fEntityStack.elementAt(i);
  
                  if (externalEntity.entityLocation != null &&
                          externalEntity.entityLocation.getLiteralSystemId() != null) {
                      return externalEntity.entityLocation.getLiteralSystemId();
                  }
              }
              return null;
          }
  
          // return line number of position in most
          // recent external entity
          public int getLineNumber() {
              // search for the first external entity on the stack
              int size = fEntityStack.size();
              for (int i = size - 1; i >= 0; --i) {
                  ScannedEntity firstExternalEntity = (ScannedEntity) fEntityStack.elementAt(i);
                  if (firstExternalEntity.isExternal()) {
                      return firstExternalEntity.lineNumber;
                  }
              }
              return -1;
          }
  
          // return column number of position in most
          // recent external entity
          public int getColumnNumber() {
              // search for the first external entity on the stack
              int size = fEntityStack.size();
              for (int i = size - 1; i >= 0; --i) {
                  ScannedEntity firstExternalEntity = (ScannedEntity) fEntityStack.elementAt(i);
                  if (firstExternalEntity.isExternal()) {
                      return firstExternalEntity.columnNumber;
                  }
              }
              return -1;
          }
  
          // return character offset of position in most
          // recent external entity
          public int getCharacterOffset() {
              // search for the first external entity on the stack
              int size = fEntityStack.size();
              for (int i = size - 1; i >= 0; --i) {
                  ScannedEntity firstExternalEntity = (ScannedEntity) fEntityStack.elementAt(i);
                  if (firstExternalEntity.isExternal()) {
                      return firstExternalEntity.baseCharOffset + (firstExternalEntity.position - firstExternalEntity.startPosition);
                  }
              }
              return -1;
          }
  
          // return encoding of most recent external entity
          public String getEncoding() {
              // search for the first external entity on the stack
              int size = fEntityStack.size();
              for (int i = size - 1; i >= 0; --i) {
                  ScannedEntity firstExternalEntity = (ScannedEntity) fEntityStack.elementAt(i);
                  if (firstExternalEntity.isExternal()) {
                      return firstExternalEntity.encoding;
                  }
              }
              return null;
          }
  
          // return xml version of most recent external entity
          public String getXMLVersion() {
              // search for the first external entity on the stack
              int size = fEntityStack.size();
              for (int i = size - 1; i >= 0; --i) {
                  ScannedEntity firstExternalEntity = (ScannedEntity) fEntityStack.elementAt(i);
                  if (firstExternalEntity.isExternal()) {
                      return firstExternalEntity.xmlVersion;
                  }
              }
              return null;
          }
  
          /**
           * Returns whether the encoding of this entity was externally specified.
           **/
          public boolean isEncodingExternallySpecified() {
              return externallySpecifiedEncoding;
          }
  
          /**
           * Sets whether the encoding of this entity was externally specified.
           **/
          public void setEncodingExternallySpecified(boolean value) {
              externallySpecifiedEncoding = value;
          }
  
          //
          // Object methods
          //
  
          /**
           * Returns a string representation of this object.
           */
          @Override
          public String toString() {
  
              StringBuffer str = new StringBuffer();
              str.append("name=\"").append(name).append('"');
              str.append(",ch=");
              str.append(ch);
              str.append(",position=").append(position);
              str.append(",count=").append(count);
              str.append(",baseCharOffset=").append(baseCharOffset);
              str.append(",startPosition=").append(startPosition);
              return str.toString();
  
          } // toString():String
  
      } // class ScannedEntity
  
      /**
       * This class wraps the byte inputstreams we're presented with.
       * We need it because java.io.InputStreams don't provide
       * functionality to reread processed bytes, and they have a habit
       * of reading more than one character when you call their read()
       * methods.  This means that, once we discover the true (declared)
       * encoding of a document, we can neither backtrack to read the
       * whole doc again nor start reading where we are with a new
       * reader.
       * <p/>
       * This class allows rewinding an inputStream by allowing a mark
       * to be set, and the stream reset to that position.  <strong>The
       * class assumes that it needs to read one character per
       * invocation when it's read() method is inovked, but uses the
       * underlying InputStream's read(char[], offset length) method--it
       * won't buffer data read this way!</strong>
       *
       * @author Neil Graham, IBM
       * @author Glenn Marcy, IBM
       * @xerces.internal
       */
      protected final class RewindableInputStream extends InputStream {
  
          private InputStream fInputStream;
          private byte[] fData;
          private int fStartOffset;
          private int fEndOffset;
          private int fOffset;
          private int fLength;
          private int fMark;
  
          public RewindableInputStream(InputStream is) {
              fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE];
              fInputStream = is;
              fStartOffset = 0;
              fEndOffset = -1;
              fOffset = 0;
              fLength = 0;
              fMark = 0;
          }
  
          public void setStartOffset(int offset) {
              fStartOffset = offset;
          }
  
          public void rewind() {
              fOffset = fStartOffset;
          }
  
          public int readAndBuffer() throws IOException {
              if (fOffset == fData.length) {
                  byte[] newData = new byte[fOffset << 1];
                  System.arraycopy(fData, 0, newData, 0, fOffset);
                  fData = newData;
              }
              final int b = fInputStream.read();
              if (b == -1) {
                  fEndOffset = fOffset;
                  return -1;
              }
              fData[fLength++] = (byte) b;
              fOffset++;
              return b & 0xff;
          }
  
          @Override
          public int read() throws IOException {
              if (fOffset < fLength) {
                  return fData[fOffset++] & 0xff;
              }
              if (fOffset == fEndOffset) {
                  return -1;
              }
              if (fCurrentEntity.mayReadChunks) {
                  return fInputStream.read();
              }
              return readAndBuffer();
          }
  
          @Override
          public int read(byte[] b, int off, int len) throws IOException {
              final int bytesLeft = fLength - fOffset;
              if (bytesLeft == 0) {
                  if (fOffset == fEndOffset) {
                      return -1;
                  }
                  // better get some more for the voracious reader...
                  if (fCurrentEntity.mayReadChunks) {
                      return fInputStream.read(b, off, len);
                  }
                  int returnedVal = readAndBuffer();
                  if (returnedVal == -1) {
                      fEndOffset = fOffset;
                      return -1;
                  }
                  b[off] = (byte) returnedVal;
                  return 1;
              }
              if (len < bytesLeft) {
                  if (len <= 0) {
                      return 0;
                  }
              } else {
                  len = bytesLeft;
              }
              if (b != null) {
                  System.arraycopy(fData, fOffset, b, off, len);
              }
              fOffset += len;
              return len;
          }
  
          @Override
          public long skip(long n)
                  throws IOException {
              int bytesLeft;
              if (n <= 0) {
                  return 0;
              }
              bytesLeft = fLength - fOffset;
              if (bytesLeft == 0) {
                  if (fOffset == fEndOffset) {
                      return 0;
                  }
                  return fInputStream.skip(n);
              }
              if (n <= bytesLeft) {
                  fOffset += n;
                  return n;
              }
              fOffset += bytesLeft;
              if (fOffset == fEndOffset) {
                  return bytesLeft;
              }
              n -= bytesLeft;
             /*
              * In a manner of speaking, when this class isn't permitting more
              * than one byte at a time to be read, it is "blocking".  The
              * available() method should indicate how much can be read without
              * blocking, so while we're in this mode, it should only indicate
              * that bytes in its buffer are available; otherwise, the result of
              * available() on the underlying InputStream is appropriate.
              */
              return fInputStream.skip(n) + bytesLeft;
          }
  
          @Override
          public int available() throws IOException {
              final int bytesLeft = fLength - fOffset;
              if (bytesLeft == 0) {
                  if (fOffset == fEndOffset) {
                      return -1;
                  }
                  return fCurrentEntity.mayReadChunks ? fInputStream.available()
                          : 0;
              }
              return bytesLeft;
          }
  
          @Override
          public void mark(int howMuch) {
              fMark = fOffset;
          }
  
          @Override
          public void reset() {
              fOffset = fMark;
          }
  
          @Override
          public boolean markSupported() {
              return true;
          }
  
          @Override
          public void close() throws IOException {
              if (fInputStream != null) {
                  fInputStream.close();
                  fInputStream = null;
              }
          }
      } // end of RewindableInputStream class
  
  } // class XMLEntityManager
  