/* * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. */ /* * 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 com.sun.org.apache.xerces.internal.impl ; import com.sun.org.apache.xerces.internal.impl.io.ASCIIReader; import com.sun.org.apache.xerces.internal.impl.io.UCSReader; import com.sun.org.apache.xerces.internal.impl.io.UTF16Reader; import com.sun.org.apache.xerces.internal.impl.io.UTF8Reader; import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter; import com.sun.org.apache.xerces.internal.impl.validation.ValidationManager; import com.sun.org.apache.xerces.internal.util.*; import com.sun.org.apache.xerces.internal.util.URI; import com.sun.org.apache.xerces.internal.utils.XMLLimitAnalyzer; import com.sun.org.apache.xerces.internal.utils.XMLSecurityManager; import com.sun.org.apache.xerces.internal.utils.XMLSecurityPropertyManager; import com.sun.org.apache.xerces.internal.xni.Augmentations; import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier; import com.sun.org.apache.xerces.internal.xni.XNIException; import com.sun.org.apache.xerces.internal.xni.parser.*; import com.sun.xml.internal.stream.Entity; import com.sun.xml.internal.stream.StaxEntityResolverWrapper; import com.sun.xml.internal.stream.StaxXMLInputSource; import com.sun.xml.internal.stream.XMLEntityStorage; import java.io.*; import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Stack; import java.util.StringTokenizer; import javax.xml.XMLConstants; import javax.xml.catalog.CatalogException; import javax.xml.catalog.CatalogFeatures.Feature; import javax.xml.catalog.CatalogFeatures; import javax.xml.catalog.CatalogManager; import javax.xml.catalog.CatalogResolver; import javax.xml.stream.XMLInputFactory; import javax.xml.transform.Source; import jdk.xml.internal.JdkXmlUtils; import jdk.xml.internal.SecuritySupport; import org.xml.sax.InputSource; /** * Will keep track of current entity. * * 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. *
* This component requires the following features and properties from the * component manager that uses it: *
* Note: This method ignores subsequent entity * declarations. *
* Note: 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 Entity.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) /** * Adds an external entity declaration. *
* Note: This method ignores subsequent entity * declarations. *
* Note: 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 the entity being added 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--) { Entity.ScannedEntity externalEntity = (Entity.ScannedEntity)fEntityStack.get(i); if (externalEntity.entityLocation != null && externalEntity.entityLocation.getExpandedSystemId() != null) { baseSystemId = externalEntity.entityLocation.getExpandedSystemId(); break; } } } Entity entity = new Entity.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) /** * Adds an unparsed entity declaration. *
* Note: This method ignores subsequent entity * declarations. *
* Note: 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.ExternalEntity entity = new Entity.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)
/** get the entity storage object from entity manager */
public XMLEntityStorage getEntityStore(){
return fEntityStorage ;
}
/** return the entity responsible for reading the entity */
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;
}
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);
}
}
/**
* This method uses the passed-in XMLInputSource to make
* fCurrentEntity usable for reading.
*
* @param reference flag to indicate whether the entity is an Entity Reference.
* @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.
* @throws IOException if anything can't be read
* XNIException If any parser-specific goes wrong.
* @return the encoding of the new entity or null if a character stream was employed
*/
public String setupCurrentEntity(boolean reference, 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;
// 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
* This method can be used to insert an application defined XML
* entity stream into the parsing stream.
*
* @param isGE flag to indicate whether the entity is a General Entity
* @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(boolean isGE, String name,
XMLInputSource xmlInputSource,
boolean literal, boolean isExternal)
throws IOException, XNIException {
String encoding = setupCurrentEntity(isGE, 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 represents the nesting level of open entities.
fEntityExpansionCount++;
if(fLimitAnalyzer != null) {
fLimitAnalyzer.addValue(entityExpansionIndex, name, 1);
}
if( fSecurityManager != null && fSecurityManager.isOverLimit(entityExpansionIndex, fLimitAnalyzer)){
fSecurityManager.debugPrint(fLimitAnalyzer);
fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,"EntityExpansionLimit",
new Object[]{fSecurityManager.getLimitValueByIndex(entityExpansionIndex)},
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)
/**
* Return the current entity being scanned. Current entity is SET using startEntity function.
* @return Entity.ScannedEntity
*/
public Entity.ScannedEntity getCurrentEntity(){
return fCurrentEntity ;
}
/**
* Return the top level entity handled by this manager, or null
* if no entity was added.
*/
public Entity.ScannedEntity getTopLevelEntity() {
return (Entity.ScannedEntity)
(fEntityStack.empty() ? null : fEntityStack.get(0));
}
// A stack containing all the open readers
protected Stack
* Note: 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.
*/
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;
}
if (suffixLength == Constants.LOAD_EXTERNAL_DTD_FEATURE.length() &&
featureId.endsWith(Constants.LOAD_EXTERNAL_DTD_FEATURE)) {
fLoadExternalDTD = state;
return;
}
} else if (featureId.equals(XMLConstants.USE_CATALOG)) {
fUseCatalog = state;
}
} // setFeature(String,boolean)
/**
* Sets the value of a property. This method is called by the component
* manager any time after reset when a property changes value.
*
* Note: 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.
*/
public void setProperty(String propertyId, Object value){
// 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);
}
}
if (suffixLength == Constants.SECURITY_MANAGER_PROPERTY.length() &&
propertyId.endsWith(Constants.SECURITY_MANAGER_PROPERTY)) {
fSecurityManager = (XMLSecurityManager)value;
}
}
//JAXP 1.5 properties
if (propertyId.equals(XML_SECURITY_PROPERTY_MANAGER))
{
XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager)value;
fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
return;
}
//Catalog properties
if (propertyId.equals(JdkXmlUtils.CATALOG_FILES)) {
fCatalogFile = (String)value;
} else if (propertyId.equals(JdkXmlUtils.CATALOG_DEFER)) {
fDefer = (String)value;
} else if (propertyId.equals(JdkXmlUtils.CATALOG_PREFER)) {
fPrefer = (String)value;
} else if (propertyId.equals(JdkXmlUtils.CATALOG_RESOLVE)) {
fResolve = (String)value;
}
}
public void setLimitAnalyzer(XMLLimitAnalyzer fLimitAnalyzer) {
this.fLimitAnalyzer = fLimitAnalyzer;
}
/**
* 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.
*/
public String[] getRecognizedProperties() {
return RECOGNIZED_PROPERTIES.clone();
} // getRecognizedProperties():String[]
/**
* 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
*/
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
*/
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
//
// Public static methods
//
/**
* 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) {
return expandSystemId(systemId, null);
} // expandSystemId(String):String
//
// Public static methods
//
// 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;
// which ASCII characters need to be escaped
private static boolean gNeedEscaping[] = new boolean[128];
// the first hex character if a character needs to be escaped
private static char gAfterEscaping1[] = new char[128];
// the second hex character if a character needs to be escaped
private static char gAfterEscaping2[] = new char[128];
private static char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
// 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];
}
}
// 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 = SecuritySupport.getSystemProperty("user.dir");
}
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;
StringBuilder buffer = new StringBuilder(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;
}
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, "%");
StringBuilder result = new StringBuilder(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;
}
/**
* 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) {
// check for bad parameters id
if (systemId == null || systemId.length() == 0) {
return systemId;
}
// if id already expanded, return
try {
URI uri = new URI(systemId);
if (uri != null) {
return systemId;
}
} catch (URI.MalformedURIException e) {
// continue on...
}
// normalize id
String id = fixURI(systemId);
// normalize base
URI base = null;
URI uri = null;
try {
if (baseSystemId == null || baseSystemId.length() == 0 ||
baseSystemId.equals(systemId)) {
String dir = getUserDir().toString();
base = new URI("file", "", dir, null, null);
} else {
try {
base = new URI(fixURI(baseSystemId));
} 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), null, null);
} else {
String dir = getUserDir().toString();
dir = dir + fixURI(baseSystemId);
base = new URI("file", "", dir, null, null);
}
}
}
// expand id
uri = new URI(base, id);
} catch (Exception e) {
// let it go through
}
if (uri == null) {
return systemId;
}
return uri.toString();
} // expandSystemId(String,String):String
/**
* 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) {
try {
// if it's already an absolute one, return it
new URI(systemId);
return systemId;
}
catch (URI.MalformedURIException ex) {
}
URI base = null;
// if there isn't a base uri, use the working directory
if (baseSystemId == null || baseSystemId.length() == 0) {
base = new URI("file", "", getUserDir().toString(), null, null);
}
// otherwise, use the base uri
else {
try {
base = new URI(baseSystemId);
}
catch (URI.MalformedURIException e) {
// assume "base" is also a relative uri
String dir = getUserDir().toString();
dir = dir + baseSystemId;
base = new URI("file", "", dir, null, null);
}
}
// absolutize the system id using the base
URI uri = new URI(base, systemId);
// return the string rep of the new uri (an absolute one)
return uri.toString();
// if any exception is thrown, it'll get thrown to the caller.
}
// 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) {
/** Xerces URI rejects unicode, try java.net.URI
* this is not ideal solution, but it covers known cases which either
* Xerces URI or java.net.URI can handle alone
* will file bug against java.net.URI
*/
try {
return expandSystemIdStrictOff1(systemId, baseSystemId);
} catch (URISyntaxException ex) {
// 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
private static String expandSystemIdStrictOff1(String systemId, String baseSystemId)
throws URISyntaxException, URI.MalformedURIException {
java.net.URI systemURI = new java.net.URI(systemId);
// If it's already an absolute one, return it
if (systemURI.isAbsolute()) {
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 URISyntaxException(systemId, "the scheme's length is only one character");
}
// 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);
systemURI = (new java.net.URI(baseURI.toString())).resolve(systemURI);
// 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
//
// Protected methods
//
/**
* 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 if unknown or irrelevant.
*
* @return Returns a reader.
*/
protected Reader createReader(InputStream inputStream, String encoding, Boolean isBigEndian)
throws IOException {
String enc = (encoding != null) ? encoding : EncodingInfo.STR_UTF8;
enc = enc.toUpperCase(Locale.ENGLISH);
MessageFormatter f = fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN);
Locale l = fErrorReporter.getLocale();
switch (enc) {
case EncodingInfo.STR_UTF8:
return new UTF8Reader(inputStream, fBufferSize, f, l);
case EncodingInfo.STR_UTF16:
if (isBigEndian != null) {
return new UTF16Reader(inputStream, fBufferSize, isBigEndian, f, l);
}
break;
case EncodingInfo.STR_UTF16BE:
return new UTF16Reader(inputStream, fBufferSize, true, f, l);
case EncodingInfo.STR_UTF16LE:
return new UTF16Reader(inputStream, fBufferSize, false, f, l);
case EncodingInfo.STR_UCS4:
if(isBigEndian != null) {
if(isBigEndian) {
return new UCSReader(inputStream, UCSReader.UCS4BE);
} else {
return new UCSReader(inputStream, UCSReader.UCS4LE);
}
} else {
fErrorReporter.reportError(this.getEntityScanner(),
XMLMessageFormatter.XML_DOMAIN,
"EncodingByteOrderUnsupported",
new Object[] { encoding },
XMLErrorReporter.SEVERITY_FATAL_ERROR);
}
break;
case EncodingInfo.STR_UCS2:
if(isBigEndian != null) {
if(isBigEndian) {
return new UCSReader(inputStream, UCSReader.UCS2BE);
} else {
return new UCSReader(inputStream, UCSReader.UCS2LE);
}
} else {
fErrorReporter.reportError(this.getEntityScanner(),
XMLMessageFormatter.XML_DOMAIN,
"EncodingByteOrderUnsupported",
new Object[] { encoding },
XMLErrorReporter.SEVERITY_FATAL_ERROR);
}
break;
}
// check for valid name
boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
boolean validJava = XMLChar.isValidJavaEncoding(encoding);
if (!validIANA || (fAllowJavaEncodings && !validJava)) {
fErrorReporter.reportError(this.getEntityScanner(),
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
encoding = "ISO-8859-1";
}
// try to use a Java reader
String javaEncoding = EncodingMap.getIANA2JavaMapping(enc);
if (javaEncoding == null) {
if (fAllowJavaEncodings) {
javaEncoding = encoding;
} else {
fErrorReporter.reportError(this.getEntityScanner(),
XMLMessageFormatter.XML_DOMAIN,
"EncodingDeclInvalid",
new Object[] { encoding },
XMLErrorReporter.SEVERITY_FATAL_ERROR);
// see comment above.
javaEncoding = "ISO8859_1";
}
}
if (DEBUG_ENCODINGS) {
System.out.print("$$$ creating Java InputStreamReader: encoding="+javaEncoding);
if (javaEncoding == encoding) {
System.out.print(" (IANA encoding)");
}
System.out.println();
}
return new BufferedReader( new InputStreamReader(inputStream, javaEncoding));
} // createReader(InputStream,String, Boolean): Reader
/**
* Return the public identifier for the current document event.
*
* The return value is the public identifier of the document
* entity or of the external parsed entity in which the markup
* triggering the event appears.
*
* @return A string containing the public identifier, or
* null if none is available.
*/
public String getPublicId() {
return (fCurrentEntity != null && fCurrentEntity.entityLocation != null) ? fCurrentEntity.entityLocation.getPublicId() : null;
} // getPublicId():String
/**
* Return the expanded system identifier for the current document event.
*
* The return value is the expanded system identifier of the document
* entity or of the external parsed entity in which the markup
* triggering the event appears.
*
* If the system identifier is a URL, the parser must resolve it
* fully before passing it to the application.
*
* @return A string containing the expanded system identifier, or null
* if none is available.
*/
public String getExpandedSystemId() {
if (fCurrentEntity != null) {
if (fCurrentEntity.entityLocation != null &&
fCurrentEntity.entityLocation.getExpandedSystemId() != null ) {
return fCurrentEntity.entityLocation.getExpandedSystemId();
} else {
// search for the first external entity on the stack
int size = fEntityStack.size();
for (int i = size - 1; i >= 0 ; i--) {
Entity.ScannedEntity externalEntity =
(Entity.ScannedEntity)fEntityStack.get(i);
if (externalEntity.entityLocation != null &&
externalEntity.entityLocation.getExpandedSystemId() != null) {
return externalEntity.entityLocation.getExpandedSystemId();
}
}
}
}
return null;
} // getExpandedSystemId():String
/**
* Return the literal system identifier for the current document event.
*
* The return value is the literal system identifier of the document
* entity or of the external parsed entity in which the markup
* triggering the event appears.
*
* @return A string containing the literal system identifier, or null
* if none is available.
*/
public String getLiteralSystemId() {
if (fCurrentEntity != null) {
if (fCurrentEntity.entityLocation != null &&
fCurrentEntity.entityLocation.getLiteralSystemId() != null ) {
return fCurrentEntity.entityLocation.getLiteralSystemId();
} else {
// search for the first external entity on the stack
int size = fEntityStack.size();
for (int i = size - 1; i >= 0 ; i--) {
Entity.ScannedEntity externalEntity =
(Entity.ScannedEntity)fEntityStack.get(i);
if (externalEntity.entityLocation != null &&
externalEntity.entityLocation.getLiteralSystemId() != null) {
return externalEntity.entityLocation.getLiteralSystemId();
}
}
}
}
return null;
} // getLiteralSystemId():String
/**
* Return the line number where the current document event ends.
*
* Warning: The return value from the method
* is intended only as an approximation for the sake of error
* reporting; it is not intended to provide sufficient information
* to edit the character content of the original XML document.
*
* The return value is an approximation of the line number
* in the document entity or external parsed entity where the
* markup triggering the event appears.
*
* If possible, the SAX driver should provide the line position
* of the first character after the text associated with the document
* event. The first line in the document is line 1.
*
* @return The line number, or -1 if none is available.
*/
public int getLineNumber() {
if (fCurrentEntity != null) {
if (fCurrentEntity.isExternal()) {
return fCurrentEntity.lineNumber;
} else {
// search for the first external entity on the stack
int size = fEntityStack.size();
for (int i=size-1; i>0 ; i--) {
Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity)fEntityStack.get(i);
if (firstExternalEntity.isExternal()) {
return firstExternalEntity.lineNumber;
}
}
}
}
return -1;
} // getLineNumber():int
/**
* Return the column number where the current document event ends.
*
* Warning: The return value from the method
* is intended only as an approximation for the sake of error
* reporting; it is not intended to provide sufficient information
* to edit the character content of the original XML document.
*
* The return value is an approximation of the column number
* in the document entity or external parsed entity where the
* markup triggering the event appears.
*
* If possible, the SAX driver should provide the line position
* of the first character after the text associated with the document
* event.
*
* If possible, the SAX driver should provide the line position
* of the first character after the text associated with the document
* event. The first column in each line is column 1.
*
* @return The column number, or -1 if none is available.
*/
public int getColumnNumber() {
if (fCurrentEntity != null) {
if (fCurrentEntity.isExternal()) {
return fCurrentEntity.columnNumber;
} else {
// search for the first external entity on the stack
int size = fEntityStack.size();
for (int i=size-1; i>0 ; i--) {
Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity)fEntityStack.get(i);
if (firstExternalEntity.isExternal()) {
return firstExternalEntity.columnNumber;
}
}
}
}
return -1;
} // getColumnNumber():int
//
// Protected static methods
//
/**
* 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, '/');
// Windows fix
if (str.length() >= 2) {
char ch1 = str.charAt(1);
// change "C:blah" to "/C:blah"
if (ch1 == ':') {
char ch0 = Character.toUpperCase(str.charAt(0));
if (ch0 >= 'A' && ch0 <= 'Z') {
str = "/" + str;
}
}
// change "//blah" to "file://blah"
else if (ch1 == '/' && str.charAt(0) == '/') {
str = "file:" + str;
}
}
// replace spaces in file names with %20.
// Original comment from JDK5: the following algorithm might not be
// very performant, but people who want to use invalid URI's have to
// pay the price.
int pos = str.indexOf(' ');
if (pos >= 0) {
StringBuilder sb = new StringBuilder(str.length());
// put characters before ' ' into the string builder
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
//
// Package visible methods
//
/** Prints the contents of the buffer. */
final void print() {
if (DEBUG_BUFFER) {
if (fCurrentEntity != null) {
System.out.print('[');
System.out.print(fCurrentEntity.count);
System.out.print(' ');
System.out.print(fCurrentEntity.position);
if (fCurrentEntity.count > 0) {
System.out.print(" \"");
for (int i = 0; i < fCurrentEntity.count; i++) {
if (i == fCurrentEntity.position) {
System.out.print('^');
}
char c = fCurrentEntity.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 (fCurrentEntity.position == fCurrentEntity.count) {
System.out.print('^');
}
System.out.print('"');
}
System.out.print(']');
System.out.print(" @ ");
System.out.print(fCurrentEntity.lineNumber);
System.out.print(',');
System.out.print(fCurrentEntity.columnNumber);
} else {
System.out.print("*NO CURRENT ENTITY*");
}
}
} // print()
/**
* Information about auto-detectable encodings.
*
* @xerces.internal
*
* @author Michael Glavassevich, IBM
*/
private static class EncodingInfo {
public static final String STR_UTF8 = "UTF-8";
public static final String STR_UTF16 = "UTF-16";
public static final String STR_UTF16BE = "UTF-16BE";
public static final String STR_UTF16LE = "UTF-16LE";
public static final String STR_UCS4 = "ISO-10646-UCS-4";
public static final String STR_UCS2 = "ISO-10646-UCS-2";
public static final String STR_CP037 = "CP037";
/** UTF-8 **/
public static final EncodingInfo UTF_8 =
new EncodingInfo(STR_UTF8, null, false);
/** UTF-8, with BOM **/
public static final EncodingInfo UTF_8_WITH_BOM =
new EncodingInfo(STR_UTF8, null, true);
/** UTF-16, big-endian **/
public static final EncodingInfo UTF_16_BIG_ENDIAN =
new EncodingInfo(STR_UTF16BE, STR_UTF16, Boolean.TRUE, false);
/** UTF-16, big-endian with BOM **/
public static final EncodingInfo UTF_16_BIG_ENDIAN_WITH_BOM =
new EncodingInfo(STR_UTF16BE, STR_UTF16, Boolean.TRUE, true);
/** UTF-16, little-endian **/
public static final EncodingInfo UTF_16_LITTLE_ENDIAN =
new EncodingInfo(STR_UTF16LE, STR_UTF16, Boolean.FALSE, false);
/** UTF-16, little-endian with BOM **/
public static final EncodingInfo UTF_16_LITTLE_ENDIAN_WITH_BOM =
new EncodingInfo(STR_UTF16LE, STR_UTF16, Boolean.FALSE, true);
/** UCS-4, big-endian **/
public static final EncodingInfo UCS_4_BIG_ENDIAN =
new EncodingInfo(STR_UCS4, Boolean.TRUE, false);
/** UCS-4, little-endian **/
public static final EncodingInfo UCS_4_LITTLE_ENDIAN =
new EncodingInfo(STR_UCS4, Boolean.FALSE, false);
/** UCS-4, unusual byte-order (2143) or (3412) **/
public static final EncodingInfo UCS_4_UNUSUAL_BYTE_ORDER =
new EncodingInfo(STR_UCS4, null, false);
/** EBCDIC **/
public static final EncodingInfo EBCDIC = new EncodingInfo(STR_CP037, null, false);
public final String autoDetectedEncoding;
public final String readerEncoding;
public final Boolean isBigEndian;
public final boolean hasBOM;
private EncodingInfo(String autoDetectedEncoding, Boolean isBigEndian, boolean hasBOM) {
this(autoDetectedEncoding, autoDetectedEncoding, isBigEndian, hasBOM);
} //