0001: /*
0002: * $Id: XMLEntityManager.java,v 1.6 2007/03/16 14:48:45 spericas Exp $
0003: */
0004:
0005: /*
0006: * The contents of this file are subject to the terms
0007: * of the Common Development and Distribution License
0008: * (the License). You may not use this file except in
0009: * compliance with the License.
0010: *
0011: * You can obtain a copy of the license at
0012: * https://glassfish.dev.java.net/public/CDDLv1.0.html.
0013: * See the License for the specific language governing
0014: * permissions and limitations under the License.
0015: *
0016: * When distributing Covered Code, include this CDDL
0017: * Header Notice in each file and include the License file
0018: * at https://glassfish.dev.java.net/public/CDDLv1.0.html.
0019: * If applicable, add the following below the CDDL Header,
0020: * with the fields enclosed by brackets [] replaced by
0021: * you own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * [Name of File] [ver.__] [Date]
0025: *
0026: * Copyright 2006 Sun Microsystems Inc. All Rights Reserved
0027: */
0028:
0029: /*
0030: * The Apache Software License, Version 1.1
0031: *
0032: *
0033: * Copyright (c) 1999-2002 The Apache Software Foundation.
0034: * All rights reserved.
0035: *
0036: * Redistribution and use in source and binary forms, with or without
0037: * modification, are permitted provided that the following conditions
0038: * are met:
0039: *
0040: * 1. Redistributions of source code must retain the above copyright
0041: * notice, this list of conditions and the following disclaimer.
0042: *
0043: * 2. Redistributions in binary form must reproduce the above copyright
0044: * notice, this list of conditions and the following disclaimer in
0045: * the documentation and/or other materials provided with the
0046: * distribution.
0047: *
0048: * 3. The end-user documentation included with the redistribution,
0049: * if any, must include the following acknowledgment:
0050: * "This product includes software developed by the
0051: * Apache Software Foundation (http://www.apache.org/)."
0052: * Alternately, this acknowledgment may appear in the software itself,
0053: * if and wherever such third-party acknowledgments normally appear.
0054: *
0055: * 4. The names "Xerces" and "Apache Software Foundation" must
0056: * not be used to endorse or promote products derived from this
0057: * software without prior written permission. For written
0058: * permission, please contact apache@apache.org.
0059: *
0060: * 5. Products derived from this software may not be called "Apache",
0061: * nor may "Apache" appear in their name, without prior written
0062: * permission of the Apache Software Foundation.
0063: *
0064: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0065: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0066: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0067: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0068: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0069: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0070: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0071: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0072: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0073: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0074: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0075: * SUCH DAMAGE.
0076: */
0077:
0078: package com.sun.xml.stream;
0079:
0080: import java.io.*;
0081: import java.io.BufferedReader;
0082: import java.net.URL;
0083: import java.util.*;
0084:
0085: import com.sun.xml.stream.xerces.impl.io.*;
0086: import com.sun.xml.stream.xerces.impl.msg.XMLMessageFormatter;
0087: import com.sun.xml.stream.xerces.util.*;
0088: import com.sun.xml.stream.xerces.xni.XMLResourceIdentifier;
0089: import com.sun.xml.stream.xerces.xni.XNIException;
0090: import com.sun.xml.stream.xerces.xni.parser.*;
0091:
0092: /**
0093: * Will keep track of current entity.
0094: *
0095: * The entity manager handles the registration of general and parameter
0096: * entities; resolves entities; and starts entities. The entity manager
0097: * is a central component in a standard parser configuration and this
0098: * class works directly with the entity scanner to manage the underlying
0099: * xni.
0100: * <p>
0101: * This component requires the following features and properties from the
0102: * component manager that uses it:
0103: * <ul>
0104: * <li>http://xml.org/sax/features/validation</li>
0105: * <li>http://xml.org/sax/features/external-general-entities</li>
0106: * <li>http://xml.org/sax/features/external-parameter-entities</li>
0107: * <li>http://apache.org/xml/features/allow-java-encodings</li>
0108: * <li>http://apache.org/xml/properties/internal/symbol-table</li>
0109: * <li>http://apache.org/xml/properties/internal/error-reporter</li>
0110: * <li>http://apache.org/xml/properties/internal/entity-resolver</li>
0111: * </ul>
0112: *
0113: *
0114: * @author Andy Clark, IBM
0115: * @author Arnaud Le Hors, IBM
0116: * @author K.Venugopal SUN Microsystems
0117: * @author Neeraj Bajaj SUN Microsystems
0118: *
0119: * @version $Id: XMLEntityManager.java,v 1.6 2007/03/16 14:48:45 spericas Exp $
0120: */
0121: public class XMLEntityManager implements XMLComponent,
0122: XMLEntityResolver {
0123:
0124: //
0125: // Constants
0126: //
0127:
0128: /** Default buffer size (2048). */
0129: public static final int DEFAULT_BUFFER_SIZE = 8192;
0130:
0131: /** Default buffer size before we've finished with the XMLDecl: */
0132: public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64;
0133:
0134: /** Default internal entity buffer size (1024). */
0135: public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 1024;
0136:
0137: // feature identifiers
0138:
0139: /** Feature identifier: validation. */
0140: protected static final String VALIDATION = Constants.SAX_FEATURE_PREFIX
0141: + Constants.VALIDATION_FEATURE;
0142:
0143: /** Feature identifier: external general entities. */
0144: protected static final String EXTERNAL_GENERAL_ENTITIES = Constants.SAX_FEATURE_PREFIX
0145: + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE;
0146:
0147: /** Feature identifier: external parameter entities. */
0148: protected static final String EXTERNAL_PARAMETER_ENTITIES = Constants.SAX_FEATURE_PREFIX
0149: + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE;
0150:
0151: /** Feature identifier: allow Java encodings. */
0152: protected static final String ALLOW_JAVA_ENCODINGS = Constants.XERCES_FEATURE_PREFIX
0153: + Constants.ALLOW_JAVA_ENCODINGS_FEATURE;
0154:
0155: /** Feature identifier: warn on duplicate EntityDef */
0156: protected static final String WARN_ON_DUPLICATE_ENTITYDEF = Constants.XERCES_FEATURE_PREFIX
0157: + Constants.WARN_ON_DUPLICATE_ENTITYDEF_FEATURE;
0158:
0159: // property identifiers
0160:
0161: /** Property identifier: symbol table. */
0162: protected static final String SYMBOL_TABLE = Constants.XERCES_PROPERTY_PREFIX
0163: + Constants.SYMBOL_TABLE_PROPERTY;
0164:
0165: /** Property identifier: error reporter. */
0166: protected static final String ERROR_REPORTER = Constants.XERCES_PROPERTY_PREFIX
0167: + Constants.ERROR_REPORTER_PROPERTY;
0168:
0169: /** Property identifier: entity resolver. */
0170: protected static final String ENTITY_RESOLVER = Constants.XERCES_PROPERTY_PREFIX
0171: + Constants.ENTITY_RESOLVER_PROPERTY;
0172:
0173: protected static final String STAX_ENTITY_RESOLVER = Constants.XERCES_PROPERTY_PREFIX
0174: + Constants.STAX_ENTITY_RESOLVER_PROPERTY;
0175:
0176: // property identifier: ValidationManager
0177: protected static final String VALIDATION_MANAGER = Constants.XERCES_PROPERTY_PREFIX
0178: + Constants.VALIDATION_MANAGER_PROPERTY;
0179:
0180: /** property identifier: buffer size. */
0181: protected static final String BUFFER_SIZE = Constants.XERCES_PROPERTY_PREFIX
0182: + Constants.BUFFER_SIZE_PROPERTY;
0183:
0184: // recognized features and properties
0185:
0186: /** Recognized features. */
0187: private static final String[] RECOGNIZED_FEATURES = { VALIDATION,
0188: EXTERNAL_GENERAL_ENTITIES, EXTERNAL_PARAMETER_ENTITIES,
0189: ALLOW_JAVA_ENCODINGS, WARN_ON_DUPLICATE_ENTITYDEF };
0190:
0191: /** Feature defaults. */
0192: private static final Boolean[] FEATURE_DEFAULTS = { null,
0193: Boolean.TRUE, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE, };
0194:
0195: /** Recognized properties. */
0196: private static final String[] RECOGNIZED_PROPERTIES = {
0197: SYMBOL_TABLE, ERROR_REPORTER, ENTITY_RESOLVER,
0198: VALIDATION_MANAGER, BUFFER_SIZE };
0199:
0200: /** Property defaults. */
0201: private static final Object[] PROPERTY_DEFAULTS = { null, null,
0202: null, null, new Integer(DEFAULT_BUFFER_SIZE), };
0203:
0204: private static final String XMLEntity = "[xml]".intern();
0205: private static final String DTDEntity = "[dtd]".intern();
0206:
0207: // debugging
0208:
0209: /**
0210: * Debug printing of buffer. This debugging flag works best when you
0211: * resize the DEFAULT_BUFFER_SIZE down to something reasonable like
0212: * 64 characters.
0213: */
0214: private static final boolean DEBUG_BUFFER = false;
0215:
0216: /** Debug some basic entities. */
0217: private static final boolean DEBUG_ENTITIES = false;
0218:
0219: /** Debug switching readers for encodings. */
0220: private static final boolean DEBUG_ENCODINGS = false;
0221:
0222: // should be diplayed trace resolving messages
0223: private static final boolean DEBUG_RESOLVER = false;
0224:
0225: //
0226: // Data
0227: //
0228:
0229: // features
0230:
0231: /**
0232: * Validation. This feature identifier is:
0233: * http://xml.org/sax/features/validation
0234: */
0235: protected boolean fValidation;
0236:
0237: /**
0238: * External general entities. This feature identifier is:
0239: * http://xml.org/sax/features/external-general-entities
0240: */
0241: protected boolean fExternalGeneralEntities;
0242:
0243: /**
0244: * External parameter entities. This feature identifier is:
0245: * http://xml.org/sax/features/external-parameter-entities
0246: */
0247: protected boolean fExternalParameterEntities;
0248:
0249: /**
0250: * Allow Java encoding names. This feature identifier is:
0251: * http://apache.org/xml/features/allow-java-encodings
0252: */
0253: protected boolean fAllowJavaEncodings = true;
0254:
0255: // properties
0256:
0257: /**
0258: * Symbol table. This property identifier is:
0259: * http://apache.org/xml/properties/internal/symbol-table
0260: */
0261: protected SymbolTable fSymbolTable;
0262:
0263: /**
0264: * Error reporter. This property identifier is:
0265: * http://apache.org/xml/properties/internal/error-reporter
0266: */
0267: protected XMLErrorReporter fErrorReporter;
0268:
0269: /**
0270: * Entity resolver. This property identifier is:
0271: * http://apache.org/xml/properties/internal/entity-resolver
0272: */
0273: protected XMLEntityResolver fEntityResolver;
0274: protected StaxEntityResolverWrapper fStaxEntityResolver;
0275: protected PropertyManager fPropertyManager;
0276:
0277: // settings
0278:
0279: /**
0280: * Buffer size. We get this value from a property. The default size
0281: * is used if the input buffer size property is not specified.
0282: * REVISIT: do we need a property for internal entity buffer size?
0283: */
0284: protected int fBufferSize = DEFAULT_BUFFER_SIZE;
0285:
0286: /**
0287: * True if the document entity is standalone. This should really
0288: * only be set by the document source (e.g. XMLDocumentScanner).
0289: */
0290: protected boolean fStandalone;
0291:
0292: // are the entities being parsed in the external subset?
0293: // NOTE: this *is not* the same as whether they're external entities!
0294: protected boolean fInExternalSubset = false;
0295:
0296: // handlers
0297:
0298: /** Entity handler. */
0299: protected XMLEntityHandler fEntityHandler;
0300:
0301: protected XMLEntityReaderImpl fEntityReader;
0302:
0303: // entities
0304:
0305: /** Entities. */
0306: protected Hashtable fEntities = new Hashtable();
0307:
0308: /** Entity stack. */
0309: protected Stack fEntityStack = new Stack();
0310:
0311: /** Current entity. */
0312: protected Entity.ScannedEntity fCurrentEntity = null;
0313:
0314: // shared context
0315:
0316: /** Shared declared entities.
0317: * XXX understand it more deeply, why are we doing this ?? Is it really required ?
0318: */
0319: protected Hashtable fDeclaredEntities;
0320:
0321: protected XMLEntityStorage fEntityStorage;
0322:
0323: protected final Object[] defaultEncoding = new Object[] { "UTF-8",
0324: null };
0325:
0326: // temp vars
0327:
0328: /** Resource identifer. */
0329: private final XMLResourceIdentifierImpl fResourceIdentifier = new XMLResourceIdentifierImpl();
0330:
0331: //
0332: // Constructors
0333: //
0334:
0335: /**
0336: * If this constructor is used to create the object, reset() should be invoked on this object
0337: */
0338: public XMLEntityManager() {
0339: //pass a reference to current entity being scanned
0340: //fEntityStorage = new XMLEntityStorage(fCurrentEntity) ;
0341: fEntityStorage = new XMLEntityStorage(this );
0342: fEntityReader = new XMLEntityReaderImpl(this );
0343: } // <init>()
0344:
0345: /** Default constructor. */
0346: public XMLEntityManager(PropertyManager propertyManager) {
0347: fPropertyManager = propertyManager;
0348: //pass a reference to current entity being scanned
0349: //fEntityStorage = new XMLEntityStorage(fCurrentEntity) ;
0350: fEntityStorage = new XMLEntityStorage(this );
0351: fEntityReader = new XMLEntityReaderImpl(propertyManager, this );
0352: reset(propertyManager);
0353: } // <init>()
0354:
0355: /** get the entity storage object from entity manager */
0356: public XMLEntityStorage getEntityStore() {
0357: return fEntityStorage;
0358: }
0359:
0360: /** return the entity responsible for reading the entity */
0361: public XMLEntityReader getEntityReader() {
0362: return fEntityReader;
0363: }
0364:
0365: //
0366: // Public methods
0367: //
0368:
0369: /**
0370: * Sets whether the document entity is standalone.
0371: *
0372: * @param standalone True if document entity is standalone.
0373: */
0374: public void setStandalone(boolean standalone) {
0375: fStandalone = standalone;
0376: }
0377:
0378: // setStandalone(boolean)
0379:
0380: /** Returns true if the document entity is standalone. */
0381: public boolean isStandalone() {
0382: return fStandalone;
0383: } //isStandalone():boolean
0384:
0385: /**
0386: * Sets the entity handler. When an entity starts and ends, the
0387: * entity handler is notified of the change.
0388: *
0389: * @param entityHandler The new entity handler.
0390: */
0391: public void setEntityHandler(XMLEntityHandler entityHandler) {
0392: fEntityHandler = entityHandler;
0393: } // setEntityHandler(XMLEntityHandler)
0394:
0395: //this function returns StaxXMLInputSource
0396: public StaxXMLInputSource resolveEntityAsPerStax(
0397: XMLResourceIdentifier resourceIdentifier)
0398: throws java.io.IOException {
0399:
0400: if (resourceIdentifier == null)
0401: return null;
0402:
0403: String publicId = resourceIdentifier.getPublicId();
0404: String literalSystemId = resourceIdentifier
0405: .getLiteralSystemId();
0406: String baseSystemId = resourceIdentifier.getBaseSystemId();
0407: String expandedSystemId = resourceIdentifier
0408: .getExpandedSystemId();
0409: // if no base systemId given, assume that it's relative
0410: // to the systemId of the current scanned entity
0411: // Sometimes the system id is not (properly) expanded.
0412: // We need to expand the system id if:
0413: // a. the expanded one was null; or
0414: // b. the base system id was null, but becomes non-null from the current entity.
0415: boolean needExpand = (expandedSystemId == null);
0416: // REVISIT: why would the baseSystemId ever be null? if we
0417: // didn't have to make this check we wouldn't have to reuse the
0418: // fXMLResourceIdentifier object...
0419: if (baseSystemId == null && fCurrentEntity != null
0420: && fCurrentEntity.entityLocation != null) {
0421: baseSystemId = fCurrentEntity.entityLocation
0422: .getExpandedSystemId();
0423: if (baseSystemId != null)
0424: needExpand = true;
0425: }
0426: if (needExpand)
0427: expandedSystemId = expandSystemId(literalSystemId,
0428: baseSystemId);
0429:
0430: // give the entity resolver a chance
0431: StaxXMLInputSource xmlInputSource = null;
0432: if (fStaxEntityResolver != null) {
0433: if (DEBUG_RESOLVER) {
0434: System.out.println("fStaxEntityResolver != null");
0435: }
0436: XMLResourceIdentifierImpl ri = null;
0437: if (resourceIdentifier instanceof XMLResourceIdentifierImpl) {
0438: ri = (XMLResourceIdentifierImpl) resourceIdentifier;
0439: } else {
0440: fResourceIdentifier.clear();
0441: ri = fResourceIdentifier;
0442: }
0443: ri.setValues(publicId, literalSystemId, baseSystemId,
0444: expandedSystemId);
0445: if (DEBUG_RESOLVER) {
0446: System.out.println("BEFORE Calling resolveEntity");
0447: }
0448: xmlInputSource = fStaxEntityResolver.resolveEntity(ri);
0449: }
0450:
0451: // do default resolution
0452: if (xmlInputSource == null) {
0453: // REVISIT: when systemId is null, I think we should return null.
0454: // is this the right solution? -SG
0455: //if (systemId != null)
0456: xmlInputSource = new StaxXMLInputSource(new XMLInputSource(
0457: publicId, literalSystemId, baseSystemId));
0458: } else if (xmlInputSource.hasXMLStreamOrXMLEventReader()) {
0459: //Waiting for the clarification from EG. - nb
0460: }
0461:
0462: if (DEBUG_RESOLVER) {
0463: System.err.println("XMLEntityManager.resolveEntity("
0464: + publicId + ")");
0465: System.err.println(" = " + xmlInputSource);
0466: }
0467:
0468: return xmlInputSource;
0469:
0470: }
0471:
0472: /**
0473: * Resolves the specified public and system identifiers. This
0474: * method first attempts to resolve the entity based on the
0475: * EntityResolver registered by the application. If no entity
0476: * resolver is registered or if the registered entity handler
0477: * is unable to resolve the entity, then default entity
0478: * resolution will occur.
0479: *
0480: * @param publicId The public identifier of the entity.
0481: * @param systemId The system identifier of the entity.
0482: * @param baseSystemId The base system identifier of the entity.
0483: * This is the system identifier of the current
0484: * entity and is used to expand the system
0485: * identifier when the system identifier is a
0486: * relative URI.
0487: *
0488: * @return Returns an input source that wraps the resolved entity.
0489: * This method will never return null.
0490: *
0491: * @throws IOException Thrown on i/o error.
0492: * @throws XNIException Thrown by entity resolver to signal an error.
0493: */
0494: public XMLInputSource resolveEntity(
0495: XMLResourceIdentifier resourceIdentifier)
0496: throws IOException, XNIException {
0497: if (resourceIdentifier == null)
0498: return null;
0499: String publicId = resourceIdentifier.getPublicId();
0500: String literalSystemId = resourceIdentifier
0501: .getLiteralSystemId();
0502: String baseSystemId = resourceIdentifier.getBaseSystemId();
0503: String expandedSystemId = resourceIdentifier
0504: .getExpandedSystemId();
0505: // if no base systemId given, assume that it's relative
0506: // to the systemId of the current scanned entity
0507: // Sometimes the system id is not (properly) expanded.
0508: // We need to expand the system id if:
0509: // a. the expanded one was null; or
0510: // b. the base system id was null, but becomes non-null from the current entity.
0511: boolean needExpand = (expandedSystemId == null);
0512: // REVISIT: why would the baseSystemId ever be null? if we
0513: // didn't have to make this check we wouldn't have to reuse the
0514: // fXMLResourceIdentifier object...
0515: if (baseSystemId == null && fCurrentEntity != null
0516: && fCurrentEntity.entityLocation != null) {
0517: baseSystemId = fCurrentEntity.entityLocation
0518: .getExpandedSystemId();
0519: if (baseSystemId != null)
0520: needExpand = true;
0521: }
0522: if (needExpand)
0523: expandedSystemId = expandSystemId(literalSystemId,
0524: baseSystemId);
0525:
0526: // give the entity resolver a chance
0527: XMLInputSource xmlInputSource = null;
0528: if (fEntityResolver != null) {
0529: XMLResourceIdentifierImpl ri = null;
0530: if (resourceIdentifier instanceof XMLResourceIdentifierImpl) {
0531: ri = (XMLResourceIdentifierImpl) resourceIdentifier;
0532: } else {
0533: fResourceIdentifier.clear();
0534: ri = fResourceIdentifier;
0535: }
0536: ri.setValues(publicId, literalSystemId, baseSystemId,
0537: expandedSystemId);
0538: xmlInputSource = fEntityResolver.resolveEntity(ri);
0539: }
0540:
0541: // do default resolution
0542: // REVISIT: what's the correct behavior if the user provided an entity
0543: // resolver (fEntityResolver != null), but resolveEntity doesn't return
0544: // an input source (xmlInputSource == null)?
0545: // do we do default resolution, or do we just return null? -SG
0546: if (xmlInputSource == null) {
0547: // REVISIT: when systemId is null, I think we should return null.
0548: // is this the right solution? -SG
0549: //if (systemId != null)
0550: xmlInputSource = new XMLInputSource(publicId,
0551: literalSystemId, baseSystemId);
0552: }
0553:
0554: if (DEBUG_RESOLVER) {
0555: System.err.println("XMLEntityManager.resolveEntity("
0556: + publicId + ")");
0557: System.err.println(" = " + xmlInputSource);
0558: }
0559:
0560: return xmlInputSource;
0561:
0562: } // resolveEntity(XMLResourceIdentifier):XMLInputSource
0563:
0564: /**
0565: * Starts a named entity.
0566: *
0567: * @param entityName The name of the entity to start.
0568: * @param literal True if this entity is started within a literal
0569: * value.
0570: *
0571: * @throws IOException Thrown on i/o error.
0572: * @throws XNIException Thrown by entity handler to signal an error.
0573: */
0574: public void startEntity(String entityName, boolean literal)
0575: throws IOException, XNIException {
0576:
0577: // was entity declared?
0578: Entity entity = (Entity) fEntityStorage.getDeclaredEntities()
0579: .get(entityName);
0580: if (entity == null) {
0581: if (fEntityHandler != null) {
0582: String encoding = null;
0583: fResourceIdentifier.clear();
0584: fEntityHandler.startEntity(entityName,
0585: fResourceIdentifier, encoding);
0586: fEntityHandler.endEntity(entityName);
0587: }
0588: return;
0589: }
0590:
0591: // should we skip external entities?
0592: boolean external = entity.isExternal();
0593: if (external) {
0594: boolean unparsed = entity.isUnparsed();
0595: boolean parameter = entityName.startsWith("%");
0596: boolean general = !parameter;
0597: if (unparsed || (general && !fExternalGeneralEntities)
0598: || (parameter && !fExternalParameterEntities)) {
0599:
0600: if (fEntityHandler != null) {
0601: fResourceIdentifier.clear();
0602: final String encoding = null;
0603: Entity.ExternalEntity externalEntity = (Entity.ExternalEntity) entity;
0604: //REVISIT: since we're storing expandedSystemId in the
0605: // externalEntity, how could this have got here if it wasn't already
0606: // expanded??? - neilg
0607: String extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation
0608: .getLiteralSystemId()
0609: : null);
0610: String extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation
0611: .getBaseSystemId()
0612: : null);
0613: String expandedSystemId = expandSystemId(
0614: extLitSysId, extBaseSysId);
0615: fResourceIdentifier
0616: .setValues(
0617: (externalEntity.entityLocation != null ? externalEntity.entityLocation
0618: .getPublicId()
0619: : null), extLitSysId,
0620: extBaseSysId, expandedSystemId);
0621: fEntityHandler.startEntity(entityName,
0622: fResourceIdentifier, encoding);
0623: fEntityHandler.endEntity(entityName);
0624: }
0625: return;
0626: }
0627: }
0628:
0629: // is entity recursive?
0630: int size = fEntityStack.size();
0631: for (int i = size; i >= 0; i--) {
0632: Entity activeEntity = i == size ? fCurrentEntity
0633: : (Entity) fEntityStack.elementAt(i);
0634: if (activeEntity.name == entityName) {
0635: String path = entityName;
0636: for (int j = i + 1; j < size; j++) {
0637: activeEntity = (Entity) fEntityStack.elementAt(j);
0638: path = path + " -> " + activeEntity.name;
0639: }
0640: path = path + " -> " + fCurrentEntity.name;
0641: path = path + " -> " + entityName;
0642: fErrorReporter.reportError(this .getEntityReader(),
0643: XMLMessageFormatter.XML_DOMAIN,
0644: "RecursiveReference", new Object[] {
0645: entityName, path },
0646: XMLErrorReporter.SEVERITY_FATAL_ERROR);
0647:
0648: if (fEntityHandler != null) {
0649: fResourceIdentifier.clear();
0650: final String encoding = null;
0651: if (external) {
0652: Entity.ExternalEntity externalEntity = (Entity.ExternalEntity) entity;
0653: // REVISIT: for the same reason above...
0654: String extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation
0655: .getLiteralSystemId()
0656: : null);
0657: String extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation
0658: .getBaseSystemId()
0659: : null);
0660: String expandedSystemId = expandSystemId(
0661: extLitSysId, extBaseSysId);
0662: fResourceIdentifier
0663: .setValues(
0664: (externalEntity.entityLocation != null ? externalEntity.entityLocation
0665: .getPublicId()
0666: : null), extLitSysId,
0667: extBaseSysId, expandedSystemId);
0668: }
0669: fEntityHandler.startEntity(entityName,
0670: fResourceIdentifier, encoding);
0671: //xxx
0672: fEntityHandler.endEntity(entityName);
0673: }
0674:
0675: return;
0676: }
0677: }
0678:
0679: // resolve external entity
0680: StaxXMLInputSource staxInputSource = null;
0681: XMLInputSource xmlInputSource = null;
0682:
0683: if (external) {
0684: Entity.ExternalEntity externalEntity = (Entity.ExternalEntity) entity;
0685: staxInputSource = resolveEntityAsPerStax(externalEntity.entityLocation);
0686: /** xxx: Waiting from the EG
0687: * //simply return if there was entity resolver registered and application
0688: * //returns either XMLStreamReader or XMLEventReader.
0689: * if(staxInputSource.hasXMLStreamOrXMLEventReader()) return ;
0690: */
0691: xmlInputSource = staxInputSource.getXMLInputSource();
0692: }
0693: // wrap internal entity
0694: else {
0695: Entity.InternalEntity internalEntity = (Entity.InternalEntity) entity;
0696: Reader reader = new StringReader(internalEntity.text);
0697: xmlInputSource = new XMLInputSource(null, null, null,
0698: reader, null);
0699: }
0700:
0701: // start the entity
0702: startEntity(entityName, xmlInputSource, literal, external);
0703:
0704: } // startEntity(String,boolean)
0705:
0706: /**
0707: * Starts the document entity. The document entity has the "[xml]"
0708: * pseudo-name.
0709: *
0710: * @param xmlInputSource The input source of the document entity.
0711: *
0712: * @throws IOException Thrown on i/o error.
0713: * @throws XNIException Thrown by entity handler to signal an error.
0714: */
0715: public void startDocumentEntity(XMLInputSource xmlInputSource)
0716: throws IOException, XNIException {
0717: startEntity(XMLEntity, xmlInputSource, false, true);
0718: } // startDocumentEntity(XMLInputSource)
0719:
0720: //xxx these methods are not required.
0721: /**
0722: * Starts the DTD entity. The DTD entity has the "[dtd]"
0723: * pseudo-name.
0724: *
0725: * @param xmlInputSource The input source of the DTD entity.
0726: *
0727: * @throws IOException Thrown on i/o error.
0728: * @throws XNIException Thrown by entity handler to signal an error.
0729: */
0730: public void startDTDEntity(XMLInputSource xmlInputSource)
0731: throws IOException, XNIException {
0732: startEntity(DTDEntity, xmlInputSource, false, true);
0733: } // startDTDEntity(XMLInputSource)
0734:
0735: // indicate start of external subset so that
0736: // location of entity decls can be tracked
0737: public void startExternalSubset() {
0738: fInExternalSubset = true;
0739: }
0740:
0741: public void endExternalSubset() {
0742: fInExternalSubset = false;
0743: }
0744:
0745: /**
0746: * Starts an entity.
0747: * <p>
0748: * This method can be used to insert an application defined XML
0749: * entity stream into the parsing stream.
0750: *
0751: * @param name The name of the entity.
0752: * @param xmlInputSource The input source of the entity.
0753: * @param literal True if this entity is started within a
0754: * literal value.
0755: * @param isExternal whether this entity should be treated as an internal or external entity.
0756: *
0757: * @throws IOException Thrown on i/o error.
0758: * @throws XNIException Thrown by entity handler to signal an error.
0759: */
0760: public void startEntity(String name, XMLInputSource xmlInputSource,
0761: boolean literal, boolean isExternal) throws IOException,
0762: XNIException {
0763:
0764: final String publicId = xmlInputSource.getPublicId();
0765: final String literalSystemId = xmlInputSource.getSystemId();
0766: String baseSystemId = xmlInputSource.getBaseSystemId();
0767: String encoding = xmlInputSource.getEncoding();
0768: Boolean isBigEndian = null;
0769:
0770: // create reader
0771: InputStream stream = null;
0772: Reader reader = xmlInputSource.getCharacterStream();
0773: String expandedSystemId = expandSystemId(literalSystemId,
0774: baseSystemId);
0775: if (baseSystemId == null) {
0776: baseSystemId = expandedSystemId;
0777: }
0778: if (reader == null) {
0779: stream = xmlInputSource.getByteStream();
0780: if (stream == null) {
0781: //create the buffered inputstream.
0782: stream = new BufferedInputStream(new URL(
0783: expandedSystemId).openStream());
0784: }
0785: // wrap this stream in RewindableInputStream
0786: stream = new RewindableInputStream(stream);
0787:
0788: // perform auto-detect of encoding if necessary
0789: if (encoding == null) {
0790: // read first four bytes and determine encoding
0791: final byte[] b4 = new byte[4];
0792: int count = 0;
0793: for (; count < 4; count++) {
0794: b4[count] = (byte) stream.read();
0795: }
0796: if (count == 4) {
0797: Object[] encodingDesc = getEncodingName(b4, count);
0798: encoding = (String) (encodingDesc[0]);
0799: isBigEndian = (Boolean) (encodingDesc[1]);
0800: stream.reset();
0801: int offset = 0;
0802: // Special case UTF-8 files with BOM created by Microsoft
0803: // tools. It's more efficient to consume the BOM than make
0804: // the reader perform extra checks. -Ac
0805: if (count > 2 && encoding.equals("UTF-8")) {
0806: int b0 = b4[0] & 0xFF;
0807: int b1 = b4[1] & 0xFF;
0808: int b2 = b4[2] & 0xFF;
0809: //consume the byte order mark
0810: if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
0811: // ignore first three bytes...
0812: stream.skip(3);
0813: }
0814: }
0815: reader = createReader(stream, encoding, isBigEndian);
0816: } else {
0817: reader = createReader(stream, encoding, isBigEndian);
0818: }
0819: }
0820:
0821: // use specified encoding
0822: else {
0823: reader = createReader(stream, encoding, isBigEndian);
0824: }
0825:
0826: // read one character at a time so we don't jump too far
0827: // ahead, converting characters from the byte stream in
0828: // the wrong encoding
0829: if (DEBUG_ENCODINGS) {
0830: System.out
0831: .println("$$$ no longer wrapping reader in OneCharReader");
0832: }
0833: //reader = new OneCharReader(reader);
0834: }
0835:
0836: // we've seen a new Reader. put it in a list, so that
0837: // we can close it later.
0838: //fOwnReaders.addElement(reader);
0839:
0840: // push entity on stack before we open new entity
0841: if (fCurrentEntity != null) {
0842: fEntityStack.push(fCurrentEntity);
0843: }
0844:
0845: //System.out.println("Setting the fCurrentEntity, entity name = " + name );
0846: // create entity
0847: fCurrentEntity = new Entity.ScannedEntity(
0848: name,
0849: new XMLResourceIdentifierImpl(publicId,
0850: literalSystemId, baseSystemId, expandedSystemId),
0851: stream, reader, encoding, literal, false, isExternal);
0852:
0853: //xxx provide the current entity to the reader, is this right way to do ??
0854: fEntityReader.setCurrentEntity(fCurrentEntity);
0855: //System.out.println(" fCurrentEntity set = " + fCurrentEntity );
0856:
0857: fResourceIdentifier.setValues(publicId, literalSystemId,
0858: baseSystemId, expandedSystemId);
0859: // call handler
0860: if (fEntityHandler != null) {
0861: fEntityHandler.startEntity(name, fResourceIdentifier,
0862: encoding);
0863: }
0864:
0865: } // startEntity(String,XMLInputSource)
0866:
0867: /**
0868: * Return the current entity being scanned. Current entity is SET using startEntity function.
0869: * @return Entity.ScannedEntity
0870: */
0871:
0872: public Entity.ScannedEntity getCurrentEntity() {
0873: return fCurrentEntity;
0874: }
0875:
0876: // a list of Readers ever seen
0877: protected Vector fOwnReaders = new Vector();
0878:
0879: /**
0880: * Close all opened InputStreams and Readers opened by this parser.
0881: */
0882: public void closeReaders() {
0883: // close all readers
0884: for (int i = fOwnReaders.size() - 1; i >= 0; i--) {
0885: try {
0886: ((Reader) fOwnReaders.elementAt(i)).close();
0887: } catch (IOException e) {
0888: // ignore
0889: }
0890: }
0891: // and clear the list
0892: fOwnReaders.removeAllElements();
0893: }
0894:
0895: public void endEntity() throws IOException, XNIException {
0896:
0897: // call handler
0898: if (DEBUG_BUFFER) {
0899: System.out.print("(endEntity: ");
0900: print();
0901: System.out.println();
0902: }
0903: //System.out.println("Closing the Entity = " + fCurrentEntity.name);
0904:
0905: if (fEntityHandler != null) {
0906: fEntityHandler.endEntity(fCurrentEntity.name);
0907: }
0908:
0909: //close the reader
0910: if (fCurrentEntity != null) {
0911: //close the reader
0912: try {
0913: fCurrentEntity.close();
0914: } catch (IOException ex) {
0915: throw new XNIException(ex);
0916: }
0917: }
0918: // pop stack
0919: // REVISIT: we are done with the current entity, should close
0920: // the associated reader
0921: //fCurrentEntity.reader.close();
0922: // Now we close all readers after we finish parsing
0923:
0924: //fCurrentEntity = fEntityStack.size() > 0 ? (Entity.ScannedEntity)fEntityStack.pop() : null;
0925: //close the reader
0926:
0927: fCurrentEntity = fEntityStack.size() > 0 ? (Entity.ScannedEntity) fEntityStack
0928: .pop()
0929: : null;
0930:
0931: fEntityReader.setCurrentEntity(fCurrentEntity);
0932:
0933: //check if there are any entity left in the stack -- if there are
0934: //no entries EOF has been reached.
0935: //REVISIT: throwing an exception is very unclean -- and takes
0936: //lot of time too
0937: /**
0938: * if(fCurrentEntity == null ){
0939: * throw new EOFException() ;
0940: * }
0941: */
0942: if (DEBUG_BUFFER) {
0943: System.out.print(")endEntity: ");
0944: print();
0945: System.out.println();
0946: }
0947:
0948: } // endEntity()
0949:
0950: //
0951: // XMLComponent methods
0952: //
0953: public void reset(PropertyManager propertyManager) {
0954: //reset fEntityStorage
0955: fEntityStorage.reset(propertyManager);
0956: //reset XMLEntityReaderImpl
0957: fEntityReader.reset(propertyManager);
0958: // xerces properties
0959: fSymbolTable = (SymbolTable) propertyManager
0960: .getProperty(Constants.XERCES_PROPERTY_PREFIX
0961: + Constants.SYMBOL_TABLE_PROPERTY);
0962: fErrorReporter = (XMLErrorReporter) propertyManager
0963: .getProperty(Constants.XERCES_PROPERTY_PREFIX
0964: + Constants.ERROR_REPORTER_PROPERTY);
0965: try {
0966: fStaxEntityResolver = (StaxEntityResolverWrapper) propertyManager
0967: .getProperty(STAX_ENTITY_RESOLVER);
0968: } catch (XMLConfigurationException e) {
0969: fStaxEntityResolver = null;
0970: }
0971:
0972: // initialize state
0973: //fStandalone = false;
0974: fEntities.clear();
0975: fEntityStack.removeAllElements();
0976: fCurrentEntity = null;
0977: fValidation = false;
0978: fExternalGeneralEntities = true;
0979: fExternalParameterEntities = true;
0980: fAllowJavaEncodings = true;
0981:
0982: //test();
0983: }
0984:
0985: /**
0986: * public void reset(){
0987: * fEntities.clear();
0988: * fEntityStack.removeAllElements();
0989: * fCurrentEntity = null;
0990: * //test();
0991: * }
0992: */
0993: /**
0994: * Resets the component. The component can query the component manager
0995: * about any features and properties that affect the operation of the
0996: * component.
0997: *
0998: * @param componentManager The component manager.
0999: *
1000: * @throws SAXException Thrown by component on initialization error.
1001: * For example, if a feature or property is
1002: * required for the operation of the component, the
1003: * component manager may throw a
1004: * SAXNotRecognizedException or a
1005: * SAXNotSupportedException.
1006: */
1007: public void reset(XMLComponentManager componentManager)
1008: throws XMLConfigurationException {
1009:
1010: // sax features
1011: try {
1012: fValidation = componentManager.getFeature(VALIDATION);
1013: } catch (XMLConfigurationException e) {
1014: fValidation = false;
1015: }
1016: try {
1017: fExternalGeneralEntities = componentManager
1018: .getFeature(EXTERNAL_GENERAL_ENTITIES);
1019: } catch (XMLConfigurationException e) {
1020: fExternalGeneralEntities = true;
1021: }
1022: try {
1023: fExternalParameterEntities = componentManager
1024: .getFeature(EXTERNAL_PARAMETER_ENTITIES);
1025: } catch (XMLConfigurationException e) {
1026: fExternalParameterEntities = true;
1027: }
1028:
1029: // xerces features
1030: try {
1031: fAllowJavaEncodings = componentManager
1032: .getFeature(ALLOW_JAVA_ENCODINGS);
1033: } catch (XMLConfigurationException e) {
1034: fAllowJavaEncodings = false;
1035: }
1036:
1037: // xerces properties
1038: fSymbolTable = (SymbolTable) componentManager
1039: .getProperty(SYMBOL_TABLE);
1040: fErrorReporter = (XMLErrorReporter) componentManager
1041: .getProperty(ERROR_REPORTER);
1042: try {
1043: fEntityResolver = (XMLEntityResolver) componentManager
1044: .getProperty(ENTITY_RESOLVER);
1045: } catch (XMLConfigurationException e) {
1046: fEntityResolver = null;
1047: }
1048:
1049: try {
1050: fStaxEntityResolver = (StaxEntityResolverWrapper) componentManager
1051: .getProperty(STAX_ENTITY_RESOLVER);
1052: } catch (XMLConfigurationException e) {
1053: fStaxEntityResolver = null;
1054: }
1055:
1056: // initialize state
1057: fStandalone = false;
1058: fEntities.clear();
1059: fEntityStack.removeAllElements();
1060:
1061: fCurrentEntity = null;
1062: /**
1063: * // DEBUG
1064: * if (DEBUG_ENTITIES) {
1065: * addInternalEntity("text", "Hello World.");
1066: * addInternalEntity("empty-element", "<foo/>");
1067: * addInternalEntity("balanced-element", "<foo></foo>");
1068: * addInternalEntity("balanced-element-with-text", "<foo>Hello, World</foo>");
1069: * addInternalEntity("balanced-element-with-entity", "<foo>&text;</foo>");
1070: * addInternalEntity("unbalanced-entity", "<foo>");
1071: * addInternalEntity("recursive-entity", "<foo>&recursive-entity2;</foo>");
1072: * addInternalEntity("recursive-entity2", "<bar>&recursive-entity3;</bar>");
1073: * addInternalEntity("recursive-entity3", "<baz>&recursive-entity;</baz>");
1074: *
1075: * addExternalEntity("external-text", null, "external-text.ent", "test/external-text.xml");
1076: * addExternalEntity("external-balanced-element", null, "external-balanced-element.ent", "test/external-balanced-element.xml");
1077: * addExternalEntity("one", null, "ent/one.ent", "test/external-entity.xml");
1078: * addExternalEntity("two", null, "ent/two.ent", "test/ent/one.xml");
1079: * }
1080: */
1081: // copy declared entities
1082: //test();
1083: if (fDeclaredEntities != null) {
1084: java.util.Enumeration keys = fDeclaredEntities.keys();
1085: while (keys.hasMoreElements()) {
1086: Object key = keys.nextElement();
1087: Object value = fDeclaredEntities.get(key);
1088: fEntities.put(key, value);
1089: }
1090: }
1091:
1092: } // reset(XMLComponentManager)
1093:
1094: /**
1095: * Returns a list of feature identifiers that are recognized by
1096: * this component. This method may return null if no features
1097: * are recognized by this component.
1098: */
1099: public String[] getRecognizedFeatures() {
1100: return (String[]) (RECOGNIZED_FEATURES.clone());
1101: } // getRecognizedFeatures():String[]
1102:
1103: /**
1104: * Sets the state of a feature. This method is called by the component
1105: * manager any time after reset when a feature changes state.
1106: * <p>
1107: * <strong>Note:</strong> Components should silently ignore features
1108: * that do not affect the operation of the component.
1109: *
1110: * @param featureId The feature identifier.
1111: * @param state The state of the feature.
1112: *
1113: * @throws SAXNotRecognizedException The component should not throw
1114: * this exception.
1115: * @throws SAXNotSupportedException The component should not throw
1116: * this exception.
1117: */
1118: public void setFeature(String featureId, boolean state)
1119: throws XMLConfigurationException {
1120:
1121: // xerces features
1122: if (featureId.startsWith(Constants.XERCES_FEATURE_PREFIX)) {
1123: String feature = featureId
1124: .substring(Constants.XERCES_FEATURE_PREFIX.length());
1125: if (feature.equals(Constants.ALLOW_JAVA_ENCODINGS_FEATURE)) {
1126: fAllowJavaEncodings = state;
1127: }
1128: }
1129:
1130: } // setFeature(String,boolean)
1131:
1132: public void setProperty(String name, Object value) {
1133: }
1134:
1135: /**
1136: * Returns a list of property identifiers that are recognized by
1137: * this component. This method may return null if no properties
1138: * are recognized by this component.
1139: */
1140: public String[] getRecognizedProperties() {
1141: return (String[]) (RECOGNIZED_PROPERTIES.clone());
1142: } // getRecognizedProperties():String[]
1143:
1144: /**
1145: * Returns the default state for a feature, or null if this
1146: * component does not want to report a default value for this
1147: * feature.
1148: *
1149: * @param featureId The feature identifier.
1150: *
1151: * @since Xerces 2.2.0
1152: */
1153: public Boolean getFeatureDefault(String featureId) {
1154: for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
1155: if (RECOGNIZED_FEATURES[i].equals(featureId)) {
1156: return FEATURE_DEFAULTS[i];
1157: }
1158: }
1159: return null;
1160: } // getFeatureDefault(String):Boolean
1161:
1162: /**
1163: * Returns the default state for a property, or null if this
1164: * component does not want to report a default value for this
1165: * property.
1166: *
1167: * @param propertyId The property identifier.
1168: *
1169: * @since Xerces 2.2.0
1170: */
1171: public Object getPropertyDefault(String propertyId) {
1172: for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
1173: if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
1174: return PROPERTY_DEFAULTS[i];
1175: }
1176: }
1177: return null;
1178: } // getPropertyDefault(String):Object
1179:
1180: //
1181: // Public static methods
1182: //
1183:
1184: /**
1185: * Expands a system id and returns the system id as a URI, if
1186: * it can be expanded. A return value of null means that the
1187: * identifier is already expanded. An exception thrown
1188: * indicates a failure to expand the id.
1189: *
1190: * @param systemId The systemId to be expanded.
1191: *
1192: * @return Returns the URI string representing the expanded system
1193: * identifier. A null value indicates that the given
1194: * system identifier is already expanded.
1195: *
1196: */
1197: public static String expandSystemId(String systemId) {
1198: return expandSystemId(systemId, null);
1199: } // expandSystemId(String):String
1200:
1201: // current value of the "user.dir" property
1202: private static String gUserDir;
1203: // escaped value of the current "user.dir" property
1204: private static String gEscapedUserDir;
1205: // which ASCII characters need to be escaped
1206: private static boolean gNeedEscaping[] = new boolean[128];
1207: // the first hex character if a character needs to be escaped
1208: private static char gAfterEscaping1[] = new char[128];
1209: // the second hex character if a character needs to be escaped
1210: private static char gAfterEscaping2[] = new char[128];
1211: private static char[] gHexChs = { '0', '1', '2', '3', '4', '5',
1212: '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
1213: // initialize the above 3 arrays
1214: static {
1215: for (int i = 0; i <= 0x1f; i++) {
1216: gNeedEscaping[i] = true;
1217: gAfterEscaping1[i] = gHexChs[i >> 4];
1218: gAfterEscaping2[i] = gHexChs[i & 0xf];
1219: }
1220: gNeedEscaping[0x7f] = true;
1221: gAfterEscaping1[0x7f] = '7';
1222: gAfterEscaping2[0x7f] = 'F';
1223: char[] escChs = { ' ', '<', '>', '#', '%', '"', '{', '}', '|',
1224: '\\', '^', '~', '[', ']', '`' };
1225: int len = escChs.length;
1226: char ch;
1227: for (int i = 0; i < len; i++) {
1228: ch = escChs[i];
1229: gNeedEscaping[ch] = true;
1230: gAfterEscaping1[ch] = gHexChs[ch >> 4];
1231: gAfterEscaping2[ch] = gHexChs[ch & 0xf];
1232: }
1233: }
1234:
1235: // To escape the "user.dir" system property, by using %HH to represent
1236: // special ASCII characters: 0x00~0x1F, 0x7F, ' ', '<', '>', '#', '%'
1237: // and '"'. It's a static method, so needs to be synchronized.
1238: // this method looks heavy, but since the system property isn't expected
1239: // to change often, so in most cases, we only need to return the string
1240: // that was escaped before.
1241: // According to the URI spec, non-ASCII characters (whose value >= 128)
1242: // need to be escaped too.
1243: // REVISIT: don't know how to escape non-ASCII characters, especially
1244: // which encoding to use. Leave them for now.
1245: private static synchronized String getUserDir() {
1246: // get the user.dir property
1247: String userDir = "";
1248: try {
1249: userDir = System.getProperty("user.dir");
1250: } catch (SecurityException se) {
1251: }
1252:
1253: // return empty string if property value is empty string.
1254: if (userDir.length() == 0)
1255: return "";
1256:
1257: // compute the new escaped value if the new property value doesn't
1258: // match the previous one
1259: if (userDir.equals(gUserDir)) {
1260: return gEscapedUserDir;
1261: }
1262:
1263: // record the new value as the global property value
1264: gUserDir = userDir;
1265:
1266: char separator = java.io.File.separatorChar;
1267: userDir = userDir.replace(separator, '/');
1268:
1269: int len = userDir.length(), ch;
1270: StringBuffer buffer = new StringBuffer(len * 3);
1271: // change C:/blah to /C:/blah
1272: if (len >= 2 && userDir.charAt(1) == ':') {
1273: ch = Character.toUpperCase(userDir.charAt(0));
1274: if (ch >= 'A' && ch <= 'Z') {
1275: buffer.append('/');
1276: }
1277: }
1278:
1279: // for each character in the path
1280: int i = 0;
1281: for (; i < len; i++) {
1282: ch = userDir.charAt(i);
1283: // if it's not an ASCII character, break here, and use UTF-8 encoding
1284: if (ch >= 128)
1285: break;
1286: if (gNeedEscaping[ch]) {
1287: buffer.append('%');
1288: buffer.append(gAfterEscaping1[ch]);
1289: buffer.append(gAfterEscaping2[ch]);
1290: // record the fact that it's escaped
1291: } else {
1292: buffer.append((char) ch);
1293: }
1294: }
1295:
1296: // we saw some non-ascii character
1297: if (i < len) {
1298: // get UTF-8 bytes for the remaining sub-string
1299: byte[] bytes = null;
1300: byte b;
1301: try {
1302: bytes = userDir.substring(i).getBytes("UTF-8");
1303: } catch (java.io.UnsupportedEncodingException e) {
1304: // should never happen
1305: return userDir;
1306: }
1307: len = bytes.length;
1308:
1309: // for each byte
1310: for (i = 0; i < len; i++) {
1311: b = bytes[i];
1312: // for non-ascii character: make it positive, then escape
1313: if (b < 0) {
1314: ch = b + 256;
1315: buffer.append('%');
1316: buffer.append(gHexChs[ch >> 4]);
1317: buffer.append(gHexChs[ch & 0xf]);
1318: } else if (gNeedEscaping[b]) {
1319: buffer.append('%');
1320: buffer.append(gAfterEscaping1[b]);
1321: buffer.append(gAfterEscaping2[b]);
1322: } else {
1323: buffer.append((char) b);
1324: }
1325: }
1326: }
1327:
1328: // change blah/blah to blah/blah/
1329: if (!userDir.endsWith("/"))
1330: buffer.append('/');
1331:
1332: gEscapedUserDir = buffer.toString();
1333:
1334: return gEscapedUserDir;
1335: }
1336:
1337: /**
1338: * Expands a system id and returns the system id as a URI, if
1339: * it can be expanded. A return value of null means that the
1340: * identifier is already expanded. An exception thrown
1341: * indicates a failure to expand the id.
1342: *
1343: * @param systemId The systemId to be expanded.
1344: *
1345: * @return Returns the URI string representing the expanded system
1346: * identifier. A null value indicates that the given
1347: * system identifier is already expanded.
1348: *
1349: */
1350: public static String expandSystemId(String systemId,
1351: String baseSystemId) {
1352:
1353: // check for bad parameters id
1354: if (systemId == null || systemId.length() == 0) {
1355: return systemId;
1356: }
1357: // if id already expanded, return
1358: try {
1359: URI uri = new URI(systemId);
1360: if (uri != null) {
1361: return systemId;
1362: }
1363: } catch (URI.MalformedURIException e) {
1364: // continue on...
1365: }
1366: // normalize id
1367: String id = fixURI(systemId);
1368:
1369: // normalize base
1370: URI base = null;
1371: URI uri = null;
1372: try {
1373: if (baseSystemId == null || baseSystemId.length() == 0
1374: || baseSystemId.equals(systemId)) {
1375: String dir = getUserDir();
1376: base = new URI("file", "", dir, null, null);
1377: } else {
1378: try {
1379: base = new URI(fixURI(baseSystemId));
1380: } catch (URI.MalformedURIException e) {
1381: if (baseSystemId.indexOf(':') != -1) {
1382: // for xml schemas we might have baseURI with
1383: // a specified drive
1384: base = new URI("file", "",
1385: fixURI(baseSystemId), null, null);
1386: } else {
1387: String dir = getUserDir();
1388: dir = dir + fixURI(baseSystemId);
1389: base = new URI("file", "", dir, null, null);
1390: }
1391: }
1392: }
1393: // expand id
1394: uri = new URI(base, id);
1395: } catch (Exception e) {
1396: // let it go through
1397:
1398: }
1399:
1400: if (uri == null) {
1401: return systemId;
1402: }
1403: return uri.toString();
1404:
1405: } // expandSystemId(String,String):String
1406:
1407: //
1408: // Protected methods
1409: //
1410:
1411: /**
1412: * Returns the IANA encoding name that is auto-detected from
1413: * the bytes specified, with the endian-ness of that encoding where appropriate.
1414: *
1415: * @param b4 The first four bytes of the input.
1416: * @param count The number of bytes actually read.
1417: * @return a 2-element array: the first element, an IANA-encoding string,
1418: * the second element a Boolean which is true iff the document is big endian, false
1419: * if it's little-endian, and null if the distinction isn't relevant.
1420: */
1421: protected Object[] getEncodingName(byte[] b4, int count) {
1422:
1423: if (count < 2) {
1424: return defaultEncoding;
1425: }
1426:
1427: // UTF-16, with BOM
1428: int b0 = b4[0] & 0xFF;
1429: int b1 = b4[1] & 0xFF;
1430: if (b0 == 0xFE && b1 == 0xFF) {
1431: // UTF-16, big-endian
1432: return new Object[] { "UTF-16BE", new Boolean(true) };
1433: }
1434: if (b0 == 0xFF && b1 == 0xFE) {
1435: // UTF-16, little-endian
1436: return new Object[] { "UTF-16LE", new Boolean(false) };
1437: }
1438:
1439: // default to UTF-8 if we don't have enough bytes to make a
1440: // good determination of the encoding
1441: if (count < 3) {
1442: return defaultEncoding;
1443: }
1444:
1445: // UTF-8 with a BOM
1446: int b2 = b4[2] & 0xFF;
1447: if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
1448: return defaultEncoding;
1449: }
1450:
1451: // default to UTF-8 if we don't have enough bytes to make a
1452: // good determination of the encoding
1453: if (count < 4) {
1454: return defaultEncoding;
1455: }
1456:
1457: // other encodings
1458: int b3 = b4[3] & 0xFF;
1459: if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) {
1460: // UCS-4, big endian (1234)
1461: return new Object[] { "ISO-10646-UCS-4", new Boolean(true) };
1462: }
1463: if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) {
1464: // UCS-4, little endian (4321)
1465: return new Object[] { "ISO-10646-UCS-4", new Boolean(false) };
1466: }
1467: if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) {
1468: // UCS-4, unusual octet order (2143)
1469: // REVISIT: What should this be?
1470: return new Object[] { "ISO-10646-UCS-4", null };
1471: }
1472: if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) {
1473: // UCS-4, unusual octect order (3412)
1474: // REVISIT: What should this be?
1475: return new Object[] { "ISO-10646-UCS-4", null };
1476: }
1477: if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
1478: // UTF-16, big-endian, no BOM
1479: // (or could turn out to be UCS-2...
1480: // REVISIT: What should this be?
1481: return new Object[] { "UTF-16BE", new Boolean(true) };
1482: }
1483: if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
1484: // UTF-16, little-endian, no BOM
1485: // (or could turn out to be UCS-2...
1486: return new Object[] { "UTF-16LE", new Boolean(false) };
1487: }
1488: if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) {
1489: // EBCDIC
1490: // a la xerces1, return CP037 instead of EBCDIC here
1491: return new Object[] { "CP037", null };
1492: }
1493:
1494: return defaultEncoding;
1495:
1496: } // getEncodingName(byte[],int):Object[]
1497:
1498: /**
1499: * Creates a reader capable of reading the given input stream in
1500: * the specified encoding.
1501: *
1502: * @param inputStream The input stream.
1503: * @param encoding The encoding name that the input stream is
1504: * encoded using. If the user has specified that
1505: * Java encoding names are allowed, then the
1506: * encoding name may be a Java encoding name;
1507: * otherwise, it is an ianaEncoding name.
1508: * @param isBigEndian For encodings (like uCS-4), whose names cannot
1509: * specify a byte order, this tells whether the order is bigEndian. null menas
1510: * unknown or not relevant.
1511: *
1512: * @return Returns a reader.
1513: */
1514: protected Reader createReader(InputStream inputStream,
1515: String encoding, Boolean isBigEndian) throws IOException {
1516:
1517: // normalize encoding name
1518: if (encoding == null) {
1519: encoding = "UTF-8";
1520: }
1521:
1522: // try to use an optimized reader
1523: String ENCODING = encoding.toUpperCase(Locale.ENGLISH);
1524: if (ENCODING.equals("UTF-8")) {
1525: if (DEBUG_ENCODINGS) {
1526: System.out.println("$$$ creating UTF8Reader");
1527: }
1528: return new UTF8Reader(
1529: inputStream,
1530: fBufferSize,
1531: fErrorReporter
1532: .getMessageFormatter(XMLMessageFormatter.XML_DOMAIN),
1533: fErrorReporter.getLocale());
1534: }
1535: if (ENCODING.equals("US-ASCII")) {
1536: if (DEBUG_ENCODINGS) {
1537: System.out.println("$$$ creating ASCIIReader");
1538: }
1539: return new ASCIIReader(
1540: inputStream,
1541: fBufferSize,
1542: fErrorReporter
1543: .getMessageFormatter(XMLMessageFormatter.XML_DOMAIN),
1544: fErrorReporter.getLocale());
1545: }
1546: if (ENCODING.equals("ISO-10646-UCS-4")) {
1547: if (isBigEndian != null) {
1548: boolean isBE = isBigEndian.booleanValue();
1549: if (isBE) {
1550: return new UCSReader(inputStream, UCSReader.UCS4BE);
1551: } else {
1552: return new UCSReader(inputStream, UCSReader.UCS4LE);
1553: }
1554: } else {
1555: fErrorReporter.reportError(this .getEntityReader(),
1556: XMLMessageFormatter.XML_DOMAIN,
1557: "EncodingByteOrderUnsupported",
1558: new Object[] { encoding },
1559: XMLErrorReporter.SEVERITY_FATAL_ERROR);
1560: }
1561: }
1562: if (ENCODING.equals("ISO-10646-UCS-2")) {
1563: if (isBigEndian != null) { // sould never happen with this encoding...
1564: boolean isBE = isBigEndian.booleanValue();
1565: if (isBE) {
1566: return new UCSReader(inputStream, UCSReader.UCS2BE);
1567: } else {
1568: return new UCSReader(inputStream, UCSReader.UCS2LE);
1569: }
1570: } else {
1571: fErrorReporter.reportError(this .getEntityReader(),
1572: XMLMessageFormatter.XML_DOMAIN,
1573: "EncodingByteOrderUnsupported",
1574: new Object[] { encoding },
1575: XMLErrorReporter.SEVERITY_FATAL_ERROR);
1576: }
1577: }
1578:
1579: // check for valid name
1580: boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
1581: boolean validJava = XMLChar.isValidJavaEncoding(encoding);
1582: if (!validIANA || (fAllowJavaEncodings && !validJava)) {
1583: fErrorReporter.reportError(this .getEntityReader(),
1584: XMLMessageFormatter.XML_DOMAIN,
1585: "EncodingDeclInvalid", new Object[] { encoding },
1586: XMLErrorReporter.SEVERITY_FATAL_ERROR);
1587: // NOTE: AndyH suggested that, on failure, we use ISO Latin 1
1588: // because every byte is a valid ISO Latin 1 character.
1589: // It may not translate correctly but if we failed on
1590: // the encoding anyway, then we're expecting the content
1591: // of the document to be bad. This will just prevent an
1592: // invalid UTF-8 sequence to be detected. This is only
1593: // important when continue-after-fatal-error is turned
1594: // on. -Ac
1595: encoding = "ISO-8859-1";
1596: }
1597:
1598: // try to use a Java reader
1599: String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING);
1600: if (javaEncoding == null) {
1601: if (fAllowJavaEncodings) {
1602: javaEncoding = encoding;
1603: } else {
1604: fErrorReporter.reportError(this .getEntityReader(),
1605: XMLMessageFormatter.XML_DOMAIN,
1606: "EncodingDeclInvalid",
1607: new Object[] { encoding },
1608: XMLErrorReporter.SEVERITY_FATAL_ERROR);
1609: // see comment above.
1610: javaEncoding = "ISO8859_1";
1611: }
1612: }
1613: if (DEBUG_ENCODINGS) {
1614: System.out
1615: .print("$$$ creating Java InputStreamReader: encoding="
1616: + javaEncoding);
1617: if (javaEncoding == encoding) {
1618: System.out.print(" (IANA encoding)");
1619: }
1620: System.out.println();
1621: }
1622: return new BufferedReader(new InputStreamReader(inputStream,
1623: javaEncoding));
1624:
1625: } // createReader(InputStream,String, Boolean): Reader
1626:
1627: /**
1628: * Return the public identifier for the current document event.
1629: * <p>
1630: * The return value is the public identifier of the document
1631: * entity or of the external parsed entity in which the markup
1632: * triggering the event appears.
1633: *
1634: * @return A string containing the public identifier, or
1635: * null if none is available.
1636: */
1637: public String getPublicId() {
1638: return (fCurrentEntity != null && fCurrentEntity.entityLocation != null) ? fCurrentEntity.entityLocation
1639: .getPublicId()
1640: : null;
1641: } // getPublicId():String
1642:
1643: /**
1644: * Return the expanded system identifier for the current document event.
1645: * <p>
1646: * The return value is the expanded system identifier of the document
1647: * entity or of the external parsed entity in which the markup
1648: * triggering the event appears.
1649: * <p>
1650: * If the system identifier is a URL, the parser must resolve it
1651: * fully before passing it to the application.
1652: *
1653: * @return A string containing the expanded system identifier, or null
1654: * if none is available.
1655: */
1656: public String getExpandedSystemId() {
1657: if (fCurrentEntity != null) {
1658: if (fCurrentEntity.entityLocation != null
1659: && fCurrentEntity.entityLocation
1660: .getExpandedSystemId() != null) {
1661: return fCurrentEntity.entityLocation
1662: .getExpandedSystemId();
1663: } else {
1664: // search for the first external entity on the stack
1665: int size = fEntityStack.size();
1666: for (int i = size - 1; i >= 0; i--) {
1667: Entity.ScannedEntity externalEntity = (Entity.ScannedEntity) fEntityStack
1668: .elementAt(i);
1669:
1670: if (externalEntity.entityLocation != null
1671: && externalEntity.entityLocation
1672: .getExpandedSystemId() != null) {
1673: return externalEntity.entityLocation
1674: .getExpandedSystemId();
1675: }
1676: }
1677: }
1678: }
1679: return null;
1680: } // getExpandedSystemId():String
1681:
1682: /**
1683: * Return the literal system identifier for the current document event.
1684: * <p>
1685: * The return value is the literal system identifier of the document
1686: * entity or of the external parsed entity in which the markup
1687: * triggering the event appears.
1688: * <p>
1689: * @return A string containing the literal system identifier, or null
1690: * if none is available.
1691: */
1692: public String getLiteralSystemId() {
1693: if (fCurrentEntity != null) {
1694: if (fCurrentEntity.entityLocation != null
1695: && fCurrentEntity.entityLocation
1696: .getLiteralSystemId() != null) {
1697: return fCurrentEntity.entityLocation
1698: .getLiteralSystemId();
1699: } else {
1700: // search for the first external entity on the stack
1701: int size = fEntityStack.size();
1702: for (int i = size - 1; i >= 0; i--) {
1703: Entity.ScannedEntity externalEntity = (Entity.ScannedEntity) fEntityStack
1704: .elementAt(i);
1705:
1706: if (externalEntity.entityLocation != null
1707: && externalEntity.entityLocation
1708: .getLiteralSystemId() != null) {
1709: return externalEntity.entityLocation
1710: .getLiteralSystemId();
1711: }
1712: }
1713: }
1714: }
1715: return null;
1716: } // getLiteralSystemId():String
1717:
1718: /**
1719: * Return the line number where the current document event ends.
1720: * <p>
1721: * <strong>Warning:</strong> The return value from the method
1722: * is intended only as an approximation for the sake of error
1723: * reporting; it is not intended to provide sufficient information
1724: * to edit the character content of the original XML document.
1725: * <p>
1726: * The return value is an approximation of the line number
1727: * in the document entity or external parsed entity where the
1728: * markup triggering the event appears.
1729: * <p>
1730: * If possible, the SAX driver should provide the line position
1731: * of the first character after the text associated with the document
1732: * event. The first line in the document is line 1.
1733: *
1734: * @return The line number, or -1 if none is available.
1735: */
1736: public int getLineNumber() {
1737: if (fCurrentEntity != null) {
1738: if (fCurrentEntity.isExternal()) {
1739: return fCurrentEntity.lineNumber;
1740: } else {
1741: // search for the first external entity on the stack
1742: int size = fEntityStack.size();
1743: for (int i = size - 1; i > 0; i--) {
1744: Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity) fEntityStack
1745: .elementAt(i);
1746: if (firstExternalEntity.isExternal()) {
1747: return firstExternalEntity.lineNumber;
1748: }
1749: }
1750: }
1751: }
1752:
1753: return -1;
1754:
1755: } // getLineNumber():int
1756:
1757: /**
1758: * Return the column number where the current document event ends.
1759: * <p>
1760: * <strong>Warning:</strong> The return value from the method
1761: * is intended only as an approximation for the sake of error
1762: * reporting; it is not intended to provide sufficient information
1763: * to edit the character content of the original XML document.
1764: * <p>
1765: * The return value is an approximation of the column number
1766: * in the document entity or external parsed entity where the
1767: * markup triggering the event appears.
1768: * <p>
1769: * If possible, the SAX driver should provide the line position
1770: * of the first character after the text associated with the document
1771: * event.
1772: * <p>
1773: * If possible, the SAX driver should provide the line position
1774: * of the first character after the text associated with the document
1775: * event. The first column in each line is column 1.
1776: *
1777: * @return The column number, or -1 if none is available.
1778: */
1779: public int getColumnNumber() {
1780: if (fCurrentEntity != null) {
1781: if (fCurrentEntity.isExternal()) {
1782: return fCurrentEntity.columnNumber;
1783: } else {
1784: // search for the first external entity on the stack
1785: int size = fEntityStack.size();
1786: for (int i = size - 1; i > 0; i--) {
1787: Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity) fEntityStack
1788: .elementAt(i);
1789: if (firstExternalEntity.isExternal()) {
1790: return firstExternalEntity.columnNumber;
1791: }
1792: }
1793: }
1794: }
1795:
1796: return -1;
1797: } // getColumnNumber():int
1798:
1799: //
1800: // Protected static methods
1801: //
1802:
1803: /**
1804: * Fixes a platform dependent filename to standard URI form.
1805: *
1806: * @param str The string to fix.
1807: *
1808: * @return Returns the fixed URI string.
1809: */
1810: protected static String fixURI(String str) {
1811:
1812: // handle platform dependent strings
1813: str = str.replace(java.io.File.separatorChar, '/');
1814:
1815: // Windows fix
1816: if (str.length() >= 2) {
1817: char ch1 = str.charAt(1);
1818: // change "C:blah" to "/C:blah"
1819: if (ch1 == ':') {
1820: char ch0 = Character.toUpperCase(str.charAt(0));
1821: if (ch0 >= 'A' && ch0 <= 'Z') {
1822: str = "/" + str;
1823: }
1824: }
1825: // change "//blah" to "file://blah"
1826: else if (ch1 == '/' && str.charAt(0) == '/') {
1827: str = "file:" + str;
1828: }
1829: }
1830:
1831: // done
1832: return str;
1833:
1834: } // fixURI(String):String
1835:
1836: //
1837: // Package visible methods
1838: //
1839: /** Prints the contents of the buffer. */
1840: final void print() {
1841: if (DEBUG_BUFFER) {
1842: if (fCurrentEntity != null) {
1843: System.out.print('[');
1844: System.out.print(fCurrentEntity.count);
1845: System.out.print(' ');
1846: System.out.print(fCurrentEntity.position);
1847: if (fCurrentEntity.count > 0) {
1848: System.out.print(" \"");
1849: for (int i = 0; i < fCurrentEntity.count; i++) {
1850: if (i == fCurrentEntity.position) {
1851: System.out.print('^');
1852: }
1853: char c = fCurrentEntity.ch[i];
1854: switch (c) {
1855: case '\n': {
1856: System.out.print("\\n");
1857: break;
1858: }
1859: case '\r': {
1860: System.out.print("\\r");
1861: break;
1862: }
1863: case '\t': {
1864: System.out.print("\\t");
1865: break;
1866: }
1867: case '\\': {
1868: System.out.print("\\\\");
1869: break;
1870: }
1871: default: {
1872: System.out.print(c);
1873: }
1874: }
1875: }
1876: if (fCurrentEntity.position == fCurrentEntity.count) {
1877: System.out.print('^');
1878: }
1879: System.out.print('"');
1880: }
1881: System.out.print(']');
1882: System.out.print(" @ ");
1883: System.out.print(fCurrentEntity.lineNumber);
1884: System.out.print(',');
1885: System.out.print(fCurrentEntity.columnNumber);
1886: } else {
1887: System.out.print("*NO CURRENT ENTITY*");
1888: }
1889: }
1890: } // print()
1891:
1892: // This class wraps the byte inputstreams we're presented with.
1893: // We need it because java.io.InputStreams don't provide
1894: // functionality to reread processed bytes, and they have a habit
1895: // of reading more than one character when you call their read()
1896: // methods. This means that, once we discover the true (declared)
1897: // encoding of a document, we can neither backtrack to read the
1898: // whole doc again nor start reading where we are with a new
1899: // reader.
1900: //
1901: // This class allows rewinding an inputStream by allowing a mark
1902: // to be set, and the stream reset to that position. <strong>The
1903: // class assumes that it needs to read one character per
1904: // invocation when it's read() method is inovked, but uses the
1905: // underlying InputStream's read(char[], offset length) method--it
1906: // won't buffer data read this way!</strong>
1907: //
1908: // @author Neil Graham, IBM
1909: // @author Glenn Marcy, IBM
1910:
1911: protected final class RewindableInputStream extends InputStream {
1912:
1913: private InputStream fInputStream;
1914: private byte[] fData;
1915: private int fStartOffset;
1916: private int fEndOffset;
1917: private int fOffset;
1918: private int fLength;
1919: private int fMark;
1920:
1921: public RewindableInputStream(InputStream is) {
1922: fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE];
1923: fInputStream = is;
1924: fStartOffset = 0;
1925: fEndOffset = -1;
1926: fOffset = 0;
1927: fLength = 0;
1928: fMark = 0;
1929: }
1930:
1931: public void setStartOffset(int offset) {
1932: fStartOffset = offset;
1933: }
1934:
1935: public void rewind() {
1936: fOffset = fStartOffset;
1937: }
1938:
1939: public int read() throws IOException {
1940: int b = 0;
1941: if (fOffset < fLength) {
1942: return fData[fOffset++] & 0xff;
1943: }
1944: if (fOffset == fEndOffset) {
1945: return -1;
1946: }
1947: if (fOffset == fData.length) {
1948: byte[] newData = new byte[fOffset << 1];
1949: System.arraycopy(fData, 0, newData, 0, fOffset);
1950: fData = newData;
1951: }
1952: b = fInputStream.read();
1953: if (b == -1) {
1954: fEndOffset = fOffset;
1955: return -1;
1956: }
1957: fData[fLength++] = (byte) b;
1958: fOffset++;
1959: return b & 0xff;
1960: }
1961:
1962: public int read(byte[] b, int off, int len) throws IOException {
1963: int bytesLeft = fLength - fOffset;
1964: if (bytesLeft == 0) {
1965: if (fOffset == fEndOffset) {
1966: return -1;
1967: }
1968: return fInputStream.read(b, off, len);
1969: /**
1970: * //System.out.println("fCurrentEntitty = " + fCurrentEntity );
1971: * //System.out.println("fInputStream = " + fInputStream );
1972: * // better get some more for the voracious reader...
1973: * if(fCurrentEntity.mayReadChunks) {
1974: * return fInputStream.read(b, off, len);
1975: * }
1976: *
1977: * int returnedVal = read();
1978: * if(returnedVal == -1) {
1979: * fEndOffset = fOffset;
1980: * return -1;
1981: * }
1982: * b[off] = (byte)returnedVal;
1983: * return 1;
1984: */
1985: }
1986: if (len < bytesLeft) {
1987: if (len <= 0) {
1988: return 0;
1989: }
1990: } else {
1991: len = bytesLeft;
1992: }
1993: if (b != null) {
1994: System.arraycopy(fData, fOffset, b, off, len);
1995: }
1996: fOffset += len;
1997: return len;
1998: }
1999:
2000: public long skip(long n) throws IOException {
2001: int bytesLeft;
2002: if (n <= 0) {
2003: return 0;
2004: }
2005: bytesLeft = fLength - fOffset;
2006: if (bytesLeft == 0) {
2007: if (fOffset == fEndOffset) {
2008: return 0;
2009: }
2010: return fInputStream.skip(n);
2011: }
2012: if (n <= bytesLeft) {
2013: fOffset += n;
2014: return n;
2015: }
2016: fOffset += bytesLeft;
2017: if (fOffset == fEndOffset) {
2018: return bytesLeft;
2019: }
2020: n -= bytesLeft;
2021: /*
2022: * In a manner of speaking, when this class isn't permitting more
2023: * than one byte at a time to be read, it is "blocking". The
2024: * available() method should indicate how much can be read without
2025: * blocking, so while we're in this mode, it should only indicate
2026: * that bytes in its buffer are available; otherwise, the result of
2027: * available() on the underlying InputStream is appropriate.
2028: */
2029: return fInputStream.skip(n) + bytesLeft;
2030: }
2031:
2032: public int available() throws IOException {
2033: int bytesLeft = fLength - fOffset;
2034: if (bytesLeft == 0) {
2035: if (fOffset == fEndOffset) {
2036: return -1;
2037: }
2038: return fCurrentEntity.mayReadChunks ? fInputStream
2039: .available() : 0;
2040: }
2041: return bytesLeft;
2042: }
2043:
2044: public void mark(int howMuch) {
2045: fMark = fOffset;
2046: }
2047:
2048: public void reset() {
2049: fOffset = fMark;
2050: //test();
2051: }
2052:
2053: public boolean markSupported() {
2054: return true;
2055: }
2056:
2057: public void close() throws IOException {
2058: if (fInputStream != null) {
2059: fInputStream.close();
2060: fInputStream = null;
2061: }
2062: }
2063: } // end of RewindableInputStream class
2064:
2065: public void test() {
2066: //System.out.println("TESTING: Added familytree to entityManager");
2067: //Usecase1
2068: fEntityStorage
2069: .addExternalEntity(
2070: "entityUsecase1",
2071: null,
2072: "/space/home/stax/sun/6thJan2004/zephyr/data/test.txt",
2073: "/space/home/stax/sun/6thJan2004/zephyr/data/entity.xml");
2074:
2075: //Usecase2
2076: fEntityStorage.addInternalEntity("entityUsecase2",
2077: "<Test>value</Test>");
2078: fEntityStorage.addInternalEntity("entityUsecase3", "value3");
2079: fEntityStorage.addInternalEntity("text", "Hello World.");
2080: fEntityStorage.addInternalEntity("empty-element", "<foo/>");
2081: fEntityStorage.addInternalEntity("balanced-element",
2082: "<foo></foo>");
2083: fEntityStorage.addInternalEntity("balanced-element-with-text",
2084: "<foo>Hello, World</foo>");
2085: fEntityStorage.addInternalEntity(
2086: "balanced-element-with-entity", "<foo>&text;</foo>");
2087: fEntityStorage.addInternalEntity("unbalanced-entity", "<foo>");
2088: fEntityStorage.addInternalEntity("recursive-entity",
2089: "<foo>&recursive-entity2;</foo>");
2090: fEntityStorage.addInternalEntity("recursive-entity2",
2091: "<bar>&recursive-entity3;</bar>");
2092: fEntityStorage.addInternalEntity("recursive-entity3",
2093: "<baz>&recursive-entity;</baz>");
2094: fEntityStorage.addInternalEntity("ch", "©");
2095: fEntityStorage.addInternalEntity("ch1", "T");
2096: fEntityStorage.addInternalEntity("% ch2", "param");
2097: }
2098:
2099: } // class XMLEntityManager
|