XMLEntityManager.java revision 1052:3654ce193315
1212420Sken/*
2212420Sken * Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
3212420Sken */
4212420Sken/*
5212420Sken * Licensed to the Apache Software Foundation (ASF) under one or more
6212420Sken * contributor license agreements.  See the NOTICE file distributed with
7212420Sken * this work for additional information regarding copyright ownership.
8212420Sken * The ASF licenses this file to You under the Apache License, Version 2.0
9212420Sken * (the "License"); you may not use this file except in compliance with
10212420Sken * the License.  You may obtain a copy of the License at
11212420Sken *
12212420Sken *      http://www.apache.org/licenses/LICENSE-2.0
13212420Sken *
14212420Sken * Unless required by applicable law or agreed to in writing, software
15212420Sken * distributed under the License is distributed on an "AS IS" BASIS,
16212420Sken * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17212420Sken * See the License for the specific language governing permissions and
18212420Sken * limitations under the License.
19212420Sken */
20212420Sken
21212420Skenpackage com.sun.org.apache.xerces.internal.impl ;
22212420Sken
23212420Skenimport com.sun.org.apache.xerces.internal.impl.io.ASCIIReader;
24212420Skenimport com.sun.org.apache.xerces.internal.impl.io.UCSReader;
25212420Skenimport com.sun.org.apache.xerces.internal.impl.io.UTF8Reader;
26212420Skenimport com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter;
27212420Skenimport com.sun.org.apache.xerces.internal.impl.validation.ValidationManager;
28212420Skenimport com.sun.org.apache.xerces.internal.util.*;
29212420Skenimport com.sun.org.apache.xerces.internal.util.URI;
30230592Skenimport com.sun.org.apache.xerces.internal.utils.SecuritySupport;
31212420Skenimport com.sun.org.apache.xerces.internal.utils.XMLLimitAnalyzer;
32230592Skenimport com.sun.org.apache.xerces.internal.utils.XMLSecurityManager;
33230592Skenimport com.sun.org.apache.xerces.internal.utils.XMLSecurityPropertyManager;
34230592Skenimport com.sun.org.apache.xerces.internal.xni.Augmentations;
35230592Skenimport com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
36230592Skenimport com.sun.org.apache.xerces.internal.xni.XNIException;
37230592Skenimport com.sun.org.apache.xerces.internal.xni.parser.*;
38230592Skenimport com.sun.xml.internal.stream.Entity;
39230592Skenimport com.sun.xml.internal.stream.StaxEntityResolverWrapper;
40230592Skenimport com.sun.xml.internal.stream.StaxXMLInputSource;
41230592Skenimport com.sun.xml.internal.stream.XMLEntityStorage;
42230592Skenimport java.io.*;
43230592Skenimport java.net.HttpURLConnection;
44230592Skenimport java.net.URISyntaxException;
45230592Skenimport java.net.URL;
46230592Skenimport java.net.URLConnection;
47230592Skenimport java.util.HashMap;
48230592Skenimport java.util.Iterator;
49230592Skenimport java.util.Locale;
50230592Skenimport java.util.Map;
51230592Skenimport java.util.Stack;
52230592Skenimport java.util.StringTokenizer;
53230592Skenimport javax.xml.XMLConstants;
54230592Skenimport javax.xml.catalog.CatalogException;
55230592Skenimport javax.xml.catalog.CatalogFeatures;
56230592Skenimport javax.xml.catalog.CatalogFeatures.Feature;
57230592Skenimport javax.xml.catalog.CatalogManager;
58230592Skenimport javax.xml.catalog.CatalogResolver;
59230592Skenimport javax.xml.stream.XMLInputFactory;
60230592Skenimport javax.xml.transform.Source;
61212420Skenimport jdk.xml.internal.JdkXmlUtils;
62212420Skenimport org.xml.sax.InputSource;
63212420Sken
64212420Sken
65213702Smdf/**
66213702Smdf * Will keep track of current entity.
67230592Sken *
68212420Sken * The entity manager handles the registration of general and parameter
69212420Sken * entities; resolves entities; and starts entities. The entity manager
70212420Sken * is a central component in a standard parser configuration and this
71212420Sken * class works directly with the entity scanner to manage the underlying
72212420Sken * xni.
73212420Sken * <p>
74212420Sken * This component requires the following features and properties from the
75212420Sken * component manager that uses it:
76212420Sken * <ul>
77212420Sken *  <li>http://xml.org/sax/features/validation</li>
78212420Sken *  <li>http://xml.org/sax/features/external-general-entities</li>
79212420Sken *  <li>http://xml.org/sax/features/external-parameter-entities</li>
80212420Sken *  <li>http://apache.org/xml/features/allow-java-encodings</li>
81212420Sken *  <li>http://apache.org/xml/properties/internal/symbol-table</li>
82230592Sken *  <li>http://apache.org/xml/properties/internal/error-reporter</li>
83230592Sken *  <li>http://apache.org/xml/properties/internal/entity-resolver</li>
84230592Sken * </ul>
85213702Smdf *
86213702Smdf *
87212420Sken * @author Andy Clark, IBM
88212420Sken * @author Arnaud  Le Hors, IBM
89212420Sken * @author K.Venugopal SUN Microsystems
90212420Sken * @author Neeraj Bajaj SUN Microsystems
91212420Sken * @author Sunitha Reddy SUN Microsystems
92230592Sken */
93212420Skenpublic class XMLEntityManager implements XMLComponent, XMLEntityResolver {
94212420Sken
95212420Sken    //
96212420Sken    // Constants
97212420Sken    //
98212420Sken
99230592Sken    /** Default buffer size (2048). */
100230592Sken    public static final int DEFAULT_BUFFER_SIZE = 8192;
101230592Sken
102212420Sken    /** Default buffer size before we've finished with the XMLDecl:  */
103212420Sken    public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64;
104230592Sken
105230592Sken    /** Default internal entity buffer size (1024). */
106230592Sken    public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 1024;
107212420Sken
108212420Sken    // feature identifiers
109212420Sken
110213702Smdf    /** Feature identifier: validation. */
111212420Sken    protected static final String VALIDATION =
112212420Sken            Constants.SAX_FEATURE_PREFIX + Constants.VALIDATION_FEATURE;
113212420Sken
114212420Sken    /**
115212420Sken     * standard uri conformant (strict uri).
116212420Sken     * http://apache.org/xml/features/standard-uri-conformant
117213702Smdf     */
118212420Sken    protected boolean fStrictURI;
119212420Sken
120212420Sken
121213708Smdf    /** Feature identifier: external general entities. */
122213708Smdf    protected static final String EXTERNAL_GENERAL_ENTITIES =
123213708Smdf            Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE;
124213708Smdf
125213708Smdf    /** Feature identifier: external parameter entities. */
126213708Smdf    protected static final String EXTERNAL_PARAMETER_ENTITIES =
127213708Smdf            Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE;
128213708Smdf
129213708Smdf    /** Feature identifier: allow Java encodings. */
130213708Smdf    protected static final String ALLOW_JAVA_ENCODINGS =
131213707Smdf            Constants.XERCES_FEATURE_PREFIX + Constants.ALLOW_JAVA_ENCODINGS_FEATURE;
132213707Smdf
133213707Smdf    /** Feature identifier: warn on duplicate EntityDef */
134213707Smdf    protected static final String WARN_ON_DUPLICATE_ENTITYDEF =
135213707Smdf            Constants.XERCES_FEATURE_PREFIX +Constants.WARN_ON_DUPLICATE_ENTITYDEF_FEATURE;
136213707Smdf
137213707Smdf    /** Feature identifier: load external DTD. */
138213707Smdf    protected static final String LOAD_EXTERNAL_DTD =
139213707Smdf            Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE;
140213707Smdf
141213708Smdf    // property identifiers
142213708Smdf
143213707Smdf    /** Property identifier: symbol table. */
144213707Smdf    protected static final String SYMBOL_TABLE =
145230592Sken            Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
146230592Sken
147230592Sken    /** Property identifier: error reporter. */
148230592Sken    protected static final String ERROR_REPORTER =
149230592Sken            Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
150230592Sken
151230592Sken    /** Feature identifier: standard uri conformant */
152230592Sken    protected static final String STANDARD_URI_CONFORMANT =
153230592Sken            Constants.XERCES_FEATURE_PREFIX +Constants.STANDARD_URI_CONFORMANT_FEATURE;
154230592Sken
155230592Sken    /** Property identifier: entity resolver. */
156230592Sken    protected static final String ENTITY_RESOLVER =
157230592Sken            Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_RESOLVER_PROPERTY;
158230592Sken
159230592Sken    protected static final String STAX_ENTITY_RESOLVER =
160230592Sken            Constants.XERCES_PROPERTY_PREFIX + Constants.STAX_ENTITY_RESOLVER_PROPERTY;
161230592Sken
162230592Sken    // property identifier:  ValidationManager
163230592Sken    protected static final String VALIDATION_MANAGER =
164230592Sken            Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY;
165230592Sken
166230592Sken    /** property identifier: buffer size. */
167230592Sken    protected static final String BUFFER_SIZE =
168230592Sken            Constants.XERCES_PROPERTY_PREFIX + Constants.BUFFER_SIZE_PROPERTY;
169230592Sken
170230592Sken    /** property identifier: security manager. */
171230592Sken    protected static final String SECURITY_MANAGER =
172230592Sken        Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY;
173230592Sken
174230592Sken    protected static final String PARSER_SETTINGS =
175230592Sken        Constants.XERCES_FEATURE_PREFIX + Constants.PARSER_SETTINGS;
176230592Sken
177230592Sken    /** Property identifier: Security property manager. */
178230592Sken    private static final String XML_SECURITY_PROPERTY_MANAGER =
179212420Sken            Constants.XML_SECURITY_PROPERTY_MANAGER;
180212420Sken
181230592Sken    /** access external dtd: file protocol */
182230592Sken    static final String EXTERNAL_ACCESS_DEFAULT = Constants.EXTERNAL_ACCESS_DEFAULT;
183230592Sken
184230592Sken    // recognized features and properties
185230592Sken
186230592Sken    /** Recognized features. */
187230592Sken    private static final String[] RECOGNIZED_FEATURES = {
188230592Sken                VALIDATION,
189230592Sken                EXTERNAL_GENERAL_ENTITIES,
190230592Sken                EXTERNAL_PARAMETER_ENTITIES,
191212420Sken                ALLOW_JAVA_ENCODINGS,
192212420Sken                WARN_ON_DUPLICATE_ENTITYDEF,
193212420Sken                STANDARD_URI_CONFORMANT,
194212420Sken                XMLConstants.USE_CATALOG
195212420Sken    };
196212420Sken
197212420Sken    /** Feature defaults. */
198212420Sken    private static final Boolean[] FEATURE_DEFAULTS = {
199212420Sken                null,
200212420Sken                Boolean.TRUE,
201212420Sken                Boolean.TRUE,
202212420Sken                Boolean.TRUE,
203212420Sken                Boolean.FALSE,
204212420Sken                Boolean.FALSE,
205212420Sken                JdkXmlUtils.USE_CATALOG_DEFAULT
206212420Sken    };
207212420Sken
208212420Sken    /** Recognized properties. */
209212420Sken    private static final String[] RECOGNIZED_PROPERTIES = {
210212420Sken                SYMBOL_TABLE,
211212420Sken                ERROR_REPORTER,
212212420Sken                ENTITY_RESOLVER,
213212420Sken                VALIDATION_MANAGER,
214212420Sken                BUFFER_SIZE,
215212420Sken                SECURITY_MANAGER,
216212420Sken                XML_SECURITY_PROPERTY_MANAGER,
217212420Sken                JdkXmlUtils.CATALOG_DEFER,
218212420Sken                JdkXmlUtils.CATALOG_FILES,
219212420Sken                JdkXmlUtils.CATALOG_PREFER,
220212420Sken                JdkXmlUtils.CATALOG_RESOLVE,
221212420Sken                JdkXmlUtils.CDATA_CHUNK_SIZE
222212420Sken    };
223212420Sken
224212420Sken    /** Property defaults. */
225212420Sken    private static final Object[] PROPERTY_DEFAULTS = {
226212420Sken                null,
227212420Sken                null,
228212420Sken                null,
229212420Sken                null,
230212420Sken                DEFAULT_BUFFER_SIZE,
231212420Sken                null,
232212420Sken                null,
233212420Sken                null,
234212420Sken                null,
235212420Sken                null,
236212420Sken                null,
237212420Sken                JdkXmlUtils.CDATA_CHUNK_SIZE_DEFAULT
238212420Sken    };
239212420Sken
240212420Sken    private static final String XMLEntity = "[xml]".intern();
241212420Sken    private static final String DTDEntity = "[dtd]".intern();
242212420Sken
243212420Sken    // debugging
244212420Sken
245212420Sken    /**
246212420Sken     * Debug printing of buffer. This debugging flag works best when you
247212420Sken     * resize the DEFAULT_BUFFER_SIZE down to something reasonable like
248212420Sken     * 64 characters.
249212420Sken     */
250212420Sken    private static final boolean DEBUG_BUFFER = false;
251212420Sken
252212420Sken    /** warn on duplicate Entity declaration.
253212420Sken     *  http://apache.org/xml/features/warn-on-duplicate-entitydef
254212420Sken     */
255212420Sken    protected boolean fWarnDuplicateEntityDef;
256212420Sken
257212420Sken    /** Debug some basic entities. */
258212420Sken    private static final boolean DEBUG_ENTITIES = false;
259212420Sken
260212420Sken    /** Debug switching readers for encodings. */
261212420Sken    private static final boolean DEBUG_ENCODINGS = false;
262212420Sken
263212420Sken    // should be diplayed trace resolving messages
264212420Sken    private static final boolean DEBUG_RESOLVER = false ;
265212420Sken
266212420Sken    //
267212420Sken    // Data
268212420Sken    //
269212420Sken
270212420Sken    // features
271212420Sken
272212420Sken    /**
273212420Sken     * Validation. This feature identifier is:
274212420Sken     * http://xml.org/sax/features/validation
275212420Sken     */
276212420Sken    protected boolean fValidation;
277212420Sken
278212420Sken    /**
279212420Sken     * External general entities. This feature identifier is:
280212420Sken     * http://xml.org/sax/features/external-general-entities
281212420Sken     */
282212420Sken    protected boolean fExternalGeneralEntities;
283212420Sken
284212420Sken    /**
285212420Sken     * External parameter entities. This feature identifier is:
286212420Sken     * http://xml.org/sax/features/external-parameter-entities
287212420Sken     */
288212420Sken    protected boolean fExternalParameterEntities;
289212420Sken
290212420Sken    /**
291212420Sken     * Allow Java encoding names. This feature identifier is:
292212420Sken     * http://apache.org/xml/features/allow-java-encodings
293212420Sken     */
294212420Sken    protected boolean fAllowJavaEncodings = true ;
295212420Sken
296212420Sken    /** Load external DTD. */
297212420Sken    protected boolean fLoadExternalDTD = true;
298212420Sken
299212420Sken    // properties
300212420Sken
301212420Sken    /**
302212420Sken     * Symbol table. This property identifier is:
303212420Sken     * http://apache.org/xml/properties/internal/symbol-table
304212420Sken     */
305212420Sken    protected SymbolTable fSymbolTable;
306212420Sken
307212420Sken    /**
308212420Sken     * Error reporter. This property identifier is:
309212420Sken     * http://apache.org/xml/properties/internal/error-reporter
310212420Sken     */
311212420Sken    protected XMLErrorReporter fErrorReporter;
312212420Sken
313212420Sken    /**
314212420Sken     * Entity resolver. This property identifier is:
315212420Sken     * http://apache.org/xml/properties/internal/entity-resolver
316212420Sken     */
317212420Sken    protected XMLEntityResolver fEntityResolver;
318212420Sken
319212420Sken    /** Stax Entity Resolver. This property identifier is XMLInputFactory.ENTITY_RESOLVER */
320212420Sken
321212420Sken    protected StaxEntityResolverWrapper fStaxEntityResolver;
322212420Sken
323212420Sken    /** Property Manager. This is used from Stax */
324212420Sken    protected PropertyManager fPropertyManager ;
325212420Sken
326212420Sken    /** StAX properties */
327212420Sken    boolean fSupportDTD = true;
328212420Sken    boolean fReplaceEntityReferences = true;
329212420Sken    boolean fSupportExternalEntities = true;
330212420Sken
331212420Sken    /** used to restrict external access */
332212420Sken    protected String fAccessExternalDTD = EXTERNAL_ACCESS_DEFAULT;
333212420Sken
334212420Sken    // settings
335212420Sken
336212420Sken    /**
337212420Sken     * Validation manager. This property identifier is:
338212420Sken     * http://apache.org/xml/properties/internal/validation-manager
339212420Sken     */
340212420Sken    protected ValidationManager fValidationManager;
341212420Sken
342212420Sken    // settings
343212420Sken
344212420Sken    /**
345212420Sken     * Buffer size. We get this value from a property. The default size
346212420Sken     * is used if the input buffer size property is not specified.
347212420Sken     * REVISIT: do we need a property for internal entity buffer size?
348212420Sken     */
349212420Sken    protected int fBufferSize = DEFAULT_BUFFER_SIZE;
350212420Sken
351212420Sken    /** Security Manager */
352212420Sken    protected XMLSecurityManager fSecurityManager = null;
353212420Sken
354212420Sken    protected XMLLimitAnalyzer fLimitAnalyzer = null;
355212420Sken
356212420Sken    protected int entityExpansionIndex;
357212420Sken
358212420Sken    /**
359212420Sken     * True if the document entity is standalone. This should really
360212420Sken     * only be set by the document source (e.g. XMLDocumentScanner).
361212420Sken     */
362212420Sken    protected boolean fStandalone;
363212420Sken
364212420Sken    // are the entities being parsed in the external subset?
365212420Sken    // NOTE:  this *is not* the same as whether they're external entities!
366212420Sken    protected boolean fInExternalSubset = false;
367212420Sken
368212420Sken
369212420Sken    // handlers
370212420Sken    /** Entity handler. */
371212420Sken    protected XMLEntityHandler fEntityHandler;
372212420Sken
373212420Sken    /** Current entity scanner */
374212420Sken    protected XMLEntityScanner fEntityScanner ;
375212420Sken
376212420Sken    /** XML 1.0 entity scanner. */
377212420Sken    protected XMLEntityScanner fXML10EntityScanner;
378212420Sken
379212420Sken    /** XML 1.1 entity scanner. */
380212420Sken    protected XMLEntityScanner fXML11EntityScanner;
381212420Sken
382212420Sken    /** count of entities expanded: */
383212420Sken    protected int fEntityExpansionCount = 0;
384212420Sken
385212420Sken    // entities
386212420Sken
387212420Sken    /** Entities. */
388212420Sken    protected Map<String, Entity> fEntities = new HashMap<>();
389212420Sken
390212420Sken    /** Entity stack. */
391212420Sken    protected Stack<Entity> fEntityStack = new Stack<>();
392212420Sken
393212420Sken    /** Current entity. */
394212420Sken    protected Entity.ScannedEntity fCurrentEntity = null;
395212420Sken
396212420Sken    /** identify if the InputSource is created by a resolver */
397212420Sken    boolean fISCreatedByResolver = false;
398212420Sken
399212420Sken    // shared context
400212420Sken
401212420Sken    protected XMLEntityStorage fEntityStorage ;
402212420Sken
403212420Sken    protected final Object [] defaultEncoding = new Object[]{"UTF-8", null};
404212420Sken
405212420Sken
406212420Sken    // temp vars
407212420Sken
408216088Sken    /** Resource identifer. */
409213839Smdf    private final XMLResourceIdentifierImpl fResourceIdentifier = new XMLResourceIdentifierImpl();
410213839Smdf
411213839Smdf    /** Augmentations for entities. */
412213839Smdf    private final Augmentations fEntityAugs = new AugmentationsImpl();
413213839Smdf
414213839Smdf    /** Pool of character buffers. */
415213839Smdf    private CharacterBufferPool fBufferPool = new CharacterBufferPool(fBufferSize, DEFAULT_INTERNAL_BUFFER_SIZE);
416213839Smdf
417213839Smdf    /** indicate whether Catalog should be used for resolving external resources */
418213839Smdf    private boolean fUseCatalog = true;
419213839Smdf    CatalogFeatures fCatalogFeatures;
420213839Smdf    CatalogResolver fCatalogResolver;
421213839Smdf
422213839Smdf    private String fCatalogFile;
423213708Smdf    private String fDefer;
424213708Smdf    private String fPrefer;
425213708Smdf    private String fResolve;
426213708Smdf
427213708Smdf    //
428213708Smdf    // Constructors
429213708Smdf    //
430213708Smdf
431213708Smdf    /**
432213708Smdf     * If this constructor is used to create the object, reset() should be invoked on this object
433213708Smdf     */
434213708Smdf    public XMLEntityManager() {
435213708Smdf        //for entity managers not created by parsers
436213708Smdf        fSecurityManager = new XMLSecurityManager(true);
437213708Smdf        fEntityStorage = new XMLEntityStorage(this) ;
438213708Smdf        setScannerVersion(Constants.XML_VERSION_1_0);
439213708Smdf    } // <init>()
440213708Smdf
441213708Smdf    /** Default constructor. */
442213708Smdf    public XMLEntityManager(PropertyManager propertyManager) {
443213708Smdf        fPropertyManager = propertyManager ;
444213708Smdf        //pass a reference to current entity being scanned
445213708Smdf        //fEntityStorage = new XMLEntityStorage(fCurrentEntity) ;
446213708Smdf        fEntityStorage = new XMLEntityStorage(this) ;
447213708Smdf        fEntityScanner = new XMLEntityScanner(propertyManager, this) ;
448213708Smdf        reset(propertyManager);
449213708Smdf    } // <init>()
450213708Smdf
451213708Smdf    /**
452213708Smdf     * Adds an internal entity declaration.
453213708Smdf     * <p>
454213708Smdf     * <strong>Note:</strong> This method ignores subsequent entity
455213708Smdf     * declarations.
456213708Smdf     * <p>
457213708Smdf     * <strong>Note:</strong> The name should be a unique symbol. The
458213708Smdf     * SymbolTable can be used for this purpose.
459213708Smdf     *
460213708Smdf     * @param name The name of the entity.
461213708Smdf     * @param text The text of the entity.
462213708Smdf     *
463213708Smdf     * @see SymbolTable
464213708Smdf     */
465213708Smdf    public void addInternalEntity(String name, String text) {
466213708Smdf        if (!fEntities.containsKey(name)) {
467213708Smdf            Entity entity = new Entity.InternalEntity(name, text, fInExternalSubset);
468213708Smdf            fEntities.put(name, entity);
469213840Smdf        } else{
470213840Smdf            if(fWarnDuplicateEntityDef){
471213708Smdf                fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
472213840Smdf                        "MSG_DUPLICATE_ENTITY_DEFINITION",
473213840Smdf                        new Object[]{ name },
474213840Smdf                        XMLErrorReporter.SEVERITY_WARNING );
475213840Smdf            }
476213840Smdf        }
477213840Smdf
478213708Smdf    } // addInternalEntity(String,String)
479213708Smdf
480213708Smdf    /**
481213708Smdf     * Adds an external entity declaration.
482213708Smdf     * <p>
483213840Smdf     * <strong>Note:</strong> This method ignores subsequent entity
484213840Smdf     * declarations.
485213840Smdf     * <p>
486213840Smdf     * <strong>Note:</strong> The name should be a unique symbol. The
487213840Smdf     * SymbolTable can be used for this purpose.
488213840Smdf     *
489213840Smdf     * @param name         The name of the entity.
490213839Smdf     * @param publicId     The public identifier of the entity.
491213840Smdf     * @param literalSystemId     The system identifier of the entity.
492213840Smdf     * @param baseSystemId The base system identifier of the entity.
493213840Smdf     *                     This is the system identifier of the entity
494213840Smdf     *                     where <em>the entity being added</em> and
495213840Smdf     *                     is used to expand the system identifier when
496213840Smdf     *                     the system identifier is a relative URI.
497213840Smdf     *                     When null the system identifier of the first
498213840Smdf     *                     external entity on the stack is used instead.
499213840Smdf     *
500213840Smdf     * @see SymbolTable
501213840Smdf     */
502213840Smdf    public void addExternalEntity(String name,
503213840Smdf            String publicId, String literalSystemId,
504213840Smdf            String baseSystemId) throws IOException {
505213840Smdf        if (!fEntities.containsKey(name)) {
506213840Smdf            if (baseSystemId == null) {
507213840Smdf                // search for the first external entity on the stack
508213840Smdf                int size = fEntityStack.size();
509213840Smdf                if (size == 0 && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
510213840Smdf                    baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
511213840Smdf                }
512213840Smdf                for (int i = size - 1; i >= 0 ; i--) {
513213708Smdf                    Entity.ScannedEntity externalEntity =
514213708Smdf                            (Entity.ScannedEntity)fEntityStack.elementAt(i);
515213708Smdf                    if (externalEntity.entityLocation != null && externalEntity.entityLocation.getExpandedSystemId() != null) {
516213708Smdf                        baseSystemId = externalEntity.entityLocation.getExpandedSystemId();
517213708Smdf                        break;
518213708Smdf                    }
519213708Smdf                }
520213708Smdf            }
521213708Smdf            Entity entity = new Entity.ExternalEntity(name,
522213708Smdf                    new XMLEntityDescriptionImpl(name, publicId, literalSystemId, baseSystemId,
523213839Smdf                    expandSystemId(literalSystemId, baseSystemId, false)), null, fInExternalSubset);
524213708Smdf            fEntities.put(name, entity);
525213708Smdf        } else{
526213708Smdf            if(fWarnDuplicateEntityDef){
527213708Smdf                fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
528213708Smdf                        "MSG_DUPLICATE_ENTITY_DEFINITION",
529213839Smdf                        new Object[]{ name },
530213708Smdf                        XMLErrorReporter.SEVERITY_WARNING );
531213708Smdf            }
532213708Smdf        }
533213708Smdf
534213708Smdf    } // addExternalEntity(String,String,String,String)
535213708Smdf
536213839Smdf
537213708Smdf    /**
538213708Smdf     * Adds an unparsed entity declaration.
539213708Smdf     * <p>
540213708Smdf     * <strong>Note:</strong> This method ignores subsequent entity
541213708Smdf     * declarations.
542213839Smdf     * <p>
543213708Smdf     * <strong>Note:</strong> The name should be a unique symbol. The
544213708Smdf     * SymbolTable can be used for this purpose.
545213708Smdf     *
546213708Smdf     * @param name     The name of the entity.
547213708Smdf     * @param publicId The public identifier of the entity.
548213839Smdf     * @param systemId The system identifier of the entity.
549213839Smdf     * @param notation The name of the notation.
550213708Smdf     *
551213708Smdf     * @see SymbolTable
552213708Smdf     */
553213708Smdf    public void addUnparsedEntity(String name,
554213839Smdf            String publicId, String systemId,
555213839Smdf            String baseSystemId, String notation) {
556213708Smdf        if (!fEntities.containsKey(name)) {
557213839Smdf            Entity.ExternalEntity entity = new Entity.ExternalEntity(name,
558213708Smdf                    new XMLEntityDescriptionImpl(name, publicId, systemId, baseSystemId, null),
559213708Smdf                    notation, fInExternalSubset);
560213708Smdf            fEntities.put(name, entity);
561213708Smdf        } else{
562213708Smdf            if(fWarnDuplicateEntityDef){
563213708Smdf                fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
564213708Smdf                        "MSG_DUPLICATE_ENTITY_DEFINITION",
565213708Smdf                        new Object[]{ name },
566213708Smdf                        XMLErrorReporter.SEVERITY_WARNING );
567213708Smdf            }
568213708Smdf        }
569213708Smdf    } // addUnparsedEntity(String,String,String,String)
570213708Smdf
571213708Smdf
572213708Smdf    /** get the entity storage object from entity manager */
573213708Smdf    public XMLEntityStorage getEntityStore(){
574213839Smdf        return fEntityStorage ;
575213708Smdf    }
576213708Smdf
577213708Smdf    /** return the entity responsible for reading the entity */
578213708Smdf    public XMLEntityScanner getEntityScanner(){
579213708Smdf        if(fEntityScanner == null) {
580213708Smdf            // default to 1.0
581213708Smdf            if(fXML10EntityScanner == null) {
582213708Smdf                fXML10EntityScanner = new XMLEntityScanner();
583213708Smdf            }
584213708Smdf            fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
585213708Smdf            fEntityScanner = fXML10EntityScanner;
586213708Smdf        }
587213708Smdf        return fEntityScanner;
588213708Smdf
589213708Smdf    }
590213708Smdf
591213708Smdf    public void setScannerVersion(short version) {
592213839Smdf
593213708Smdf        if(version == Constants.XML_VERSION_1_0) {
594213708Smdf            if(fXML10EntityScanner == null) {
595213708Smdf                fXML10EntityScanner = new XMLEntityScanner();
596213708Smdf            }
597213708Smdf            fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
598213708Smdf            fEntityScanner = fXML10EntityScanner;
599213708Smdf            fEntityScanner.setCurrentEntity(fCurrentEntity);
600213708Smdf        } else {
601213708Smdf            if(fXML11EntityScanner == null) {
602213708Smdf                fXML11EntityScanner = new XML11EntityScanner();
603213708Smdf            }
604213708Smdf            fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter);
605213708Smdf            fEntityScanner = fXML11EntityScanner;
606213708Smdf            fEntityScanner.setCurrentEntity(fCurrentEntity);
607213708Smdf        }
608213708Smdf
609213708Smdf    }
610213839Smdf
611213708Smdf    /**
612213708Smdf     * This method uses the passed-in XMLInputSource to make
613213708Smdf     * fCurrentEntity usable for reading.
614213708Smdf     *
615213708Smdf     * @param reference flag to indicate whether the entity is an Entity Reference.
616213708Smdf     * @param name  name of the entity (XML is it's the document entity)
617213708Smdf     * @param xmlInputSource    the input source, with sufficient information
618213708Smdf     *      to begin scanning characters.
619213708Smdf     * @param literal        True if this entity is started within a
620213708Smdf     *                       literal value.
621213708Smdf     * @param isExternal    whether this entity should be treated as an internal or external entity.
622213708Smdf     * @throws IOException  if anything can't be read
623213708Smdf     *  XNIException    If any parser-specific goes wrong.
624213708Smdf     * @return the encoding of the new entity or null if a character stream was employed
625213708Smdf     */
626213708Smdf    public String setupCurrentEntity(boolean reference, String name, XMLInputSource xmlInputSource,
627213708Smdf            boolean literal, boolean isExternal)
628213708Smdf            throws IOException, XNIException {
629213708Smdf        // get information
630213708Smdf
631212420Sken        final String publicId = xmlInputSource.getPublicId();
632213708Smdf        String literalSystemId = xmlInputSource.getSystemId();
633213708Smdf        String baseSystemId = xmlInputSource.getBaseSystemId();
634212420Sken        String encoding = xmlInputSource.getEncoding();
635213708Smdf        final boolean encodingExternallySpecified = (encoding != null);
636213708Smdf        Boolean isBigEndian = null;
637213708Smdf
638213708Smdf        // create reader
639213708Smdf        InputStream stream = null;
640213708Smdf        Reader reader = xmlInputSource.getCharacterStream();
641213708Smdf
642213708Smdf        // First chance checking strict URI
643213708Smdf        String expandedSystemId = expandSystemId(literalSystemId, baseSystemId, fStrictURI);
644213708Smdf        if (baseSystemId == null) {
645212420Sken            baseSystemId = expandedSystemId;
646212420Sken        }
647213708Smdf        if (reader == null) {
648212420Sken            stream = xmlInputSource.getByteStream();
649213708Smdf            if (stream == null) {
650213708Smdf                URL location = new URL(expandedSystemId);
651212420Sken                URLConnection connect = location.openConnection();
652213708Smdf                if (!(connect instanceof HttpURLConnection)) {
653213708Smdf                    stream = connect.getInputStream();
654213708Smdf                }
655213708Smdf                else {
656213708Smdf                    boolean followRedirects = true;
657212420Sken
658212420Sken                    // setup URLConnection if we have an HTTPInputSource
659212420Sken                    if (xmlInputSource instanceof HTTPInputSource) {
660212420Sken                        final HttpURLConnection urlConnection = (HttpURLConnection) connect;
661212420Sken                        final HTTPInputSource httpInputSource = (HTTPInputSource) xmlInputSource;
662212420Sken
663212420Sken                        // set request properties
664213704Smdf                        Iterator<Map.Entry<String, String>> propIter = httpInputSource.getHTTPRequestProperties();
665213882Smdf                        while (propIter.hasNext()) {
666212420Sken                            Map.Entry<String, String> entry = propIter.next();
667212420Sken                            urlConnection.setRequestProperty(entry.getKey(), entry.getValue());
668212420Sken                        }
669212420Sken
670212420Sken                        // set preference for redirection
671212420Sken                        followRedirects = httpInputSource.getFollowHTTPRedirects();
672212420Sken                        if (!followRedirects) {
673212420Sken                            urlConnection.setInstanceFollowRedirects(followRedirects);
674212420Sken                        }
675212420Sken                    }
676212420Sken
677212420Sken                    stream = connect.getInputStream();
678212420Sken
679212420Sken                    // REVISIT: If the URLConnection has external encoding
680212420Sken                    // information, we should be reading it here. It's located
681212420Sken                    // in the charset parameter of Content-Type. -- mrglavas
682212420Sken
683212420Sken                    if (followRedirects) {
684213704Smdf                        String redirect = connect.getURL().toString();
685213704Smdf                        // E43: Check if the URL was redirected, and then
686213704Smdf                        // update literal and expanded system IDs if needed.
687213704Smdf                        if (!redirect.equals(expandedSystemId)) {
688213704Smdf                            literalSystemId = redirect;
689213704Smdf                            expandedSystemId = redirect;
690213704Smdf                        }
691212420Sken                    }
692212420Sken                }
693212420Sken            }
694212420Sken
695213708Smdf            // wrap this stream in RewindableInputStream
696212420Sken            stream = new RewindableInputStream(stream);
697212420Sken
698212420Sken            // perform auto-detect of encoding if necessary
699213704Smdf            if (encoding == null) {
700212420Sken                // read first four bytes and determine encoding
701212420Sken                final byte[] b4 = new byte[4];
702212420Sken                int count = 0;
703212420Sken                for (; count<4; count++ ) {
704212420Sken                    b4[count] = (byte)stream.read();
705212420Sken                }
706212420Sken                if (count == 4) {
707212420Sken                    Object [] encodingDesc = getEncodingName(b4, count);
708212420Sken                    encoding = (String)(encodingDesc[0]);
709212420Sken                    isBigEndian = (Boolean)(encodingDesc[1]);
710212420Sken
711230592Sken                    stream.reset();
712212420Sken                    // Special case UTF-8 files with BOM created by Microsoft
713212420Sken                    // tools. It's more efficient to consume the BOM than make
714212420Sken                    // the reader perform extra checks. -Ac
715230592Sken                    if (count > 2 && encoding.equals("UTF-8")) {
716212420Sken                        int b0 = b4[0] & 0xFF;
717230592Sken                        int b1 = b4[1] & 0xFF;
718213882Smdf                        int b2 = b4[2] & 0xFF;
719213882Smdf                        if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
720212420Sken                            // ignore first three bytes...
721212420Sken                            stream.skip(3);
722212420Sken                        }
723212420Sken                    }
724212420Sken                    reader = createReader(stream, encoding, isBigEndian);
725212420Sken                } else {
726212420Sken                    reader = createReader(stream, encoding, isBigEndian);
727212420Sken                }
728212420Sken            }
729212420Sken
730212420Sken            // use specified encoding
731212420Sken            else {
732212420Sken                encoding = encoding.toUpperCase(Locale.ENGLISH);
733212420Sken
734212420Sken                // If encoding is UTF-8, consume BOM if one is present.
735212420Sken                if (encoding.equals("UTF-8")) {
736213704Smdf                    final int[] b3 = new int[3];
737212420Sken                    int count = 0;
738212420Sken                    for (; count < 3; ++count) {
739212420Sken                        b3[count] = stream.read();
740213704Smdf                        if (b3[count] == -1)
741213704Smdf                            break;
742213882Smdf                    }
743213882Smdf                    if (count == 3) {
744213882Smdf                        if (b3[0] != 0xEF || b3[1] != 0xBB || b3[2] != 0xBF) {
745213704Smdf                            // First three bytes are not BOM, so reset.
746213704Smdf                            stream.reset();
747213704Smdf                        }
748213704Smdf                    } else {
749230592Sken                        stream.reset();
750212420Sken                    }
751212420Sken                }
752230592Sken                // If encoding is UTF-16, we still need to read the first four bytes
753230592Sken                // in order to discover the byte order.
754230592Sken                else if (encoding.equals("UTF-16")) {
755230592Sken                    final int[] b4 = new int[4];
756230592Sken                    int count = 0;
757230592Sken                    for (; count < 4; ++count) {
758230592Sken                        b4[count] = stream.read();
759230592Sken                        if (b4[count] == -1)
760230592Sken                            break;
761230592Sken                    }
762230592Sken                    stream.reset();
763230592Sken
764230592Sken                    String utf16Encoding = "UTF-16";
765230592Sken                    if (count >= 2) {
766230592Sken                        final int b0 = b4[0];
767230592Sken                        final int b1 = b4[1];
768230592Sken                        if (b0 == 0xFE && b1 == 0xFF) {
769230592Sken                            // UTF-16, big-endian
770230592Sken                            utf16Encoding = "UTF-16BE";
771230592Sken                            isBigEndian = Boolean.TRUE;
772230592Sken                        }
773230592Sken                        else if (b0 == 0xFF && b1 == 0xFE) {
774230592Sken                            // UTF-16, little-endian
775230592Sken                            utf16Encoding = "UTF-16LE";
776230592Sken                            isBigEndian = Boolean.FALSE;
777230592Sken                        }
778230592Sken                        else if (count == 4) {
779230592Sken                            final int b2 = b4[2];
780230592Sken                            final int b3 = b4[3];
781230592Sken                            if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
782230592Sken                                // UTF-16, big-endian, no BOM
783230592Sken                                utf16Encoding = "UTF-16BE";
784230592Sken                                isBigEndian = Boolean.TRUE;
785230592Sken                            }
786230592Sken                            if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
787230592Sken                                // UTF-16, little-endian, no BOM
788230592Sken                                utf16Encoding = "UTF-16LE";
789230592Sken                                isBigEndian = Boolean.FALSE;
790230592Sken                            }
791230592Sken                        }
792230592Sken                    }
793230592Sken                    reader = createReader(stream, utf16Encoding, isBigEndian);
794230592Sken                }
795230592Sken                // If encoding is UCS-4, we still need to read the first four bytes
796230592Sken                // in order to discover the byte order.
797230592Sken                else if (encoding.equals("ISO-10646-UCS-4")) {
798230592Sken                    final int[] b4 = new int[4];
799230592Sken                    int count = 0;
800230592Sken                    for (; count < 4; ++count) {
801230592Sken                        b4[count] = stream.read();
802230592Sken                        if (b4[count] == -1)
803230592Sken                            break;
804230592Sken                    }
805230592Sken                    stream.reset();
806230592Sken
807230592Sken                    // Ignore unusual octet order for now.
808230592Sken                    if (count == 4) {
809230592Sken                        // UCS-4, big endian (1234)
810230592Sken                        if (b4[0] == 0x00 && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x3C) {
811230592Sken                            isBigEndian = Boolean.TRUE;
812230592Sken                        }
813230592Sken                        // UCS-4, little endian (1234)
814230592Sken                        else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x00) {
815230592Sken                            isBigEndian = Boolean.FALSE;
816230592Sken                        }
817230592Sken                    }
818230592Sken                }
819230592Sken                // If encoding is UCS-2, we still need to read the first four bytes
820230592Sken                // in order to discover the byte order.
821230592Sken                else if (encoding.equals("ISO-10646-UCS-2")) {
822230592Sken                    final int[] b4 = new int[4];
823230592Sken                    int count = 0;
824230592Sken                    for (; count < 4; ++count) {
825230592Sken                        b4[count] = stream.read();
826230592Sken                        if (b4[count] == -1)
827230592Sken                            break;
828230592Sken                    }
829230592Sken                    stream.reset();
830230592Sken
831230592Sken                    if (count == 4) {
832230592Sken                        // UCS-2, big endian
833230592Sken                        if (b4[0] == 0x00 && b4[1] == 0x3C && b4[2] == 0x00 && b4[3] == 0x3F) {
834230592Sken                            isBigEndian = Boolean.TRUE;
835230592Sken                        }
836230592Sken                        // UCS-2, little endian
837230592Sken                        else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x3F && b4[3] == 0x00) {
838230592Sken                            isBigEndian = Boolean.FALSE;
839230592Sken                        }
840230592Sken                    }
841230592Sken                }
842230592Sken
843230592Sken                reader = createReader(stream, encoding, isBigEndian);
844230592Sken            }
845230592Sken
846230592Sken            // read one character at a time so we don't jump too far
847230592Sken            // ahead, converting characters from the byte stream in
848230592Sken            // the wrong encoding
849230592Sken            if (DEBUG_ENCODINGS) {
850230592Sken                System.out.println("$$$ no longer wrapping reader in OneCharReader");
851230592Sken            }
852230592Sken            //reader = new OneCharReader(reader);
853230592Sken        }
854230592Sken
855230592Sken        // We've seen a new Reader.
856230592Sken        // Push it on the stack so we can close it later.
857230592Sken        //fOwnReaders.add(reader);
858230592Sken
859230592Sken        // push entity on stack
860230592Sken        if (fCurrentEntity != null) {
861230592Sken            fEntityStack.push(fCurrentEntity);
862230592Sken        }
863230592Sken
864230592Sken        // create entity
865230592Sken        /* if encoding is specified externally, 'encoding' information present
866230592Sken         * in the prolog of the XML document is not considered. Hence, prolog can
867230592Sken         * be read in Chunks of data instead of byte by byte.
868230592Sken         */
869230592Sken        fCurrentEntity = new Entity.ScannedEntity(reference, name,
870230592Sken                new XMLResourceIdentifierImpl(publicId, literalSystemId, baseSystemId, expandedSystemId),
871230592Sken                stream, reader, encoding, literal, encodingExternallySpecified, isExternal);
872230592Sken        fCurrentEntity.setEncodingExternallySpecified(encodingExternallySpecified);
873230592Sken        fEntityScanner.setCurrentEntity(fCurrentEntity);
874230592Sken        fResourceIdentifier.setValues(publicId, literalSystemId, baseSystemId, expandedSystemId);
875230592Sken        if (fLimitAnalyzer != null) {
876230592Sken            fLimitAnalyzer.startEntity(name);
877230592Sken        }
878230592Sken        return encoding;
879230592Sken    } //setupCurrentEntity(String, XMLInputSource, boolean, boolean):  String
880230592Sken
881230592Sken
882230592Sken    /**
883230592Sken     * Checks whether an entity given by name is external.
884230592Sken     *
885230592Sken     * @param entityName The name of the entity to check.
886230592Sken     * @return True if the entity is external, false otherwise
887230592Sken     * (including when the entity is not declared).
888230592Sken     */
889230592Sken    public boolean isExternalEntity(String entityName) {
890230592Sken
891230592Sken        Entity entity = fEntities.get(entityName);
892230592Sken        if (entity == null) {
893230592Sken            return false;
894230592Sken        }
895230592Sken        return entity.isExternal();
896230592Sken    }
897230592Sken
898230592Sken    /**
899230592Sken     * Checks whether the declaration of an entity given by name is
900230592Sken     * // in the external subset.
901230592Sken     *
902230592Sken     * @param entityName The name of the entity to check.
903230592Sken     * @return True if the entity was declared in the external subset, false otherwise
904230592Sken     *           (including when the entity is not declared).
905230592Sken     */
906230592Sken    public boolean isEntityDeclInExternalSubset(String entityName) {
907230592Sken
908230592Sken        Entity entity = fEntities.get(entityName);
909230592Sken        if (entity == null) {
910230592Sken            return false;
911230592Sken        }
912230592Sken        return entity.isEntityDeclInExternalSubset();
913230592Sken    }
914230592Sken
915230592Sken
916230592Sken
917230592Sken    //
918230592Sken    // Public methods
919230592Sken    //
920230592Sken
921230592Sken    /**
922230592Sken     * Sets whether the document entity is standalone.
923230592Sken     *
924230592Sken     * @param standalone True if document entity is standalone.
925230592Sken     */
926230592Sken    public void setStandalone(boolean standalone) {
927230592Sken        fStandalone = standalone;
928230592Sken    }
929230592Sken    // setStandalone(boolean)
930230592Sken
931230592Sken    /** Returns true if the document entity is standalone. */
932230592Sken    public boolean isStandalone() {
933230592Sken        return fStandalone;
934230592Sken    }  //isStandalone():boolean
935230592Sken
936230592Sken    public boolean isDeclaredEntity(String entityName) {
937230592Sken
938230592Sken        Entity entity = fEntities.get(entityName);
939230592Sken        return entity != null;
940230592Sken    }
941230592Sken
942230592Sken    public boolean isUnparsedEntity(String entityName) {
943230592Sken
944230592Sken        Entity entity = fEntities.get(entityName);
945230592Sken        if (entity == null) {
946230592Sken            return false;
947230592Sken        }
948230592Sken        return entity.isUnparsed();
949230592Sken    }
950230592Sken
951230592Sken
952230592Sken
953230592Sken    // this simply returns the fResourceIdentifier object;
954230592Sken    // this should only be used with caution by callers that
955230592Sken    // carefully manage the entity manager's behaviour, so that
956230592Sken    // this doesn't returning meaningless or misleading data.
957230592Sken    // @return  a reference to the current fResourceIdentifier object
958230592Sken    public XMLResourceIdentifier getCurrentResourceIdentifier() {
959230592Sken        return fResourceIdentifier;
960230592Sken    }
961230592Sken
962230592Sken    /**
963230592Sken     * Sets the entity handler. When an entity starts and ends, the
964230592Sken     * entity handler is notified of the change.
965230592Sken     *
966230592Sken     * @param entityHandler The new entity handler.
967230592Sken     */
968230592Sken
969230592Sken    public void setEntityHandler(com.sun.org.apache.xerces.internal.impl.XMLEntityHandler entityHandler) {
970230592Sken        fEntityHandler = entityHandler;
971230592Sken    } // setEntityHandler(XMLEntityHandler)
972230592Sken
973230592Sken    //this function returns StaxXMLInputSource
974230592Sken    public StaxXMLInputSource resolveEntityAsPerStax(XMLResourceIdentifier resourceIdentifier) throws java.io.IOException{
975230592Sken
976230592Sken        if(resourceIdentifier == null ) return null;
977230592Sken
978230592Sken        String publicId = resourceIdentifier.getPublicId();
979230592Sken        String literalSystemId = resourceIdentifier.getLiteralSystemId();
980230592Sken        String baseSystemId = resourceIdentifier.getBaseSystemId();
981230592Sken        String expandedSystemId = resourceIdentifier.getExpandedSystemId();
982230592Sken        // if no base systemId given, assume that it's relative
983230592Sken        // to the systemId of the current scanned entity
984230592Sken        // Sometimes the system id is not (properly) expanded.
985230592Sken        // We need to expand the system id if:
986230592Sken        // a. the expanded one was null; or
987230592Sken        // b. the base system id was null, but becomes non-null from the current entity.
988230592Sken        boolean needExpand = (expandedSystemId == null);
989230592Sken        // REVISIT:  why would the baseSystemId ever be null?  if we
990230592Sken        // didn't have to make this check we wouldn't have to reuse the
991230592Sken        // fXMLResourceIdentifier object...
992230592Sken        if (baseSystemId == null && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
993230592Sken            baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
994230592Sken            if (baseSystemId != null)
995230592Sken                needExpand = true;
996230592Sken        }
997230592Sken        if (needExpand)
998230592Sken            expandedSystemId = expandSystemId(literalSystemId, baseSystemId,false);
999230592Sken
1000230592Sken        // give the entity resolver a chance
1001230592Sken        StaxXMLInputSource staxInputSource = null;
1002230592Sken        XMLInputSource xmlInputSource = null;
1003230592Sken
1004230592Sken        XMLResourceIdentifierImpl ri = null;
1005230592Sken
1006230592Sken        if (resourceIdentifier instanceof XMLResourceIdentifierImpl) {
1007230592Sken            ri = (XMLResourceIdentifierImpl)resourceIdentifier;
1008230592Sken        } else {
1009230592Sken            fResourceIdentifier.clear();
1010230592Sken            ri = fResourceIdentifier;
1011230592Sken        }
1012230592Sken        ri.setValues(publicId, literalSystemId, baseSystemId, expandedSystemId);
1013230592Sken        if(DEBUG_RESOLVER){
1014230592Sken            System.out.println("BEFORE Calling resolveEntity") ;
1015230592Sken        }
1016230592Sken
1017230592Sken        fISCreatedByResolver = false;
1018230592Sken        //either of Stax or Xerces would be null
1019230592Sken        if(fStaxEntityResolver != null){
1020230592Sken            staxInputSource = fStaxEntityResolver.resolveEntity(ri);
1021230592Sken            if(staxInputSource != null) {
1022230592Sken                fISCreatedByResolver = true;
1023230592Sken            }
1024230592Sken        }
1025230592Sken
1026230592Sken        if(fEntityResolver != null){
1027230592Sken            xmlInputSource = fEntityResolver.resolveEntity(ri);
1028230592Sken            if(xmlInputSource != null) {
1029230592Sken                fISCreatedByResolver = true;
1030230592Sken            }
1031230592Sken        }
1032230592Sken
1033230592Sken        if(xmlInputSource != null){
1034230592Sken            //wrap this XMLInputSource to StaxInputSource
1035230592Sken            staxInputSource = new StaxXMLInputSource(xmlInputSource, fISCreatedByResolver);
1036230592Sken        }
1037230592Sken
1038230592Sken        if (staxInputSource == null && fUseCatalog) {
1039230592Sken            if (fCatalogFeatures == null) {
1040230592Sken                fCatalogFeatures = JdkXmlUtils.getCatalogFeatures(fDefer, fCatalogFile, fPrefer, fResolve);
1041230592Sken            }
1042230592Sken            fCatalogFile = fCatalogFeatures.get(Feature.FILES);
1043230592Sken            if (fCatalogFile != null) {
1044230592Sken                try {
1045230592Sken                    if (fCatalogResolver == null) {
1046230592Sken                        fCatalogResolver = CatalogManager.catalogResolver(fCatalogFeatures);
1047230592Sken                    }
1048230592Sken                    InputSource is = fCatalogResolver.resolveEntity(publicId, literalSystemId);
1049230592Sken                    if (is != null && !is.isEmpty()) {
1050230592Sken                        staxInputSource = new StaxXMLInputSource(new XMLInputSource(is, true), true);
1051230592Sken                    }
1052230592Sken                } catch (CatalogException e) {
1053230592Sken                    fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,"CatalogException",
1054230592Sken                    new Object[]{SecuritySupport.sanitizePath(fCatalogFile)},
1055230592Sken                    XMLErrorReporter.SEVERITY_FATAL_ERROR, e );
1056230592Sken                }
1057230592Sken            }
1058230592Sken        }
1059230592Sken
1060230592Sken        // do default resolution
1061230592Sken        //this works for both stax & Xerces, if staxInputSource is null,
1062230592Sken        //it means parser need to revert to default resolution
1063230592Sken        if (staxInputSource == null) {
1064230592Sken            // REVISIT: when systemId is null, I think we should return null.
1065230592Sken            //          is this the right solution? -SG
1066230592Sken            //if (systemId != null)
1067230592Sken            staxInputSource = new StaxXMLInputSource(
1068230592Sken                    new XMLInputSource(publicId, literalSystemId, baseSystemId, true), false);
1069230592Sken        }else if(staxInputSource.hasXMLStreamOrXMLEventReader()){
1070230592Sken            //Waiting for the clarification from EG. - nb
1071230592Sken        }
1072230592Sken
1073230592Sken        if (DEBUG_RESOLVER) {
1074230592Sken            System.err.println("XMLEntityManager.resolveEntity(" + publicId + ")");
1075230592Sken            System.err.println(" = " + xmlInputSource);
1076230592Sken        }
1077230592Sken
1078230592Sken        return staxInputSource;
1079230592Sken
1080230592Sken    }
1081230592Sken
1082230592Sken    /**
1083230592Sken     * Resolves the specified public and system identifiers. This
1084230592Sken     * method first attempts to resolve the entity based on the
1085230592Sken     * EntityResolver registered by the application. If no entity
1086230592Sken     * resolver is registered or if the registered entity handler
1087230592Sken     * is unable to resolve the entity, then default entity
1088230592Sken     * resolution will occur.
1089230592Sken     *
1090230592Sken     * @param publicId     The public identifier of the entity.
1091230592Sken     * @param systemId     The system identifier of the entity.
1092230592Sken     * @param baseSystemId The base system identifier of the entity.
1093230592Sken     *                     This is the system identifier of the current
1094230592Sken     *                     entity and is used to expand the system
1095230592Sken     *                     identifier when the system identifier is a
1096230592Sken     *                     relative URI.
1097230592Sken     *
1098230592Sken     * @return Returns an input source that wraps the resolved entity.
1099230592Sken     *         This method will never return null.
1100230592Sken     *
1101230592Sken     * @throws IOException  Thrown on i/o error.
1102230592Sken     * @throws XNIException Thrown by entity resolver to signal an error.
1103230592Sken     */
1104230592Sken    public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) throws IOException, XNIException {
1105230592Sken        if(resourceIdentifier == null ) return null;
1106230592Sken        String publicId = resourceIdentifier.getPublicId();
1107230592Sken        String literalSystemId = resourceIdentifier.getLiteralSystemId();
1108230592Sken        String baseSystemId = resourceIdentifier.getBaseSystemId();
1109230592Sken        String expandedSystemId = resourceIdentifier.getExpandedSystemId();
1110230592Sken
1111230592Sken        // if no base systemId given, assume that it's relative
1112230592Sken        // to the systemId of the current scanned entity
1113230592Sken        // Sometimes the system id is not (properly) expanded.
1114230592Sken        // We need to expand the system id if:
1115230592Sken        // a. the expanded one was null; or
1116230592Sken        // b. the base system id was null, but becomes non-null from the current entity.
1117230592Sken        boolean needExpand = (expandedSystemId == null);
1118230592Sken        // REVISIT:  why would the baseSystemId ever be null?  if we
1119230592Sken        // didn't have to make this check we wouldn't have to reuse the
1120230592Sken        // fXMLResourceIdentifier object...
1121230592Sken        if (baseSystemId == null && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
1122230592Sken            baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
1123230592Sken            if (baseSystemId != null)
1124230592Sken                needExpand = true;
1125230592Sken        }
1126230592Sken        if (needExpand)
1127230592Sken            expandedSystemId = expandSystemId(literalSystemId, baseSystemId,false);
1128230592Sken
1129230592Sken        // give the entity resolver a chance
1130230592Sken        XMLInputSource xmlInputSource = null;
1131230592Sken
1132230592Sken        if (fEntityResolver != null) {
1133230592Sken            resourceIdentifier.setBaseSystemId(baseSystemId);
1134230592Sken            resourceIdentifier.setExpandedSystemId(expandedSystemId);
1135230592Sken            xmlInputSource = fEntityResolver.resolveEntity(resourceIdentifier);
1136230592Sken        }
1137230592Sken
1138230592Sken        if (xmlInputSource == null && fUseCatalog) {
1139230592Sken            if (fCatalogFeatures == null) {
1140230592Sken                fCatalogFeatures = JdkXmlUtils.getCatalogFeatures(fDefer, fCatalogFile, fPrefer, fResolve);
1141230592Sken            }
1142230592Sken            fCatalogFile = fCatalogFeatures.get(Feature.FILES);
1143230592Sken            if (fCatalogFile != null) {
1144230592Sken                /*
1145230592Sken                 since the method can be called from various processors, both
1146230592Sken                 EntityResolver and URIResolver are used to attempt to find
1147230592Sken                 a match
1148230592Sken                */
1149230592Sken                InputSource is = null;
1150230592Sken                try {
1151230592Sken                    if (fCatalogResolver == null) {
1152230592Sken                        fCatalogResolver = CatalogManager.catalogResolver(fCatalogFeatures);
1153230592Sken                    }
1154230592Sken                    String pid = (publicId != null? publicId : resourceIdentifier.getNamespace());
1155230592Sken                    if (pid != null || literalSystemId != null) {
1156230592Sken                        is = fCatalogResolver.resolveEntity(pid, literalSystemId);
1157230592Sken                    }
1158230592Sken                } catch (CatalogException e) {}
1159230592Sken
1160230592Sken                if (is != null && !is.isEmpty()) {
1161230592Sken                    xmlInputSource = new XMLInputSource(is, true);
1162230592Sken                } else if (literalSystemId != null) {
1163230592Sken                    if (fCatalogResolver == null) {
1164230592Sken                        fCatalogResolver = CatalogManager.catalogResolver(fCatalogFeatures);
1165230592Sken                    }
1166230592Sken
1167230592Sken                    Source source = null;
1168230592Sken                    try {
1169230592Sken                        source = fCatalogResolver.resolve(literalSystemId, baseSystemId);
1170230592Sken                    } catch (CatalogException e) {
1171230592Sken                        throw new XNIException(e);
1172230592Sken                    }
1173230592Sken                    if (source != null && !source.isEmpty()) {
1174230592Sken                        xmlInputSource = new XMLInputSource(publicId, source.getSystemId(), baseSystemId, true);
1175230592Sken                    }
1176230592Sken                }
1177230592Sken            }
1178230592Sken        }
1179230592Sken
1180230592Sken        // do default resolution
1181230592Sken        // REVISIT: what's the correct behavior if the user provided an entity
1182230592Sken        // resolver (fEntityResolver != null), but resolveEntity doesn't return
1183230592Sken        // an input source (xmlInputSource == null)?
1184230592Sken        // do we do default resolution, or do we just return null? -SG
1185230592Sken        if (xmlInputSource == null) {
1186230592Sken            // REVISIT: when systemId is null, I think we should return null.
1187230592Sken            //          is this the right solution? -SG
1188230592Sken            //if (systemId != null)
1189230592Sken            xmlInputSource = new XMLInputSource(publicId, literalSystemId, baseSystemId, false);
1190230592Sken        }
1191230592Sken
1192230592Sken        if (DEBUG_RESOLVER) {
1193230592Sken            System.err.println("XMLEntityManager.resolveEntity(" + publicId + ")");
1194230592Sken            System.err.println(" = " + xmlInputSource);
1195230592Sken        }
1196230592Sken
1197230592Sken        return xmlInputSource;
1198230592Sken
1199230592Sken    } // resolveEntity(XMLResourceIdentifier):XMLInputSource
1200230592Sken
1201230592Sken    /**
1202230592Sken     * Starts a named entity.
1203230592Sken     *
1204230592Sken     * @param isGE flag to indicate whether the entity is a General Entity
1205230592Sken     * @param entityName The name of the entity to start.
1206230592Sken     * @param literal    True if this entity is started within a literal
1207230592Sken     *                   value.
1208230592Sken     *
1209230592Sken     * @throws IOException  Thrown on i/o error.
1210230592Sken     * @throws XNIException Thrown by entity handler to signal an error.
1211230592Sken     */
1212230592Sken    public void startEntity(boolean isGE, String entityName, boolean literal)
1213230592Sken    throws IOException, XNIException {
1214230592Sken
1215230592Sken        // was entity declared?
1216230592Sken        Entity entity = fEntityStorage.getEntity(entityName);
1217230592Sken        if (entity == null) {
1218230592Sken            if (fEntityHandler != null) {
1219230592Sken                String encoding = null;
1220230592Sken                fResourceIdentifier.clear();
1221230592Sken                fEntityAugs.removeAllItems();
1222230592Sken                fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1223230592Sken                fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
1224230592Sken                fEntityAugs.removeAllItems();
1225230592Sken                fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1226230592Sken                fEntityHandler.endEntity(entityName, fEntityAugs);
1227230592Sken            }
1228230592Sken            return;
1229230592Sken        }
1230230592Sken
1231230592Sken        // should we skip external entities?
1232230592Sken        boolean external = entity.isExternal();
1233230592Sken        Entity.ExternalEntity externalEntity = null;
1234230592Sken        String extLitSysId = null, extBaseSysId = null, expandedSystemId = null;
1235230592Sken        if (external) {
1236230592Sken            externalEntity = (Entity.ExternalEntity)entity;
1237230592Sken            extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getLiteralSystemId() : null);
1238230592Sken            extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getBaseSystemId() : null);
1239230592Sken            expandedSystemId = expandSystemId(extLitSysId, extBaseSysId);
1240230592Sken            boolean unparsed = entity.isUnparsed();
1241230592Sken            boolean parameter = entityName.startsWith("%");
1242230592Sken            boolean general = !parameter;
1243230592Sken            if (unparsed || (general && !fExternalGeneralEntities) ||
1244230592Sken                    (parameter && !fExternalParameterEntities) ||
1245230592Sken                    !fSupportDTD || !fSupportExternalEntities) {
1246230592Sken
1247230592Sken                if (fEntityHandler != null) {
1248230592Sken                    fResourceIdentifier.clear();
1249230592Sken                    final String encoding = null;
1250230592Sken                    fResourceIdentifier.setValues(
1251230592Sken                            (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null),
1252230592Sken                            extLitSysId, extBaseSysId, expandedSystemId);
1253230592Sken                    fEntityAugs.removeAllItems();
1254230592Sken                    fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1255230592Sken                    fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
1256230592Sken                    fEntityAugs.removeAllItems();
1257230592Sken                    fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1258230592Sken                    fEntityHandler.endEntity(entityName, fEntityAugs);
1259230592Sken                }
1260230592Sken                return;
1261230592Sken            }
1262230592Sken        }
1263230592Sken
1264230592Sken        // is entity recursive?
1265230592Sken        int size = fEntityStack.size();
1266230592Sken        for (int i = size; i >= 0; i--) {
1267230592Sken            Entity activeEntity = i == size
1268230592Sken                    ? fCurrentEntity
1269230592Sken                    : fEntityStack.elementAt(i);
1270230592Sken            if (activeEntity.name == entityName) {
1271230592Sken                String path = entityName;
1272230592Sken                for (int j = i + 1; j < size; j++) {
1273230592Sken                    activeEntity = fEntityStack.elementAt(j);
1274230592Sken                    path = path + " -> " + activeEntity.name;
1275230592Sken                }
1276230592Sken                path = path + " -> " + fCurrentEntity.name;
1277230592Sken                path = path + " -> " + entityName;
1278230592Sken                fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
1279230592Sken                        "RecursiveReference",
1280230592Sken                        new Object[] { entityName, path },
1281230592Sken                        XMLErrorReporter.SEVERITY_FATAL_ERROR);
1282230592Sken
1283230592Sken                        if (fEntityHandler != null) {
1284230592Sken                            fResourceIdentifier.clear();
1285230592Sken                            final String encoding = null;
1286230592Sken                            if (external) {
1287230592Sken                                fResourceIdentifier.setValues(
1288230592Sken                                        (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null),
1289230592Sken                                        extLitSysId, extBaseSysId, expandedSystemId);
1290230592Sken                            }
1291230592Sken                            fEntityAugs.removeAllItems();
1292230592Sken                            fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1293230592Sken                            fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
1294230592Sken                            fEntityAugs.removeAllItems();
1295230592Sken                            fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1296230592Sken                            fEntityHandler.endEntity(entityName, fEntityAugs);
1297230592Sken                        }
1298230592Sken
1299230592Sken                        return;
1300230592Sken            }
1301230592Sken        }
1302230592Sken
1303230592Sken        // resolve external entity
1304230592Sken        StaxXMLInputSource staxInputSource = null;
1305230592Sken        XMLInputSource xmlInputSource = null ;
1306230592Sken
1307230592Sken        if (external) {
1308230592Sken            staxInputSource = resolveEntityAsPerStax(externalEntity.entityLocation);
1309230592Sken            /** xxx:  Waiting from the EG
1310230592Sken             * //simply return if there was entity resolver registered and application
1311230592Sken             * //returns either XMLStreamReader or XMLEventReader.
1312230592Sken             * if(staxInputSource.hasXMLStreamOrXMLEventReader()) return ;
1313230592Sken             */
1314230592Sken            xmlInputSource = staxInputSource.getXMLInputSource() ;
1315230592Sken            if (!fISCreatedByResolver) {
1316230592Sken                //let the not-LoadExternalDTD or not-SupportDTD process to handle the situation
1317230592Sken                if (fLoadExternalDTD) {
1318230592Sken                    String accessError = SecuritySupport.checkAccess(expandedSystemId, fAccessExternalDTD, Constants.ACCESS_EXTERNAL_ALL);
1319230592Sken                    if (accessError != null) {
1320230592Sken                        fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
1321230592Sken                                "AccessExternalEntity",
1322230592Sken                                new Object[] { SecuritySupport.sanitizePath(expandedSystemId), accessError },
1323230592Sken                                XMLErrorReporter.SEVERITY_FATAL_ERROR);
1324230592Sken                    }
1325230592Sken                }
1326230592Sken            }
1327230592Sken        }
1328230592Sken        // wrap internal entity
1329230592Sken        else {
1330230592Sken            Entity.InternalEntity internalEntity = (Entity.InternalEntity)entity;
1331230592Sken            Reader reader = new StringReader(internalEntity.text);
1332230592Sken            xmlInputSource = new XMLInputSource(null, null, null, reader, null);
1333230592Sken        }
1334230592Sken
1335230592Sken        // start the entity
1336230592Sken        startEntity(isGE, entityName, xmlInputSource, literal, external);
1337230592Sken
1338230592Sken    } // startEntity(String,boolean)
1339230592Sken
1340230592Sken    /**
1341230592Sken     * Starts the document entity. The document entity has the "[xml]"
1342230592Sken     * pseudo-name.
1343230592Sken     *
1344230592Sken     * @param xmlInputSource The input source of the document entity.
1345230592Sken     *
1346230592Sken     * @throws IOException  Thrown on i/o error.
1347230592Sken     * @throws XNIException Thrown by entity handler to signal an error.
1348230592Sken     */
1349230592Sken    public void startDocumentEntity(XMLInputSource xmlInputSource)
1350230592Sken    throws IOException, XNIException {
1351230592Sken        startEntity(false, XMLEntity, xmlInputSource, false, true);
1352230592Sken    } // startDocumentEntity(XMLInputSource)
1353230592Sken
1354230592Sken    //xxx these methods are not required.
1355230592Sken    /**
1356230592Sken     * Starts the DTD entity. The DTD entity has the "[dtd]"
1357230592Sken     * pseudo-name.
1358230592Sken     *
1359230592Sken     * @param xmlInputSource The input source of the DTD entity.
1360230592Sken     *
1361230592Sken     * @throws IOException  Thrown on i/o error.
1362230592Sken     * @throws XNIException Thrown by entity handler to signal an error.
1363230592Sken     */
1364230592Sken    public void startDTDEntity(XMLInputSource xmlInputSource)
1365230592Sken    throws IOException, XNIException {
1366230592Sken        startEntity(false, DTDEntity, xmlInputSource, false, true);
1367230592Sken    } // startDTDEntity(XMLInputSource)
1368230592Sken
1369230592Sken    // indicate start of external subset so that
1370230592Sken    // location of entity decls can be tracked
1371230592Sken    public void startExternalSubset() {
1372230592Sken        fInExternalSubset = true;
1373230592Sken    }
1374230592Sken
1375230592Sken    public void endExternalSubset() {
1376230592Sken        fInExternalSubset = false;
1377230592Sken    }
1378230592Sken
1379230592Sken    /**
1380230592Sken     * Starts an entity.
1381230592Sken     * <p>
1382230592Sken     * This method can be used to insert an application defined XML
1383230592Sken     * entity stream into the parsing stream.
1384230592Sken     *
1385230592Sken     * @param isGE flag to indicate whether the entity is a General Entity
1386230592Sken     * @param name           The name of the entity.
1387230592Sken     * @param xmlInputSource The input source of the entity.
1388230592Sken     * @param literal        True if this entity is started within a
1389230592Sken     *                       literal value.
1390230592Sken     * @param isExternal    whether this entity should be treated as an internal or external entity.
1391230592Sken     *
1392230592Sken     * @throws IOException  Thrown on i/o error.
1393230592Sken     * @throws XNIException Thrown by entity handler to signal an error.
1394230592Sken     */
1395230592Sken    public void startEntity(boolean isGE, String name,
1396230592Sken            XMLInputSource xmlInputSource,
1397230592Sken            boolean literal, boolean isExternal)
1398230592Sken            throws IOException, XNIException {
1399230592Sken
1400230592Sken        String encoding = setupCurrentEntity(isGE, name, xmlInputSource, literal, isExternal);
1401230592Sken
1402230592Sken        //when entity expansion limit is set by the Application, we need to
1403230592Sken        //check for the entity expansion limit set by the parser, if number of entity
1404230592Sken        //expansions exceeds the entity expansion limit, parser will throw fatal error.
1405230592Sken        // Note that this represents the nesting level of open entities.
1406230592Sken        fEntityExpansionCount++;
1407230592Sken        if(fLimitAnalyzer != null) {
1408230592Sken           fLimitAnalyzer.addValue(entityExpansionIndex, name, 1);
1409230592Sken        }
1410230592Sken        if( fSecurityManager != null && fSecurityManager.isOverLimit(entityExpansionIndex, fLimitAnalyzer)){
1411230592Sken            fSecurityManager.debugPrint(fLimitAnalyzer);
1412230592Sken            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,"EntityExpansionLimit",
1413230592Sken                    new Object[]{fSecurityManager.getLimitValueByIndex(entityExpansionIndex)},
1414230592Sken                                             XMLErrorReporter.SEVERITY_FATAL_ERROR );
1415230592Sken            // is there anything better to do than reset the counter?
1416230592Sken            // at least one can envision debugging applications where this might
1417230592Sken            // be useful...
1418230592Sken            fEntityExpansionCount = 0;
1419230592Sken        }
1420230592Sken
1421230592Sken        // call handler
1422230592Sken        if (fEntityHandler != null) {
1423230592Sken            fEntityHandler.startEntity(name, fResourceIdentifier, encoding, null);
1424230592Sken        }
1425230592Sken
1426230592Sken    } // startEntity(String,XMLInputSource)
1427230592Sken
1428230592Sken    /**
1429230592Sken     * Return the current entity being scanned. Current entity is SET using startEntity function.
1430230592Sken     * @return Entity.ScannedEntity
1431230592Sken     */
1432230592Sken
1433230592Sken    public Entity.ScannedEntity getCurrentEntity(){
1434230592Sken        return fCurrentEntity ;
1435230592Sken    }
1436230592Sken
1437230592Sken    /**
1438230592Sken     * Return the top level entity handled by this manager, or null
1439230592Sken     * if no entity was added.
1440230592Sken     */
1441230592Sken    public Entity.ScannedEntity getTopLevelEntity() {
1442230592Sken        return (Entity.ScannedEntity)
1443230592Sken            (fEntityStack.empty() ? null : fEntityStack.elementAt(0));
1444230592Sken    }
1445230592Sken
1446230592Sken
1447230592Sken    /**
1448230592Sken     * Close all opened InputStreams and Readers opened by this parser.
1449230592Sken     */
1450230592Sken    public void closeReaders() {
1451230592Sken        /** this call actually does nothing, readers are closed in the endEntity method
1452230592Sken         * through the current entity.
1453230592Sken         * The change seems to have happened during the jdk6 development with the
1454230592Sken         * addition of StAX
1455230592Sken        **/
1456230592Sken    }
1457230592Sken
1458230592Sken    public void endEntity() throws IOException, XNIException {
1459230592Sken
1460230592Sken        // call handler
1461230592Sken        if (DEBUG_BUFFER) {
1462230592Sken            System.out.print("(endEntity: ");
1463230592Sken            print();
1464230592Sken            System.out.println();
1465230592Sken        }
1466230592Sken        //pop the entity from the stack
1467230592Sken        Entity.ScannedEntity entity = fEntityStack.size() > 0 ? (Entity.ScannedEntity)fEntityStack.pop() : null ;
1468230592Sken
1469230592Sken        /** need to close the reader first since the program can end
1470230592Sken         *  prematurely (e.g. fEntityHandler.endEntity may throw exception)
1471230592Sken         *  leaving the reader open
1472230592Sken         */
1473230592Sken        //close the reader
1474230592Sken        if(fCurrentEntity != null){
1475230592Sken            //close the reader
1476230592Sken            try{
1477230592Sken                if (fLimitAnalyzer != null) {
1478230592Sken                    fLimitAnalyzer.endEntity(XMLSecurityManager.Limit.GENERAL_ENTITY_SIZE_LIMIT, fCurrentEntity.name);
1479230592Sken                    if (fCurrentEntity.name.equals("[xml]")) {
1480230592Sken                        fSecurityManager.debugPrint(fLimitAnalyzer);
1481230592Sken                    }
1482230592Sken                }
1483230592Sken                fCurrentEntity.close();
1484230592Sken            }catch(IOException ex){
1485230592Sken                throw new XNIException(ex);
1486230592Sken            }
1487230592Sken        }
1488230592Sken
1489230592Sken        if (fEntityHandler != null) {
1490230592Sken            //so this is the last opened entity, signal it to current fEntityHandler using Augmentation
1491230592Sken            if(entity == null){
1492230592Sken                fEntityAugs.removeAllItems();
1493230592Sken                fEntityAugs.putItem(Constants.LAST_ENTITY, Boolean.TRUE);
1494230592Sken                fEntityHandler.endEntity(fCurrentEntity.name, fEntityAugs);
1495230592Sken                fEntityAugs.removeAllItems();
1496230592Sken            }else{
1497230592Sken                fEntityHandler.endEntity(fCurrentEntity.name, null);
1498230592Sken            }
1499230592Sken        }
1500230592Sken        //check if it is a document entity
1501230592Sken        boolean documentEntity = fCurrentEntity.name == XMLEntity;
1502230592Sken
1503230592Sken        //set popped entity as current entity
1504230592Sken        fCurrentEntity = entity;
1505230592Sken        fEntityScanner.setCurrentEntity(fCurrentEntity);
1506230592Sken
1507230592Sken        //check if there are any entity left in the stack -- if there are
1508230592Sken        //no entries EOF has been reached.
1509230592Sken        // throw exception when it is the last entity but it is not a document entity
1510230592Sken
1511230592Sken        if(fCurrentEntity == null & !documentEntity){
1512230592Sken            throw new EOFException() ;
1513230592Sken        }
1514230592Sken
1515230592Sken        if (DEBUG_BUFFER) {
1516230592Sken            System.out.print(")endEntity: ");
1517230592Sken            print();
1518230592Sken            System.out.println();
1519230592Sken        }
1520230592Sken
1521230592Sken    } // endEntity()
1522230592Sken
1523230592Sken
1524230592Sken    //
1525230592Sken    // XMLComponent methods
1526230592Sken    //
1527230592Sken    public void reset(PropertyManager propertyManager){
1528230592Sken        // xerces properties
1529230592Sken        fSymbolTable = (SymbolTable)propertyManager.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY);
1530230592Sken        fErrorReporter = (XMLErrorReporter)propertyManager.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY);
1531230592Sken        try {
1532230592Sken            fStaxEntityResolver = (StaxEntityResolverWrapper)propertyManager.getProperty(STAX_ENTITY_RESOLVER);
1533230592Sken        } catch (XMLConfigurationException e) {
1534230592Sken            fStaxEntityResolver = null;
1535230592Sken        }
1536230592Sken
1537230592Sken        fSupportDTD = ((Boolean)propertyManager.getProperty(XMLInputFactory.SUPPORT_DTD));
1538230592Sken        fReplaceEntityReferences = ((Boolean)propertyManager.getProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES));
1539230592Sken        fSupportExternalEntities = ((Boolean)propertyManager.getProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES));
1540230592Sken
1541230592Sken        // Zephyr feature ignore-external-dtd is the opposite of Xerces' load-external-dtd
1542230592Sken        fLoadExternalDTD = !((Boolean)propertyManager.getProperty(Constants.ZEPHYR_PROPERTY_PREFIX + Constants.IGNORE_EXTERNAL_DTD));
1543230592Sken
1544230592Sken        //Use Catalog
1545230592Sken        fUseCatalog = (Boolean)propertyManager.getProperty(XMLConstants.USE_CATALOG);
1546230592Sken        fCatalogFile = (String)propertyManager.getProperty(JdkXmlUtils.CATALOG_FILES);
1547230592Sken        fDefer = (String)propertyManager.getProperty(JdkXmlUtils.CATALOG_DEFER);
1548230592Sken        fPrefer = (String)propertyManager.getProperty(JdkXmlUtils.CATALOG_PREFER);
1549230592Sken        fResolve = (String)propertyManager.getProperty(JdkXmlUtils.CATALOG_RESOLVE);
1550230592Sken
1551230592Sken        // JAXP 1.5 feature
1552230592Sken        XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager) propertyManager.getProperty(XML_SECURITY_PROPERTY_MANAGER);
1553230592Sken        fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
1554230592Sken
1555230592Sken        fSecurityManager = (XMLSecurityManager)propertyManager.getProperty(SECURITY_MANAGER);
1556230592Sken
1557230592Sken        fLimitAnalyzer = new XMLLimitAnalyzer();
1558230592Sken        //reset fEntityStorage
1559230592Sken        fEntityStorage.reset(propertyManager);
1560230592Sken        //reset XMLEntityReaderImpl
1561230592Sken        fEntityScanner.reset(propertyManager);
1562230592Sken
1563230592Sken        // initialize state
1564230592Sken        //fStandalone = false;
1565230592Sken        fEntities.clear();
1566230592Sken        fEntityStack.removeAllElements();
1567230592Sken        fCurrentEntity = null;
1568230592Sken        fValidation = false;
1569230592Sken        fExternalGeneralEntities = true;
1570230592Sken        fExternalParameterEntities = true;
1571230592Sken        fAllowJavaEncodings = true ;
1572230592Sken    }
1573230592Sken
1574230592Sken    /**
1575230592Sken     * Resets the component. The component can query the component manager
1576230592Sken     * about any features and properties that affect the operation of the
1577230592Sken     * component.
1578230592Sken     *
1579230592Sken     * @param componentManager The component manager.
1580230592Sken     *
1581230592Sken     * @throws SAXException Thrown by component on initialization error.
1582230592Sken     *                      For example, if a feature or property is
1583230592Sken     *                      required for the operation of the component, the
1584230592Sken     *                      component manager may throw a
1585230592Sken     *                      SAXNotRecognizedException or a
1586230592Sken     *                      SAXNotSupportedException.
1587230592Sken     */
1588230592Sken    public void reset(XMLComponentManager componentManager)
1589230592Sken    throws XMLConfigurationException {
1590230592Sken
1591230592Sken        boolean parser_settings = componentManager.getFeature(PARSER_SETTINGS, true);
1592230592Sken
1593230592Sken        if (!parser_settings) {
1594230592Sken            // parser settings have not been changed
1595230592Sken            reset();
1596230592Sken            if(fEntityScanner != null){
1597230592Sken                fEntityScanner.reset(componentManager);
1598230592Sken            }
1599230592Sken            if(fEntityStorage != null){
1600230592Sken                fEntityStorage.reset(componentManager);
1601230592Sken            }
1602230592Sken            return;
1603230592Sken        }
1604230592Sken
1605230592Sken        // sax features
1606230592Sken        fValidation = componentManager.getFeature(VALIDATION, false);
1607230592Sken        fExternalGeneralEntities = componentManager.getFeature(EXTERNAL_GENERAL_ENTITIES, true);
1608230592Sken        fExternalParameterEntities = componentManager.getFeature(EXTERNAL_PARAMETER_ENTITIES, true);
1609230592Sken
1610230592Sken        // xerces features
1611230592Sken        fAllowJavaEncodings = componentManager.getFeature(ALLOW_JAVA_ENCODINGS, false);
1612230592Sken        fWarnDuplicateEntityDef = componentManager.getFeature(WARN_ON_DUPLICATE_ENTITYDEF, false);
1613230592Sken        fStrictURI = componentManager.getFeature(STANDARD_URI_CONFORMANT, false);
1614230592Sken        fLoadExternalDTD = componentManager.getFeature(LOAD_EXTERNAL_DTD, true);
1615230592Sken
1616230592Sken        // xerces properties
1617230592Sken        fSymbolTable = (SymbolTable)componentManager.getProperty(SYMBOL_TABLE);
1618230592Sken        fErrorReporter = (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER);
1619230592Sken        fEntityResolver = (XMLEntityResolver)componentManager.getProperty(ENTITY_RESOLVER, null);
1620230592Sken        fStaxEntityResolver = (StaxEntityResolverWrapper)componentManager.getProperty(STAX_ENTITY_RESOLVER, null);
1621230592Sken        fValidationManager = (ValidationManager)componentManager.getProperty(VALIDATION_MANAGER, null);
1622230592Sken        fSecurityManager = (XMLSecurityManager)componentManager.getProperty(SECURITY_MANAGER, null);
1623230592Sken        entityExpansionIndex = fSecurityManager.getIndex(Constants.JDK_ENTITY_EXPANSION_LIMIT);
1624230592Sken
1625230592Sken        //StAX Property
1626230592Sken        fSupportDTD = true;
1627230592Sken        fReplaceEntityReferences = true;
1628230592Sken        fSupportExternalEntities = true;
1629230592Sken
1630230592Sken        // JAXP 1.5 feature
1631230592Sken        XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager) componentManager.getProperty(XML_SECURITY_PROPERTY_MANAGER, null);
1632230592Sken        if (spm == null) {
1633230592Sken            spm = new XMLSecurityPropertyManager();
1634230592Sken        }
1635230592Sken        fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
1636230592Sken
1637230592Sken        //Use Catalog
1638230592Sken        fUseCatalog = componentManager.getFeature(XMLConstants.USE_CATALOG, true);
1639230592Sken        fCatalogFile = (String)componentManager.getProperty(JdkXmlUtils.CATALOG_FILES);
1640230592Sken        fDefer = (String)componentManager.getProperty(JdkXmlUtils.CATALOG_DEFER);
1641230592Sken        fPrefer = (String)componentManager.getProperty(JdkXmlUtils.CATALOG_PREFER);
1642230592Sken        fResolve = (String)componentManager.getProperty(JdkXmlUtils.CATALOG_RESOLVE);
1643230592Sken
1644230592Sken        //reset general state
1645230592Sken        reset();
1646230592Sken
1647230592Sken        fEntityScanner.reset(componentManager);
1648230592Sken        fEntityStorage.reset(componentManager);
1649230592Sken
1650230592Sken    } // reset(XMLComponentManager)
1651230592Sken
1652230592Sken    // reset general state.  Should not be called other than by
1653230592Sken    // a class acting as a component manager but not
1654230592Sken    // implementing that interface for whatever reason.
1655230592Sken    public void reset() {
1656230592Sken        fLimitAnalyzer = new XMLLimitAnalyzer();
1657230592Sken        // initialize state
1658230592Sken        fStandalone = false;
1659230592Sken        fEntities.clear();
1660230592Sken        fEntityStack.removeAllElements();
1661230592Sken        fEntityExpansionCount = 0;
1662230592Sken
1663230592Sken        fCurrentEntity = null;
1664230592Sken        // reset scanner
1665230592Sken        if(fXML10EntityScanner != null){
1666230592Sken            fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
1667230592Sken        }
1668230592Sken        if(fXML11EntityScanner != null) {
1669230592Sken            fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter);
1670230592Sken        }
1671230592Sken
1672230592Sken        // DEBUG
1673230592Sken        if (DEBUG_ENTITIES) {
1674230592Sken            addInternalEntity("text", "Hello, World.");
1675230592Sken            addInternalEntity("empty-element", "<foo/>");
1676230592Sken            addInternalEntity("balanced-element", "<foo></foo>");
1677230592Sken            addInternalEntity("balanced-element-with-text", "<foo>Hello, World</foo>");
1678230592Sken            addInternalEntity("balanced-element-with-entity", "<foo>&text;</foo>");
1679230592Sken            addInternalEntity("unbalanced-entity", "<foo>");
1680230592Sken            addInternalEntity("recursive-entity", "<foo>&recursive-entity2;</foo>");
1681230592Sken            addInternalEntity("recursive-entity2", "<bar>&recursive-entity3;</bar>");
1682230592Sken            addInternalEntity("recursive-entity3", "<baz>&recursive-entity;</baz>");
1683230592Sken            try {
1684230592Sken                addExternalEntity("external-text", null, "external-text.ent", "test/external-text.xml");
1685230592Sken                addExternalEntity("external-balanced-element", null, "external-balanced-element.ent", "test/external-balanced-element.xml");
1686230592Sken                addExternalEntity("one", null, "ent/one.ent", "test/external-entity.xml");
1687230592Sken                addExternalEntity("two", null, "ent/two.ent", "test/ent/one.xml");
1688230592Sken            }
1689230592Sken            catch (IOException ex) {
1690230592Sken                // should never happen
1691230592Sken            }
1692230592Sken        }
1693230592Sken
1694230592Sken        fEntityHandler = null;
1695230592Sken
1696230592Sken        // reset scanner
1697230592Sken        //if(fEntityScanner!=null)
1698230592Sken          //  fEntityScanner.reset(fSymbolTable, this,fErrorReporter);
1699230592Sken
1700230592Sken    }
1701230592Sken    /**
1702230592Sken     * Returns a list of feature identifiers that are recognized by
1703230592Sken     * this component. This method may return null if no features
1704230592Sken     * are recognized by this component.
1705230592Sken     */
1706230592Sken    public String[] getRecognizedFeatures() {
1707230592Sken        return RECOGNIZED_FEATURES.clone();
1708230592Sken    } // getRecognizedFeatures():String[]
1709230592Sken
1710230592Sken    /**
1711230592Sken     * Sets the state of a feature. This method is called by the component
1712230592Sken     * manager any time after reset when a feature changes state.
1713230592Sken     * <p>
1714230592Sken     * <strong>Note:</strong> Components should silently ignore features
1715230592Sken     * that do not affect the operation of the component.
1716230592Sken     *
1717230592Sken     * @param featureId The feature identifier.
1718230592Sken     * @param state     The state of the feature.
1719230592Sken     *
1720230592Sken     * @throws SAXNotRecognizedException The component should not throw
1721230592Sken     *                                   this exception.
1722230592Sken     * @throws SAXNotSupportedException The component should not throw
1723230592Sken     *                                  this exception.
1724230592Sken     */
1725230592Sken    public void setFeature(String featureId, boolean state)
1726230592Sken    throws XMLConfigurationException {
1727230592Sken
1728230592Sken        // xerces features
1729230592Sken        if (featureId.startsWith(Constants.XERCES_FEATURE_PREFIX)) {
1730230592Sken            final int suffixLength = featureId.length() - Constants.XERCES_FEATURE_PREFIX.length();
1731230592Sken            if (suffixLength == Constants.ALLOW_JAVA_ENCODINGS_FEATURE.length() &&
1732230592Sken                featureId.endsWith(Constants.ALLOW_JAVA_ENCODINGS_FEATURE)) {
1733230592Sken                fAllowJavaEncodings = state;
1734230592Sken            }
1735230592Sken            if (suffixLength == Constants.LOAD_EXTERNAL_DTD_FEATURE.length() &&
1736230592Sken                featureId.endsWith(Constants.LOAD_EXTERNAL_DTD_FEATURE)) {
1737230592Sken                fLoadExternalDTD = state;
1738230592Sken                return;
1739230592Sken            }
1740230592Sken        } else if (featureId.equals(XMLConstants.USE_CATALOG)) {
1741230592Sken            fUseCatalog = state;
1742230592Sken        }
1743230592Sken
1744230592Sken    } // setFeature(String,boolean)
1745230592Sken
1746230592Sken    /**
1747230592Sken     * Sets the value of a property. This method is called by the component
1748230592Sken     * manager any time after reset when a property changes value.
1749230592Sken     * <p>
1750230592Sken     * <strong>Note:</strong> Components should silently ignore properties
1751230592Sken     * that do not affect the operation of the component.
1752230592Sken     *
1753230592Sken     * @param propertyId The property identifier.
1754230592Sken     * @param value      The value of the property.
1755230592Sken     *
1756230592Sken     * @throws SAXNotRecognizedException The component should not throw
1757230592Sken     *                                   this exception.
1758230592Sken     * @throws SAXNotSupportedException The component should not throw
1759230592Sken     *                                  this exception.
1760230592Sken     */
1761230592Sken    public void setProperty(String propertyId, Object value){
1762230592Sken        // Xerces properties
1763230592Sken        if (propertyId.startsWith(Constants.XERCES_PROPERTY_PREFIX)) {
1764230592Sken            final int suffixLength = propertyId.length() - Constants.XERCES_PROPERTY_PREFIX.length();
1765230592Sken
1766230592Sken            if (suffixLength == Constants.SYMBOL_TABLE_PROPERTY.length() &&
1767230592Sken                propertyId.endsWith(Constants.SYMBOL_TABLE_PROPERTY)) {
1768230592Sken                fSymbolTable = (SymbolTable)value;
1769230592Sken                return;
1770230592Sken            }
1771230592Sken            if (suffixLength == Constants.ERROR_REPORTER_PROPERTY.length() &&
1772230592Sken                propertyId.endsWith(Constants.ERROR_REPORTER_PROPERTY)) {
1773230592Sken                fErrorReporter = (XMLErrorReporter)value;
1774230592Sken                return;
1775230592Sken            }
1776230592Sken            if (suffixLength == Constants.ENTITY_RESOLVER_PROPERTY.length() &&
1777230592Sken                propertyId.endsWith(Constants.ENTITY_RESOLVER_PROPERTY)) {
1778230592Sken                fEntityResolver = (XMLEntityResolver)value;
1779230592Sken                return;
1780230592Sken            }
1781230592Sken            if (suffixLength == Constants.BUFFER_SIZE_PROPERTY.length() &&
1782230592Sken                propertyId.endsWith(Constants.BUFFER_SIZE_PROPERTY)) {
1783230592Sken                Integer bufferSize = (Integer)value;
1784230592Sken                if (bufferSize != null &&
1785230592Sken                    bufferSize.intValue() > DEFAULT_XMLDECL_BUFFER_SIZE) {
1786230592Sken                    fBufferSize = bufferSize.intValue();
1787230592Sken                    fEntityScanner.setBufferSize(fBufferSize);
1788230592Sken                    fBufferPool.setExternalBufferSize(fBufferSize);
1789230592Sken                }
1790230592Sken            }
1791230592Sken            if (suffixLength == Constants.SECURITY_MANAGER_PROPERTY.length() &&
1792230592Sken                propertyId.endsWith(Constants.SECURITY_MANAGER_PROPERTY)) {
1793230592Sken                fSecurityManager = (XMLSecurityManager)value;
1794230592Sken            }
1795230592Sken        }
1796230592Sken
1797230592Sken        //JAXP 1.5 properties
1798230592Sken        if (propertyId.equals(XML_SECURITY_PROPERTY_MANAGER))
1799230592Sken        {
1800230592Sken            XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager)value;
1801230592Sken            fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
1802230592Sken            return;
1803230592Sken        }
1804230592Sken
1805230592Sken        //Catalog properties
1806230592Sken        if (propertyId.equals(JdkXmlUtils.CATALOG_FILES)) {
1807230592Sken            fCatalogFile = (String)value;
1808230592Sken        } else if (propertyId.equals(JdkXmlUtils.CATALOG_DEFER)) {
1809230592Sken            fDefer = (String)value;
1810230592Sken        } else if (propertyId.equals(JdkXmlUtils.CATALOG_PREFER)) {
1811230592Sken            fPrefer = (String)value;
1812230592Sken        } else if (propertyId.equals(JdkXmlUtils.CATALOG_RESOLVE)) {
1813230592Sken            fResolve = (String)value;
1814230592Sken        }
1815230592Sken    }
1816230592Sken
1817230592Sken    public void setLimitAnalyzer(XMLLimitAnalyzer fLimitAnalyzer) {
1818230592Sken        this.fLimitAnalyzer = fLimitAnalyzer;
1819230592Sken    }
1820230592Sken
1821230592Sken    /**
1822230592Sken     * Returns a list of property identifiers that are recognized by
1823230592Sken     * this component. This method may return null if no properties
1824230592Sken     * are recognized by this component.
1825230592Sken     */
1826230592Sken    public String[] getRecognizedProperties() {
1827230592Sken        return RECOGNIZED_PROPERTIES.clone();
1828230592Sken    } // getRecognizedProperties():String[]
1829230592Sken    /**
1830230592Sken     * Returns the default state for a feature, or null if this
1831230592Sken     * component does not want to report a default value for this
1832230592Sken     * feature.
1833230592Sken     *
1834230592Sken     * @param featureId The feature identifier.
1835230592Sken     *
1836230592Sken     * @since Xerces 2.2.0
1837230592Sken     */
1838230592Sken    public Boolean getFeatureDefault(String featureId) {
1839230592Sken        for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
1840230592Sken            if (RECOGNIZED_FEATURES[i].equals(featureId)) {
1841230592Sken                return FEATURE_DEFAULTS[i];
1842230592Sken            }
1843230592Sken        }
1844230592Sken        return null;
1845230592Sken    } // getFeatureDefault(String):Boolean
1846230592Sken
1847230592Sken    /**
1848230592Sken     * Returns the default state for a property, or null if this
1849230592Sken     * component does not want to report a default value for this
1850230592Sken     * property.
1851230592Sken     *
1852230592Sken     * @param propertyId The property identifier.
1853230592Sken     *
1854230592Sken     * @since Xerces 2.2.0
1855230592Sken     */
1856230592Sken    public Object getPropertyDefault(String propertyId) {
1857230592Sken        for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
1858230592Sken            if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
1859230592Sken                return PROPERTY_DEFAULTS[i];
1860230592Sken            }
1861230592Sken        }
1862230592Sken        return null;
1863230592Sken    } // getPropertyDefault(String):Object
1864230592Sken
1865230592Sken    //
1866230592Sken    // Public static methods
1867230592Sken    //
1868230592Sken
1869230592Sken    /**
1870230592Sken     * Expands a system id and returns the system id as a URI, if
1871230592Sken     * it can be expanded. A return value of null means that the
1872230592Sken     * identifier is already expanded. An exception thrown
1873230592Sken     * indicates a failure to expand the id.
1874230592Sken     *
1875230592Sken     * @param systemId The systemId to be expanded.
1876230592Sken     *
1877230592Sken     * @return Returns the URI string representing the expanded system
1878230592Sken     *         identifier. A null value indicates that the given
1879230592Sken     *         system identifier is already expanded.
1880230592Sken     *
1881230592Sken     */
1882230592Sken    public static String expandSystemId(String systemId) {
1883230592Sken        return expandSystemId(systemId, null);
1884230592Sken    } // expandSystemId(String):String
1885230592Sken
1886230592Sken    //
1887230592Sken    // Public static methods
1888230592Sken    //
1889230592Sken
1890230592Sken    // current value of the "user.dir" property
1891230592Sken    private static String gUserDir;
1892230592Sken    // cached URI object for the current value of the escaped "user.dir" property stored as a URI
1893230592Sken    private static URI gUserDirURI;
1894230592Sken    // which ASCII characters need to be escaped
1895230592Sken    private static boolean gNeedEscaping[] = new boolean[128];
1896230592Sken    // the first hex character if a character needs to be escaped
1897230592Sken    private static char gAfterEscaping1[] = new char[128];
1898230592Sken    // the second hex character if a character needs to be escaped
1899230592Sken    private static char gAfterEscaping2[] = new char[128];
1900230592Sken    private static char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7',
1901230592Sken                                     '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
1902230592Sken    // initialize the above 3 arrays
1903230592Sken    static {
1904230592Sken        for (int i = 0; i <= 0x1f; i++) {
1905230592Sken            gNeedEscaping[i] = true;
1906230592Sken            gAfterEscaping1[i] = gHexChs[i >> 4];
1907230592Sken            gAfterEscaping2[i] = gHexChs[i & 0xf];
1908230592Sken        }
1909230592Sken        gNeedEscaping[0x7f] = true;
1910230592Sken        gAfterEscaping1[0x7f] = '7';
1911230592Sken        gAfterEscaping2[0x7f] = 'F';
1912230592Sken        char[] escChs = {' ', '<', '>', '#', '%', '"', '{', '}',
1913230592Sken                         '|', '\\', '^', '~', '[', ']', '`'};
1914230592Sken        int len = escChs.length;
1915230592Sken        char ch;
1916230592Sken        for (int i = 0; i < len; i++) {
1917230592Sken            ch = escChs[i];
1918230592Sken            gNeedEscaping[ch] = true;
1919230592Sken            gAfterEscaping1[ch] = gHexChs[ch >> 4];
1920230592Sken            gAfterEscaping2[ch] = gHexChs[ch & 0xf];
1921230592Sken        }
1922230592Sken    }
1923230592Sken
1924230592Sken    // To escape the "user.dir" system property, by using %HH to represent
1925230592Sken    // special ASCII characters: 0x00~0x1F, 0x7F, ' ', '<', '>', '#', '%'
1926230592Sken    // and '"'. It's a static method, so needs to be synchronized.
1927230592Sken    // this method looks heavy, but since the system property isn't expected
1928230592Sken    // to change often, so in most cases, we only need to return the URI
1929230592Sken    // that was escaped before.
1930230592Sken    // According to the URI spec, non-ASCII characters (whose value >= 128)
1931230592Sken    // need to be escaped too.
1932230592Sken    // REVISIT: don't know how to escape non-ASCII characters, especially
1933230592Sken    // which encoding to use. Leave them for now.
1934230592Sken    private static synchronized URI getUserDir() throws URI.MalformedURIException {
1935230592Sken        // get the user.dir property
1936230592Sken        String userDir = "";
1937230592Sken        try {
1938230592Sken            userDir = SecuritySupport.getSystemProperty("user.dir");
1939230592Sken        }
1940230592Sken        catch (SecurityException se) {
1941230592Sken        }
1942230592Sken
1943230592Sken        // return empty string if property value is empty string.
1944230592Sken        if (userDir.length() == 0)
1945230592Sken            return new URI("file", "", "", null, null);
1946230592Sken        // compute the new escaped value if the new property value doesn't
1947230592Sken        // match the previous one
1948230592Sken        if (gUserDirURI != null && userDir.equals(gUserDir)) {
1949230592Sken            return gUserDirURI;
1950230592Sken        }
1951230592Sken
1952230592Sken        // record the new value as the global property value
1953230592Sken        gUserDir = userDir;
1954230592Sken
1955230592Sken        char separator = java.io.File.separatorChar;
1956230592Sken        userDir = userDir.replace(separator, '/');
1957230592Sken
1958230592Sken        int len = userDir.length(), ch;
1959230592Sken        StringBuilder buffer = new StringBuilder(len*3);
1960230592Sken        // change C:/blah to /C:/blah
1961230592Sken        if (len >= 2 && userDir.charAt(1) == ':') {
1962230592Sken            ch = Character.toUpperCase(userDir.charAt(0));
1963230592Sken            if (ch >= 'A' && ch <= 'Z') {
1964230592Sken                buffer.append('/');
1965230592Sken            }
1966230592Sken        }
1967230592Sken
1968230592Sken        // for each character in the path
1969230592Sken        int i = 0;
1970230592Sken        for (; i < len; i++) {
1971230592Sken            ch = userDir.charAt(i);
1972230592Sken            // if it's not an ASCII character, break here, and use UTF-8 encoding
1973230592Sken            if (ch >= 128)
1974230592Sken                break;
1975230592Sken            if (gNeedEscaping[ch]) {
1976230592Sken                buffer.append('%');
1977230592Sken                buffer.append(gAfterEscaping1[ch]);
1978230592Sken                buffer.append(gAfterEscaping2[ch]);
1979230592Sken                // record the fact that it's escaped
1980230592Sken            }
1981230592Sken            else {
1982230592Sken                buffer.append((char)ch);
1983230592Sken            }
1984230592Sken        }
1985230592Sken
1986230592Sken        // we saw some non-ascii character
1987230592Sken        if (i < len) {
1988230592Sken            // get UTF-8 bytes for the remaining sub-string
1989230592Sken            byte[] bytes = null;
1990230592Sken            byte b;
1991230592Sken            try {
1992230592Sken                bytes = userDir.substring(i).getBytes("UTF-8");
1993230592Sken            } catch (java.io.UnsupportedEncodingException e) {
1994230592Sken                // should never happen
1995230592Sken                return new URI("file", "", userDir, null, null);
1996230592Sken            }
1997230592Sken            len = bytes.length;
1998230592Sken
1999230592Sken            // for each byte
2000230592Sken            for (i = 0; i < len; i++) {
2001230592Sken                b = bytes[i];
2002230592Sken                // for non-ascii character: make it positive, then escape
2003230592Sken                if (b < 0) {
2004230592Sken                    ch = b + 256;
2005230592Sken                    buffer.append('%');
2006230592Sken                    buffer.append(gHexChs[ch >> 4]);
2007230592Sken                    buffer.append(gHexChs[ch & 0xf]);
2008230592Sken                }
2009230592Sken                else if (gNeedEscaping[b]) {
2010230592Sken                    buffer.append('%');
2011230592Sken                    buffer.append(gAfterEscaping1[b]);
2012230592Sken                    buffer.append(gAfterEscaping2[b]);
2013230592Sken                }
2014230592Sken                else {
2015230592Sken                    buffer.append((char)b);
2016230592Sken                }
2017230592Sken            }
2018230592Sken        }
2019230592Sken
2020230592Sken        // change blah/blah to blah/blah/
2021230592Sken        if (!userDir.endsWith("/"))
2022230592Sken            buffer.append('/');
2023230592Sken
2024230592Sken        gUserDirURI = new URI("file", "", buffer.toString(), null, null);
2025230592Sken
2026230592Sken        return gUserDirURI;
2027230592Sken    }
2028230592Sken
2029230592Sken    public static OutputStream createOutputStream(String uri) throws IOException {
2030230592Sken        // URI was specified. Handle relative URIs.
2031230592Sken        final String expanded = XMLEntityManager.expandSystemId(uri, null, true);
2032230592Sken        final URL url = new URL(expanded != null ? expanded : uri);
2033230592Sken        OutputStream out = null;
2034230592Sken        String protocol = url.getProtocol();
2035230592Sken        String host = url.getHost();
2036230592Sken        // Use FileOutputStream if this URI is for a local file.
2037230592Sken        if (protocol.equals("file")
2038230592Sken                && (host == null || host.length() == 0 || host.equals("localhost"))) {
2039230592Sken            File file = new File(getPathWithoutEscapes(url.getPath()));
2040230592Sken            if (!file.exists()) {
2041230592Sken                File parent = file.getParentFile();
2042230592Sken                if (parent != null && !parent.exists()) {
2043230592Sken                    parent.mkdirs();
2044230592Sken                }
2045230592Sken            }
2046230592Sken            out = new FileOutputStream(file);
2047213702Smdf        }
2048212420Sken        // Try to write to some other kind of URI. Some protocols
2049212420Sken        // won't support this, though HTTP should work.
2050212420Sken        else {
2051212420Sken            URLConnection urlCon = url.openConnection();
2052212420Sken            urlCon.setDoInput(false);
2053212420Sken            urlCon.setDoOutput(true);
2054230592Sken            urlCon.setUseCaches(false); // Enable tunneling.
2055212420Sken            if (urlCon instanceof HttpURLConnection) {
2056212420Sken                // The DOM L3 REC says if we are writing to an HTTP URI
2057212420Sken                // it is to be done with an HTTP PUT.
2058212420Sken                HttpURLConnection httpCon = (HttpURLConnection) urlCon;
2059212420Sken                httpCon.setRequestMethod("PUT");
2060212420Sken            }
2061212420Sken            out = urlCon.getOutputStream();
2062212420Sken        }
2063212420Sken        return out;
2064212420Sken    }
2065212420Sken
2066212420Sken    private static String getPathWithoutEscapes(String origPath) {
2067212420Sken        if (origPath != null && origPath.length() != 0 && origPath.indexOf('%') != -1) {
2068212420Sken            // Locate the escape characters
2069212420Sken            StringTokenizer tokenizer = new StringTokenizer(origPath, "%");
2070212420Sken            StringBuilder result = new StringBuilder(origPath.length());
2071212420Sken            int size = tokenizer.countTokens();
2072212420Sken            result.append(tokenizer.nextToken());
2073212420Sken            for(int i = 1; i < size; ++i) {
2074212420Sken                String token = tokenizer.nextToken();
2075212420Sken                // Decode the 2 digit hexadecimal number following % in '%nn'
2076212420Sken                result.append((char)Integer.valueOf(token.substring(0, 2), 16).intValue());
2077212420Sken                result.append(token.substring(2));
2078212420Sken            }
2079212420Sken            return result.toString();
2080212420Sken        }
2081212420Sken        return origPath;
2082212420Sken    }
2083212420Sken
2084212420Sken    /**
2085212420Sken     * Absolutizes a URI using the current value
2086212420Sken     * of the "user.dir" property as the base URI. If
2087212420Sken     * the URI is already absolute, this is a no-op.
2088212420Sken     *
2089212420Sken     * @param uri the URI to absolutize
2090212420Sken     */
2091212420Sken    public static void absolutizeAgainstUserDir(URI uri)
2092212420Sken        throws URI.MalformedURIException {
2093212420Sken        uri.absolutize(getUserDir());
2094212420Sken    }
2095212420Sken
2096212420Sken    /**
2097212420Sken     * Expands a system id and returns the system id as a URI, if
2098212420Sken     * it can be expanded. A return value of null means that the
2099212420Sken     * identifier is already expanded. An exception thrown
2100212420Sken     * indicates a failure to expand the id.
2101212420Sken     *
2102212420Sken     * @param systemId The systemId to be expanded.
2103212420Sken     *
2104212420Sken     * @return Returns the URI string representing the expanded system
2105212420Sken     *         identifier. A null value indicates that the given
2106212420Sken     *         system identifier is already expanded.
2107212420Sken     *
2108212420Sken     */
2109212420Sken    public static String expandSystemId(String systemId, String baseSystemId) {
2110230592Sken
2111230592Sken        // check for bad parameters id
2112230592Sken        if (systemId == null || systemId.length() == 0) {
2113230592Sken            return systemId;
2114230592Sken        }
2115230592Sken        // if id already expanded, return
2116230592Sken        try {
2117230592Sken            URI uri = new URI(systemId);
2118230592Sken            if (uri != null) {
2119230592Sken                return systemId;
2120230592Sken            }
2121230592Sken        } catch (URI.MalformedURIException e) {
2122230592Sken            // continue on...
2123230592Sken        }
2124230592Sken        // normalize id
2125230592Sken        String id = fixURI(systemId);
2126230592Sken
2127230592Sken        // normalize base
2128230592Sken        URI base = null;
2129230592Sken        URI uri = null;
2130230592Sken        try {
2131230592Sken            if (baseSystemId == null || baseSystemId.length() == 0 ||
2132230592Sken                    baseSystemId.equals(systemId)) {
2133230592Sken                String dir = getUserDir().toString();
2134230592Sken                base = new URI("file", "", dir, null, null);
2135230592Sken            } else {
2136230592Sken                try {
2137230592Sken                    base = new URI(fixURI(baseSystemId));
2138230592Sken                } catch (URI.MalformedURIException e) {
2139230592Sken                    if (baseSystemId.indexOf(':') != -1) {
2140230592Sken                        // for xml schemas we might have baseURI with
2141230592Sken                        // a specified drive
2142230592Sken                        base = new URI("file", "", fixURI(baseSystemId), null, null);
2143230592Sken                    } else {
2144230592Sken                        String dir = getUserDir().toString();
2145230592Sken                        dir = dir + fixURI(baseSystemId);
2146230592Sken                        base = new URI("file", "", dir, null, null);
2147230592Sken                    }
2148230592Sken                }
2149230592Sken            }
2150230592Sken            // expand id
2151230592Sken            uri = new URI(base, id);
2152230592Sken        } catch (Exception e) {
2153230592Sken            // let it go through
2154230592Sken
2155230592Sken        }
2156230592Sken
2157230592Sken        if (uri == null) {
2158230592Sken            return systemId;
2159230592Sken        }
2160230592Sken        return uri.toString();
2161230592Sken
2162230592Sken    } // expandSystemId(String,String):String
2163230592Sken
2164230592Sken    /**
2165230592Sken     * Expands a system id and returns the system id as a URI, if
2166230592Sken     * it can be expanded. A return value of null means that the
2167230592Sken     * identifier is already expanded. An exception thrown
2168230592Sken     * indicates a failure to expand the id.
2169230592Sken     *
2170230592Sken     * @param systemId The systemId to be expanded.
2171230592Sken     *
2172230592Sken     * @return Returns the URI string representing the expanded system
2173230592Sken     *         identifier. A null value indicates that the given
2174230592Sken     *         system identifier is already expanded.
2175230592Sken     *
2176230592Sken     */
2177230592Sken    public static String expandSystemId(String systemId, String baseSystemId,
2178230592Sken                                        boolean strict)
2179230592Sken            throws URI.MalformedURIException {
2180230592Sken
2181230592Sken        // check if there is a system id before
2182230592Sken        // trying to expand it.
2183230592Sken        if (systemId == null) {
2184230592Sken            return null;
2185230592Sken        }
2186230592Sken
2187230592Sken        // system id has to be a valid URI
2188230592Sken        if (strict) {
2189230592Sken            try {
2190230592Sken                // if it's already an absolute one, return it
2191230592Sken                new URI(systemId);
2192230592Sken                return systemId;
2193230592Sken            }
2194230592Sken            catch (URI.MalformedURIException ex) {
2195230592Sken            }
2196230592Sken            URI base = null;
2197230592Sken            // if there isn't a base uri, use the working directory
2198230592Sken            if (baseSystemId == null || baseSystemId.length() == 0) {
2199230592Sken                base = new URI("file", "", getUserDir().toString(), null, null);
2200230592Sken            }
2201230592Sken            // otherwise, use the base uri
2202212420Sken            else {
2203212420Sken                try {
2204212420Sken                    base = new URI(baseSystemId);
2205212420Sken                }
2206212420Sken                catch (URI.MalformedURIException e) {
2207212420Sken                    // assume "base" is also a relative uri
2208212420Sken                    String dir = getUserDir().toString();
2209212420Sken                    dir = dir + baseSystemId;
2210213702Smdf                    base = new URI("file", "", dir, null, null);
2211213702Smdf                }
2212212420Sken            }
2213213702Smdf            // absolutize the system id using the base
2214213702Smdf            URI uri = new URI(base, systemId);
2215213702Smdf            // return the string rep of the new uri (an absolute one)
2216213702Smdf            return uri.toString();
2217213702Smdf
2218213702Smdf            // if any exception is thrown, it'll get thrown to the caller.
2219213702Smdf        }
2220213702Smdf
2221213702Smdf        // Assume the URIs are well-formed. If it turns out they're not, try fixing them up.
2222213702Smdf        try {
2223213702Smdf             return expandSystemIdStrictOff(systemId, baseSystemId);
2224213702Smdf        }
2225213702Smdf        catch (URI.MalformedURIException e) {
2226213702Smdf            /** Xerces URI rejects unicode, try java.net.URI
2227213702Smdf             * this is not ideal solution, but it covers known cases which either
2228213702Smdf             * Xerces URI or java.net.URI can handle alone
2229213702Smdf             * will file bug against java.net.URI
2230213702Smdf             */
2231213702Smdf            try {
2232213702Smdf                return expandSystemIdStrictOff1(systemId, baseSystemId);
2233213702Smdf            } catch (URISyntaxException ex) {
2234213702Smdf                // continue on...
2235213702Smdf            }
2236213702Smdf        }
2237213702Smdf        // check for bad parameters id
2238213702Smdf        if (systemId.length() == 0) {
2239213702Smdf            return systemId;
2240213702Smdf        }
2241213702Smdf
2242213702Smdf        // normalize id
2243213702Smdf        String id = fixURI(systemId);
2244213702Smdf
2245213702Smdf        // normalize base
2246213702Smdf        URI base = null;
2247213702Smdf        URI uri = null;
2248213702Smdf        try {
2249213702Smdf            if (baseSystemId == null || baseSystemId.length() == 0 ||
2250213702Smdf                baseSystemId.equals(systemId)) {
2251213702Smdf                base = getUserDir();
2252213702Smdf            }
2253213702Smdf            else {
2254213702Smdf                try {
2255213702Smdf                    base = new URI(fixURI(baseSystemId).trim());
2256213702Smdf                }
2257213702Smdf                catch (URI.MalformedURIException e) {
2258213702Smdf                    if (baseSystemId.indexOf(':') != -1) {
2259213702Smdf                        // for xml schemas we might have baseURI with
2260213702Smdf                        // a specified drive
2261213702Smdf                        base = new URI("file", "", fixURI(baseSystemId).trim(), null, null);
2262213702Smdf                    }
2263213702Smdf                    else {
2264213702Smdf                        base = new URI(getUserDir(), fixURI(baseSystemId));
2265213702Smdf                    }
2266213702Smdf                }
2267213702Smdf             }
2268213702Smdf             // expand id
2269213702Smdf             uri = new URI(base, id.trim());
2270213702Smdf        }
2271213702Smdf        catch (Exception e) {
2272213702Smdf            // let it go through
2273213702Smdf
2274213702Smdf        }
2275213702Smdf
2276213702Smdf        if (uri == null) {
2277213702Smdf            return systemId;
2278213702Smdf        }
2279213702Smdf        return uri.toString();
2280213702Smdf
2281213702Smdf    } // expandSystemId(String,String,boolean):String
2282212420Sken
2283212420Sken    /**
2284212420Sken     * Helper method for expandSystemId(String,String,boolean):String
2285213702Smdf     */
2286213702Smdf    private static String expandSystemIdStrictOn(String systemId, String baseSystemId)
2287213702Smdf        throws URI.MalformedURIException {
2288213702Smdf
2289213702Smdf        URI systemURI = new URI(systemId, true);
2290213702Smdf        // If it's already an absolute one, return it
2291213702Smdf        if (systemURI.isAbsoluteURI()) {
2292213702Smdf            return systemId;
2293213702Smdf        }
2294213702Smdf
2295213702Smdf        // If there isn't a base URI, use the working directory
2296212420Sken        URI baseURI = null;
2297213702Smdf        if (baseSystemId == null || baseSystemId.length() == 0) {
2298212420Sken            baseURI = getUserDir();
2299213702Smdf        }
2300213702Smdf        else {
2301213702Smdf            baseURI = new URI(baseSystemId, true);
2302213702Smdf            if (!baseURI.isAbsoluteURI()) {
2303213702Smdf                // assume "base" is also a relative uri
2304213702Smdf                baseURI.absolutize(getUserDir());
2305213702Smdf            }
2306213702Smdf        }
2307213702Smdf
2308213702Smdf        // absolutize the system identifier using the base URI
2309212420Sken        systemURI.absolutize(baseURI);
2310213702Smdf
2311213702Smdf        // return the string rep of the new uri (an absolute one)
2312213702Smdf        return systemURI.toString();
2313213702Smdf
2314213702Smdf        // if any exception is thrown, it'll get thrown to the caller.
2315213702Smdf
2316213702Smdf    } // expandSystemIdStrictOn(String,String):String
2317213702Smdf
2318213702Smdf    /**
2319213702Smdf     * Helper method for expandSystemId(String,String,boolean):String
2320213702Smdf     */
2321213702Smdf    private static String expandSystemIdStrictOff(String systemId, String baseSystemId)
2322213702Smdf        throws URI.MalformedURIException {
2323213702Smdf
2324213702Smdf        URI systemURI = new URI(systemId, true);
2325213702Smdf        // If it's already an absolute one, return it
2326213702Smdf        if (systemURI.isAbsoluteURI()) {
2327213702Smdf            if (systemURI.getScheme().length() > 1) {
2328213702Smdf                return systemId;
2329213702Smdf            }
2330213702Smdf            /**
2331213702Smdf             * If the scheme's length is only one character,
2332213702Smdf             * it's likely that this was intended as a file
2333213702Smdf             * path. Fixing this up in expandSystemId to
2334213702Smdf             * maintain backwards compatibility.
2335213702Smdf             */
2336213702Smdf            throw new URI.MalformedURIException();
2337212420Sken        }
2338212420Sken
2339212420Sken        // If there isn't a base URI, use the working directory
2340212420Sken        URI baseURI = null;
2341213702Smdf        if (baseSystemId == null || baseSystemId.length() == 0) {
2342213702Smdf            baseURI = getUserDir();
2343213702Smdf        }
2344213702Smdf        else {
2345213702Smdf            baseURI = new URI(baseSystemId, true);
2346213702Smdf            if (!baseURI.isAbsoluteURI()) {
2347213702Smdf                // assume "base" is also a relative uri
2348213702Smdf                baseURI.absolutize(getUserDir());
2349213702Smdf            }
2350213702Smdf        }
2351213702Smdf
2352213702Smdf        // absolutize the system identifier using the base URI
2353213702Smdf        systemURI.absolutize(baseURI);
2354213702Smdf
2355213702Smdf        // return the string rep of the new uri (an absolute one)
2356213702Smdf        return systemURI.toString();
2357213702Smdf
2358213702Smdf        // if any exception is thrown, it'll get thrown to the caller.
2359213702Smdf
2360213702Smdf    } // expandSystemIdStrictOff(String,String):String
2361213702Smdf
2362213702Smdf    private static String expandSystemIdStrictOff1(String systemId, String baseSystemId)
2363213702Smdf        throws URISyntaxException, URI.MalformedURIException {
2364213702Smdf
2365213702Smdf            java.net.URI systemURI = new java.net.URI(systemId);
2366213702Smdf        // If it's already an absolute one, return it
2367213702Smdf        if (systemURI.isAbsolute()) {
2368213702Smdf            if (systemURI.getScheme().length() > 1) {
2369213702Smdf                return systemId;
2370213702Smdf            }
2371213702Smdf            /**
2372213702Smdf             * If the scheme's length is only one character,
2373213702Smdf             * it's likely that this was intended as a file
2374213702Smdf             * path. Fixing this up in expandSystemId to
2375213702Smdf             * maintain backwards compatibility.
2376213702Smdf             */
2377213702Smdf            throw new URISyntaxException(systemId, "the scheme's length is only one character");
2378213702Smdf        }
2379213702Smdf
2380213702Smdf        // If there isn't a base URI, use the working directory
2381213702Smdf        URI baseURI = null;
2382213702Smdf        if (baseSystemId == null || baseSystemId.length() == 0) {
2383213702Smdf            baseURI = getUserDir();
2384213702Smdf        }
2385213702Smdf        else {
2386213702Smdf            baseURI = new URI(baseSystemId, true);
2387213702Smdf            if (!baseURI.isAbsoluteURI()) {
2388213702Smdf                // assume "base" is also a relative uri
2389213702Smdf                baseURI.absolutize(getUserDir());
2390213702Smdf            }
2391212420Sken        }
2392213702Smdf
2393213702Smdf        // absolutize the system identifier using the base URI
2394213702Smdf//        systemURI.absolutize(baseURI);
2395213702Smdf        systemURI = (new java.net.URI(baseURI.toString())).resolve(systemURI);
2396213702Smdf
2397213702Smdf        // return the string rep of the new uri (an absolute one)
2398213702Smdf        return systemURI.toString();
2399213702Smdf
2400213702Smdf        // if any exception is thrown, it'll get thrown to the caller.
2401213702Smdf
2402213702Smdf    } // expandSystemIdStrictOff(String,String):String
2403213702Smdf
2404    //
2405    // Protected methods
2406    //
2407
2408
2409    /**
2410     * Returns the IANA encoding name that is auto-detected from
2411     * the bytes specified, with the endian-ness of that encoding where appropriate.
2412     *
2413     * @param b4    The first four bytes of the input.
2414     * @param count The number of bytes actually read.
2415     * @return a 2-element array:  the first element, an IANA-encoding string,
2416     *  the second element a Boolean which is true iff the document is big endian, false
2417     *  if it's little-endian, and null if the distinction isn't relevant.
2418     */
2419    protected Object[] getEncodingName(byte[] b4, int count) {
2420
2421        if (count < 2) {
2422            return defaultEncoding;
2423        }
2424
2425        // UTF-16, with BOM
2426        int b0 = b4[0] & 0xFF;
2427        int b1 = b4[1] & 0xFF;
2428        if (b0 == 0xFE && b1 == 0xFF) {
2429            // UTF-16, big-endian
2430            return new Object [] {"UTF-16BE", new Boolean(true)};
2431        }
2432        if (b0 == 0xFF && b1 == 0xFE) {
2433            // UTF-16, little-endian
2434            return new Object [] {"UTF-16LE", new Boolean(false)};
2435        }
2436
2437        // default to UTF-8 if we don't have enough bytes to make a
2438        // good determination of the encoding
2439        if (count < 3) {
2440            return defaultEncoding;
2441        }
2442
2443        // UTF-8 with a BOM
2444        int b2 = b4[2] & 0xFF;
2445        if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
2446            return defaultEncoding;
2447        }
2448
2449        // default to UTF-8 if we don't have enough bytes to make a
2450        // good determination of the encoding
2451        if (count < 4) {
2452            return defaultEncoding;
2453        }
2454
2455        // other encodings
2456        int b3 = b4[3] & 0xFF;
2457        if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) {
2458            // UCS-4, big endian (1234)
2459            return new Object [] {"ISO-10646-UCS-4", new Boolean(true)};
2460        }
2461        if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) {
2462            // UCS-4, little endian (4321)
2463            return new Object [] {"ISO-10646-UCS-4", new Boolean(false)};
2464        }
2465        if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) {
2466            // UCS-4, unusual octet order (2143)
2467            // REVISIT: What should this be?
2468            return new Object [] {"ISO-10646-UCS-4", null};
2469        }
2470        if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) {
2471            // UCS-4, unusual octect order (3412)
2472            // REVISIT: What should this be?
2473            return new Object [] {"ISO-10646-UCS-4", null};
2474        }
2475        if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
2476            // UTF-16, big-endian, no BOM
2477            // (or could turn out to be UCS-2...
2478            // REVISIT: What should this be?
2479            return new Object [] {"UTF-16BE", new Boolean(true)};
2480        }
2481        if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
2482            // UTF-16, little-endian, no BOM
2483            // (or could turn out to be UCS-2...
2484            return new Object [] {"UTF-16LE", new Boolean(false)};
2485        }
2486        if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) {
2487            // EBCDIC
2488            // a la xerces1, return CP037 instead of EBCDIC here
2489            return new Object [] {"CP037", null};
2490        }
2491
2492        return defaultEncoding;
2493
2494    } // getEncodingName(byte[],int):Object[]
2495
2496    /**
2497     * Creates a reader capable of reading the given input stream in
2498     * the specified encoding.
2499     *
2500     * @param inputStream  The input stream.
2501     * @param encoding     The encoding name that the input stream is
2502     *                     encoded using. If the user has specified that
2503     *                     Java encoding names are allowed, then the
2504     *                     encoding name may be a Java encoding name;
2505     *                     otherwise, it is an ianaEncoding name.
2506     * @param isBigEndian   For encodings (like uCS-4), whose names cannot
2507     *                      specify a byte order, this tells whether the order is bigEndian.  null menas
2508     *                      unknown or not relevant.
2509     *
2510     * @return Returns a reader.
2511     */
2512    protected Reader createReader(InputStream inputStream, String encoding, Boolean isBigEndian)
2513    throws IOException {
2514
2515        // normalize encoding name
2516        if (encoding == null) {
2517            encoding = "UTF-8";
2518        }
2519
2520        // try to use an optimized reader
2521        String ENCODING = encoding.toUpperCase(Locale.ENGLISH);
2522        if (ENCODING.equals("UTF-8")) {
2523            if (DEBUG_ENCODINGS) {
2524                System.out.println("$$$ creating UTF8Reader");
2525            }
2526            return new UTF8Reader(inputStream, fBufferSize, fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN), fErrorReporter.getLocale() );
2527        }
2528        if (ENCODING.equals("US-ASCII")) {
2529            if (DEBUG_ENCODINGS) {
2530                System.out.println("$$$ creating ASCIIReader");
2531            }
2532            return new ASCIIReader(inputStream, fBufferSize, fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN), fErrorReporter.getLocale());
2533        }
2534        if(ENCODING.equals("ISO-10646-UCS-4")) {
2535            if(isBigEndian != null) {
2536                boolean isBE = isBigEndian.booleanValue();
2537                if(isBE) {
2538                    return new UCSReader(inputStream, UCSReader.UCS4BE);
2539                } else {
2540                    return new UCSReader(inputStream, UCSReader.UCS4LE);
2541                }
2542            } else {
2543                fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
2544                        "EncodingByteOrderUnsupported",
2545                        new Object[] { encoding },
2546                        XMLErrorReporter.SEVERITY_FATAL_ERROR);
2547            }
2548        }
2549        if(ENCODING.equals("ISO-10646-UCS-2")) {
2550            if(isBigEndian != null) { // sould never happen with this encoding...
2551                boolean isBE = isBigEndian.booleanValue();
2552                if(isBE) {
2553                    return new UCSReader(inputStream, UCSReader.UCS2BE);
2554                } else {
2555                    return new UCSReader(inputStream, UCSReader.UCS2LE);
2556                }
2557            } else {
2558                fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
2559                        "EncodingByteOrderUnsupported",
2560                        new Object[] { encoding },
2561                        XMLErrorReporter.SEVERITY_FATAL_ERROR);
2562            }
2563        }
2564
2565        // check for valid name
2566        boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
2567        boolean validJava = XMLChar.isValidJavaEncoding(encoding);
2568        if (!validIANA || (fAllowJavaEncodings && !validJava)) {
2569            fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
2570                    "EncodingDeclInvalid",
2571                    new Object[] { encoding },
2572                    XMLErrorReporter.SEVERITY_FATAL_ERROR);
2573                    // NOTE: AndyH suggested that, on failure, we use ISO Latin 1
2574                    //       because every byte is a valid ISO Latin 1 character.
2575                    //       It may not translate correctly but if we failed on
2576                    //       the encoding anyway, then we're expecting the content
2577                    //       of the document to be bad. This will just prevent an
2578                    //       invalid UTF-8 sequence to be detected. This is only
2579                    //       important when continue-after-fatal-error is turned
2580                    //       on. -Ac
2581                    encoding = "ISO-8859-1";
2582        }
2583
2584        // try to use a Java reader
2585        String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING);
2586        if (javaEncoding == null) {
2587            if(fAllowJavaEncodings) {
2588                javaEncoding = encoding;
2589            } else {
2590                fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
2591                        "EncodingDeclInvalid",
2592                        new Object[] { encoding },
2593                        XMLErrorReporter.SEVERITY_FATAL_ERROR);
2594                        // see comment above.
2595                        javaEncoding = "ISO8859_1";
2596            }
2597        }
2598        if (DEBUG_ENCODINGS) {
2599            System.out.print("$$$ creating Java InputStreamReader: encoding="+javaEncoding);
2600            if (javaEncoding == encoding) {
2601                System.out.print(" (IANA encoding)");
2602            }
2603            System.out.println();
2604        }
2605        return new BufferedReader( new InputStreamReader(inputStream, javaEncoding));
2606
2607    } // createReader(InputStream,String, Boolean): Reader
2608
2609
2610    /**
2611     * Return the public identifier for the current document event.
2612     * <p>
2613     * The return value is the public identifier of the document
2614     * entity or of the external parsed entity in which the markup
2615     * triggering the event appears.
2616     *
2617     * @return A string containing the public identifier, or
2618     *         null if none is available.
2619     */
2620    public String getPublicId() {
2621        return (fCurrentEntity != null && fCurrentEntity.entityLocation != null) ? fCurrentEntity.entityLocation.getPublicId() : null;
2622    } // getPublicId():String
2623
2624    /**
2625     * Return the expanded system identifier for the current document event.
2626     * <p>
2627     * The return value is the expanded system identifier of the document
2628     * entity or of the external parsed entity in which the markup
2629     * triggering the event appears.
2630     * <p>
2631     * If the system identifier is a URL, the parser must resolve it
2632     * fully before passing it to the application.
2633     *
2634     * @return A string containing the expanded system identifier, or null
2635     *         if none is available.
2636     */
2637    public String getExpandedSystemId() {
2638        if (fCurrentEntity != null) {
2639            if (fCurrentEntity.entityLocation != null &&
2640                    fCurrentEntity.entityLocation.getExpandedSystemId() != null ) {
2641                return fCurrentEntity.entityLocation.getExpandedSystemId();
2642            } else {
2643                // search for the first external entity on the stack
2644                int size = fEntityStack.size();
2645                for (int i = size - 1; i >= 0 ; i--) {
2646                    Entity.ScannedEntity externalEntity =
2647                            (Entity.ScannedEntity)fEntityStack.elementAt(i);
2648
2649                    if (externalEntity.entityLocation != null &&
2650                            externalEntity.entityLocation.getExpandedSystemId() != null) {
2651                        return externalEntity.entityLocation.getExpandedSystemId();
2652                    }
2653                }
2654            }
2655        }
2656        return null;
2657    } // getExpandedSystemId():String
2658
2659    /**
2660     * Return the literal system identifier for the current document event.
2661     * <p>
2662     * The return value is the literal system identifier of the document
2663     * entity or of the external parsed entity in which the markup
2664     * triggering the event appears.
2665     * <p>
2666     * @return A string containing the literal system identifier, or null
2667     *         if none is available.
2668     */
2669    public String getLiteralSystemId() {
2670        if (fCurrentEntity != null) {
2671            if (fCurrentEntity.entityLocation != null &&
2672                    fCurrentEntity.entityLocation.getLiteralSystemId() != null ) {
2673                return fCurrentEntity.entityLocation.getLiteralSystemId();
2674            } else {
2675                // search for the first external entity on the stack
2676                int size = fEntityStack.size();
2677                for (int i = size - 1; i >= 0 ; i--) {
2678                    Entity.ScannedEntity externalEntity =
2679                            (Entity.ScannedEntity)fEntityStack.elementAt(i);
2680
2681                    if (externalEntity.entityLocation != null &&
2682                            externalEntity.entityLocation.getLiteralSystemId() != null) {
2683                        return externalEntity.entityLocation.getLiteralSystemId();
2684                    }
2685                }
2686            }
2687        }
2688        return null;
2689    } // getLiteralSystemId():String
2690
2691    /**
2692     * Return the line number where the current document event ends.
2693     * <p>
2694     * <strong>Warning:</strong> The return value from the method
2695     * is intended only as an approximation for the sake of error
2696     * reporting; it is not intended to provide sufficient information
2697     * to edit the character content of the original XML document.
2698     * <p>
2699     * The return value is an approximation of the line number
2700     * in the document entity or external parsed entity where the
2701     * markup triggering the event appears.
2702     * <p>
2703     * If possible, the SAX driver should provide the line position
2704     * of the first character after the text associated with the document
2705     * event.  The first line in the document is line 1.
2706     *
2707     * @return The line number, or -1 if none is available.
2708     */
2709    public int getLineNumber() {
2710        if (fCurrentEntity != null) {
2711            if (fCurrentEntity.isExternal()) {
2712                return fCurrentEntity.lineNumber;
2713            } else {
2714                // search for the first external entity on the stack
2715                int size = fEntityStack.size();
2716                for (int i=size-1; i>0 ; i--) {
2717                    Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity)fEntityStack.elementAt(i);
2718                    if (firstExternalEntity.isExternal()) {
2719                        return firstExternalEntity.lineNumber;
2720                    }
2721                }
2722            }
2723        }
2724
2725        return -1;
2726
2727    } // getLineNumber():int
2728
2729    /**
2730     * Return the column number where the current document event ends.
2731     * <p>
2732     * <strong>Warning:</strong> The return value from the method
2733     * is intended only as an approximation for the sake of error
2734     * reporting; it is not intended to provide sufficient information
2735     * to edit the character content of the original XML document.
2736     * <p>
2737     * The return value is an approximation of the column number
2738     * in the document entity or external parsed entity where the
2739     * markup triggering the event appears.
2740     * <p>
2741     * If possible, the SAX driver should provide the line position
2742     * of the first character after the text associated with the document
2743     * event.
2744     * <p>
2745     * If possible, the SAX driver should provide the line position
2746     * of the first character after the text associated with the document
2747     * event.  The first column in each line is column 1.
2748     *
2749     * @return The column number, or -1 if none is available.
2750     */
2751    public int getColumnNumber() {
2752        if (fCurrentEntity != null) {
2753            if (fCurrentEntity.isExternal()) {
2754                return fCurrentEntity.columnNumber;
2755            } else {
2756                // search for the first external entity on the stack
2757                int size = fEntityStack.size();
2758                for (int i=size-1; i>0 ; i--) {
2759                    Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity)fEntityStack.elementAt(i);
2760                    if (firstExternalEntity.isExternal()) {
2761                        return firstExternalEntity.columnNumber;
2762                    }
2763                }
2764            }
2765        }
2766
2767        return -1;
2768    } // getColumnNumber():int
2769
2770
2771    //
2772    // Protected static methods
2773    //
2774
2775    /**
2776     * Fixes a platform dependent filename to standard URI form.
2777     *
2778     * @param str The string to fix.
2779     *
2780     * @return Returns the fixed URI string.
2781     */
2782    protected static String fixURI(String str) {
2783
2784        // handle platform dependent strings
2785        str = str.replace(java.io.File.separatorChar, '/');
2786
2787        // Windows fix
2788        if (str.length() >= 2) {
2789            char ch1 = str.charAt(1);
2790            // change "C:blah" to "/C:blah"
2791            if (ch1 == ':') {
2792                char ch0 = Character.toUpperCase(str.charAt(0));
2793                if (ch0 >= 'A' && ch0 <= 'Z') {
2794                    str = "/" + str;
2795                }
2796            }
2797            // change "//blah" to "file://blah"
2798            else if (ch1 == '/' && str.charAt(0) == '/') {
2799                str = "file:" + str;
2800            }
2801        }
2802
2803        // replace spaces in file names with %20.
2804        // Original comment from JDK5: the following algorithm might not be
2805        // very performant, but people who want to use invalid URI's have to
2806        // pay the price.
2807        int pos = str.indexOf(' ');
2808        if (pos >= 0) {
2809            StringBuilder sb = new StringBuilder(str.length());
2810            // put characters before ' ' into the string builder
2811            for (int i = 0; i < pos; i++)
2812                sb.append(str.charAt(i));
2813            // and %20 for the space
2814            sb.append("%20");
2815            // for the remamining part, also convert ' ' to "%20".
2816            for (int i = pos+1; i < str.length(); i++) {
2817                if (str.charAt(i) == ' ')
2818                    sb.append("%20");
2819                else
2820                    sb.append(str.charAt(i));
2821            }
2822            str = sb.toString();
2823        }
2824
2825        // done
2826        return str;
2827
2828    } // fixURI(String):String
2829
2830
2831    //
2832    // Package visible methods
2833    //
2834    /** Prints the contents of the buffer. */
2835    final void print() {
2836        if (DEBUG_BUFFER) {
2837            if (fCurrentEntity != null) {
2838                System.out.print('[');
2839                System.out.print(fCurrentEntity.count);
2840                System.out.print(' ');
2841                System.out.print(fCurrentEntity.position);
2842                if (fCurrentEntity.count > 0) {
2843                    System.out.print(" \"");
2844                    for (int i = 0; i < fCurrentEntity.count; i++) {
2845                        if (i == fCurrentEntity.position) {
2846                            System.out.print('^');
2847                        }
2848                        char c = fCurrentEntity.ch[i];
2849                        switch (c) {
2850                            case '\n': {
2851                                System.out.print("\\n");
2852                                break;
2853                            }
2854                            case '\r': {
2855                                System.out.print("\\r");
2856                                break;
2857                            }
2858                            case '\t': {
2859                                System.out.print("\\t");
2860                                break;
2861                            }
2862                            case '\\': {
2863                                System.out.print("\\\\");
2864                                break;
2865                            }
2866                            default: {
2867                                System.out.print(c);
2868                            }
2869                        }
2870                    }
2871                    if (fCurrentEntity.position == fCurrentEntity.count) {
2872                        System.out.print('^');
2873                    }
2874                    System.out.print('"');
2875                }
2876                System.out.print(']');
2877                System.out.print(" @ ");
2878                System.out.print(fCurrentEntity.lineNumber);
2879                System.out.print(',');
2880                System.out.print(fCurrentEntity.columnNumber);
2881            } else {
2882                System.out.print("*NO CURRENT ENTITY*");
2883            }
2884        }
2885    } // print()
2886
2887    /**
2888     * Buffer used in entity manager to reuse character arrays instead
2889     * of creating new ones every time.
2890     *
2891     * @xerces.internal
2892     *
2893     * @author Ankit Pasricha, IBM
2894     */
2895    private static class CharacterBuffer {
2896
2897        /** character buffer */
2898        private char[] ch;
2899
2900        /** whether the buffer is for an external or internal scanned entity */
2901        private boolean isExternal;
2902
2903        public CharacterBuffer(boolean isExternal, int size) {
2904            this.isExternal = isExternal;
2905            ch = new char[size];
2906        }
2907    }
2908
2909
2910     /**
2911     * Stores a number of character buffers and provides it to the entity
2912     * manager to use when an entity is seen.
2913     *
2914     * @xerces.internal
2915     *
2916     * @author Ankit Pasricha, IBM
2917     */
2918    private static class CharacterBufferPool {
2919
2920        private static final int DEFAULT_POOL_SIZE = 3;
2921
2922        private CharacterBuffer[] fInternalBufferPool;
2923        private CharacterBuffer[] fExternalBufferPool;
2924
2925        private int fExternalBufferSize;
2926        private int fInternalBufferSize;
2927        private int poolSize;
2928
2929        private int fInternalTop;
2930        private int fExternalTop;
2931
2932        public CharacterBufferPool(int externalBufferSize, int internalBufferSize) {
2933            this(DEFAULT_POOL_SIZE, externalBufferSize, internalBufferSize);
2934        }
2935
2936        public CharacterBufferPool(int poolSize, int externalBufferSize, int internalBufferSize) {
2937            fExternalBufferSize = externalBufferSize;
2938            fInternalBufferSize = internalBufferSize;
2939            this.poolSize = poolSize;
2940            init();
2941        }
2942
2943        /** Initializes buffer pool. **/
2944        private void init() {
2945            fInternalBufferPool = new CharacterBuffer[poolSize];
2946            fExternalBufferPool = new CharacterBuffer[poolSize];
2947            fInternalTop = -1;
2948            fExternalTop = -1;
2949        }
2950
2951        /** Retrieves buffer from pool. **/
2952        public CharacterBuffer getBuffer(boolean external) {
2953            if (external) {
2954                if (fExternalTop > -1) {
2955                    return fExternalBufferPool[fExternalTop--];
2956                }
2957                else {
2958                    return new CharacterBuffer(true, fExternalBufferSize);
2959                }
2960            }
2961            else {
2962                if (fInternalTop > -1) {
2963                    return fInternalBufferPool[fInternalTop--];
2964                }
2965                else {
2966                    return new CharacterBuffer(false, fInternalBufferSize);
2967                }
2968            }
2969        }
2970
2971        /** Returns buffer to pool. **/
2972        public void returnToPool(CharacterBuffer buffer) {
2973            if (buffer.isExternal) {
2974                if (fExternalTop < fExternalBufferPool.length - 1) {
2975                    fExternalBufferPool[++fExternalTop] = buffer;
2976                }
2977            }
2978            else if (fInternalTop < fInternalBufferPool.length - 1) {
2979                fInternalBufferPool[++fInternalTop] = buffer;
2980            }
2981        }
2982
2983        /** Sets the size of external buffers and dumps the old pool. **/
2984        public void setExternalBufferSize(int bufferSize) {
2985            fExternalBufferSize = bufferSize;
2986            fExternalBufferPool = new CharacterBuffer[poolSize];
2987            fExternalTop = -1;
2988        }
2989    }
2990
2991    /**
2992    * This class wraps the byte inputstreams we're presented with.
2993    * We need it because java.io.InputStreams don't provide
2994    * functionality to reread processed bytes, and they have a habit
2995    * of reading more than one character when you call their read()
2996    * methods.  This means that, once we discover the true (declared)
2997    * encoding of a document, we can neither backtrack to read the
2998    * whole doc again nor start reading where we are with a new
2999    * reader.
3000    *
3001    * This class allows rewinding an inputStream by allowing a mark
3002    * to be set, and the stream reset to that position.  <strong>The
3003    * class assumes that it needs to read one character per
3004    * invocation when it's read() method is inovked, but uses the
3005    * underlying InputStream's read(char[], offset length) method--it
3006    * won't buffer data read this way!</strong>
3007    *
3008    * @xerces.internal
3009    *
3010    * @author Neil Graham, IBM
3011    * @author Glenn Marcy, IBM
3012    */
3013
3014    protected final class RewindableInputStream extends InputStream {
3015
3016        private InputStream fInputStream;
3017        private byte[] fData;
3018        private int fStartOffset;
3019        private int fEndOffset;
3020        private int fOffset;
3021        private int fLength;
3022        private int fMark;
3023
3024        public RewindableInputStream(InputStream is) {
3025            fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE];
3026            fInputStream = is;
3027            fStartOffset = 0;
3028            fEndOffset = -1;
3029            fOffset = 0;
3030            fLength = 0;
3031            fMark = 0;
3032        }
3033
3034        public void setStartOffset(int offset) {
3035            fStartOffset = offset;
3036        }
3037
3038        public void rewind() {
3039            fOffset = fStartOffset;
3040        }
3041
3042        public int read() throws IOException {
3043            int b = 0;
3044            if (fOffset < fLength) {
3045                return fData[fOffset++] & 0xff;
3046            }
3047            if (fOffset == fEndOffset) {
3048                return -1;
3049            }
3050            if (fOffset == fData.length) {
3051                byte[] newData = new byte[fOffset << 1];
3052                System.arraycopy(fData, 0, newData, 0, fOffset);
3053                fData = newData;
3054            }
3055            b = fInputStream.read();
3056            if (b == -1) {
3057                fEndOffset = fOffset;
3058                return -1;
3059            }
3060            fData[fLength++] = (byte)b;
3061            fOffset++;
3062            return b & 0xff;
3063        }
3064
3065        public int read(byte[] b, int off, int len) throws IOException {
3066            int bytesLeft = fLength - fOffset;
3067            if (bytesLeft == 0) {
3068                if (fOffset == fEndOffset) {
3069                    return -1;
3070                }
3071
3072                /**
3073                 * //System.out.println("fCurrentEntitty = " + fCurrentEntity );
3074                 * //System.out.println("fInputStream = " + fInputStream );
3075                 * // better get some more for the voracious reader... */
3076
3077                if(fCurrentEntity.mayReadChunks || !fCurrentEntity.xmlDeclChunkRead) {
3078
3079                    if (!fCurrentEntity.xmlDeclChunkRead)
3080                    {
3081                        fCurrentEntity.xmlDeclChunkRead = true;
3082                        len = Entity.ScannedEntity.DEFAULT_XMLDECL_BUFFER_SIZE;
3083                    }
3084                    return fInputStream.read(b, off, len);
3085                }
3086
3087                int returnedVal = read();
3088                if(returnedVal == -1) {
3089                  fEndOffset = fOffset;
3090                  return -1;
3091                }
3092                b[off] = (byte)returnedVal;
3093                return 1;
3094
3095            }
3096            if (len < bytesLeft) {
3097                if (len <= 0) {
3098                    return 0;
3099                }
3100            } else {
3101                len = bytesLeft;
3102            }
3103            if (b != null) {
3104                System.arraycopy(fData, fOffset, b, off, len);
3105            }
3106            fOffset += len;
3107            return len;
3108        }
3109
3110        public long skip(long n)
3111        throws IOException {
3112            int bytesLeft;
3113            if (n <= 0) {
3114                return 0;
3115            }
3116            bytesLeft = fLength - fOffset;
3117            if (bytesLeft == 0) {
3118                if (fOffset == fEndOffset) {
3119                    return 0;
3120                }
3121                return fInputStream.skip(n);
3122            }
3123            if (n <= bytesLeft) {
3124                fOffset += n;
3125                return n;
3126            }
3127            fOffset += bytesLeft;
3128            if (fOffset == fEndOffset) {
3129                return bytesLeft;
3130            }
3131            n -= bytesLeft;
3132            /*
3133            * In a manner of speaking, when this class isn't permitting more
3134            * than one byte at a time to be read, it is "blocking".  The
3135            * available() method should indicate how much can be read without
3136            * blocking, so while we're in this mode, it should only indicate
3137            * that bytes in its buffer are available; otherwise, the result of
3138            * available() on the underlying InputStream is appropriate.
3139            */
3140            return fInputStream.skip(n) + bytesLeft;
3141        }
3142
3143        public int available() throws IOException {
3144            int bytesLeft = fLength - fOffset;
3145            if (bytesLeft == 0) {
3146                if (fOffset == fEndOffset) {
3147                    return -1;
3148                }
3149                return fCurrentEntity.mayReadChunks ? fInputStream.available()
3150                : 0;
3151            }
3152            return bytesLeft;
3153        }
3154
3155        public void mark(int howMuch) {
3156            fMark = fOffset;
3157        }
3158
3159        public void reset() {
3160            fOffset = fMark;
3161            //test();
3162        }
3163
3164        public boolean markSupported() {
3165            return true;
3166        }
3167
3168        public void close() throws IOException {
3169            if (fInputStream != null) {
3170                fInputStream.close();
3171                fInputStream = null;
3172            }
3173        }
3174    } // end of RewindableInputStream class
3175
3176    public void test(){
3177        //System.out.println("TESTING: Added familytree to entityManager");
3178        //Usecase1
3179        fEntityStorage.addExternalEntity("entityUsecase1",null,
3180                "/space/home/stax/sun/6thJan2004/zephyr/data/test.txt",
3181                "/space/home/stax/sun/6thJan2004/zephyr/data/entity.xml");
3182
3183        //Usecase2
3184        fEntityStorage.addInternalEntity("entityUsecase2","<Test>value</Test>");
3185        fEntityStorage.addInternalEntity("entityUsecase3","value3");
3186        fEntityStorage.addInternalEntity("text", "Hello World.");
3187        fEntityStorage.addInternalEntity("empty-element", "<foo/>");
3188        fEntityStorage.addInternalEntity("balanced-element", "<foo></foo>");
3189        fEntityStorage.addInternalEntity("balanced-element-with-text", "<foo>Hello, World</foo>");
3190        fEntityStorage.addInternalEntity("balanced-element-with-entity", "<foo>&text;</foo>");
3191        fEntityStorage.addInternalEntity("unbalanced-entity", "<foo>");
3192        fEntityStorage.addInternalEntity("recursive-entity", "<foo>&recursive-entity2;</foo>");
3193        fEntityStorage.addInternalEntity("recursive-entity2", "<bar>&recursive-entity3;</bar>");
3194        fEntityStorage.addInternalEntity("recursive-entity3", "<baz>&recursive-entity;</baz>");
3195        fEntityStorage.addInternalEntity("ch","&#x00A9;");
3196        fEntityStorage.addInternalEntity("ch1","&#84;");
3197        fEntityStorage.addInternalEntity("% ch2","param");
3198    }
3199
3200} // class XMLEntityManager
3201