/*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.ukit.xml;
import java.io.IOException;
import java.util.NoSuchElementException;
import javax.xml.stream.DTDStreamReader;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
/**
* DTDStreamReader implementation.
*
* @see javax.xml.stream.DTDStreamReader
* @see javax.xml.stream.XMLStreamReader
*/
/* pkg */ final class DTDStreamReaderImp
implements DTDStreamReader, Location
{
private Pair mEQhead; // internal event queue
private Pair mEQtail;
private ParserStAX mStream;
/**
* Constructor.
*/
/* pkg */ DTDStreamReaderImp(ParserStAX stream, Pair dtdevt)
{
if (stream == null || dtdevt == null)
throw new NullPointerException();
mStream = stream;
eqAdd(dtdevt);
mEQhead.id = START_DTD;
}
/**
* Returns next DTD parsing event.
*
* @return the integer code corresponding to the current parse event
* @throws NoSuchElementException if this is called when
* {@link #hasNext() hasNext()} returns <code>false</code>
* @throws XMLStreamException if there is an error processing the
* underlying DTD source
*/
public int next()
throws XMLStreamException
{
if (getEventType() == END_DTD)
throw new NoSuchElementException();
if (mEQhead.list != null)
mStream.del(mEQhead.list);
// Remove the head of the queue
mStream.del(eqGet());
if (mEQhead != null)
return mEQhead.id;
try {
// Event queue is empty. Get more events.
switch (mStream.step()) {
case Parser.EV_COMM:
mEQhead.id = COMMENT;
break;
case Parser.EV_PI:
mEQhead.id = PROCESSING_INSTRUCTION;
break;
case Parser.EV_PENT:
mEQhead.id = ENTITY_DECLARATION;
break;
case Parser.EV_UENT:
mEQhead.id = UNPARSED_ENTITY_DECLARATION;
break;
case Parser.EV_NOT:
mEQhead.id = NOTATION_DECLARATION;
break;
case Parser.EV_DTDE:
mStream.mPh = Parser.PH_DTD_MISC;
Pair evt = mStream.pair(null);
evt.id = END_DTD;
eqAdd(evt);
break;
default:
throw new XMLStreamException();
}
} catch(XMLStreamException xmlse) {
throw xmlse;
} catch(IOException ioe) {
throw new XMLStreamException(ioe);
} catch(RuntimeException rte) {
throw rte;
} catch(Exception e) {
throw new XMLStreamException(e.toString());
}
return mEQhead.id;
}
/**
* Returns <code>true</code> if there are more parsing events and
* <code>false</code> if there are no more events. This method will return
* <code>false</code> if the current state of the DTDStreamReader is
* {@link #END_DTD END_DTD}
*
* @return <code>true</code> if there are more events, <code>false</code>
* otherwise
* @throws XMLStreamException if there is a fatal error detecting the next
* state
*/
public boolean hasNext()
throws XMLStreamException
{
return (getEventType() != END_DTD);
}
/**
* Returns an integer code that indicates the type of the event at
* the current cursor location.
*
* @return the integer code corresponding to the current parse event
*/
public int getEventType()
{
return (mStream.mDTD != null)? mEQhead.id: END_DTD;
}
/**
* Returns the current value of the parse event as a string.
* This returns the value of a {@link #COMMENT COMMENT} or the replacement
* value for an {@link #ENTITY_DECLARATION ENTITY_DECLARATION}. This
* method returns <code>null</code> if there is no text available.
*
* @return the current text or <code>null</code> if there is no text available
* @throws java.lang.IllegalStateException if this state is not a valid
* text state.
*/
public String getText()
{
switch (getEventType()) {
case ENTITY_DECLARATION:
if (mEQhead.num < 0) // there is zero length array in chars
return null;
case COMMENT:
if (mEQhead.value == null)
mEQhead.value = new String(mEQhead.chars, 0, mEQhead.num);
return mEQhead.value;
default:
throw new IllegalStateException();
}
}
/**
* Returns an array which contains the characters from this event.
* This array should be treated as read-only and transient: the array
* will contain the text characters only until the DTDStreamReader
* moves on to the next event.
* Attempts to hold onto the character array beyond that time or
* modify the contents of the array are breaches of the contract for this
* interface.
*
* @return the current text or an empty array
* @throws java.lang.IllegalStateException if this state is not a valid
* text state.
*/
public char[] getTextCharacters()
{
switch (getEventType()) {
case COMMENT:
case ENTITY_DECLARATION:
return mEQhead.chars;
default:
throw new IllegalStateException();
}
}
/**
* Returns the offset into the text character array at which the first
* character of the data for the current event is located.
*
* @return the offset of the first character
* @throws java.lang.IllegalStateException if this state is not a valid
* text state.
*/
public int getTextStart()
{
switch (getEventType()) {
case COMMENT:
case ENTITY_DECLARATION:
return 0;
default:
throw new IllegalStateException();
}
}
/**
* Returns the length of the sequence of characters for the current event
* within the text character array. This method returns <code>-1</code>
* if there is no text available.
*
* @return the length of the character sequence for current event or
* <code>-1</code>
* @throws java.lang.IllegalStateException if this state is not a valid
* text state.
*/
public int getTextLength()
{
switch (getEventType()) {
case COMMENT:
case ENTITY_DECLARATION:
return mEQhead.num;
default:
throw new IllegalStateException();
}
}
/**
* Returns the current location of the processor.
* If the location is unknown the processor should return an
* implementation
* of {@link Location Location} that returns <code>-1</code> for the
* location and <code>null</code> for each of the publicId and systemId.
* The location information is only valid until {@link #next() next()} is
* called.
*
* @return the {@link Location Location} object
*/
public Location getLocation()
{
return this;
}
/**
* Returns the public ID of the XML
*
* @return the public ID, or null if not available
*/
public String getPublicId()
{
return mStream.getPublicId();
}
/**
* Returns the system ID of the XML
*
* @return the system ID, or null if not available
*/
public String getSystemId()
{
return mStream.getSystemId();
}
/**
* Return the line number where the current event ends,
* returns -1 if none is available.
*
* @return the current line number
*/
public int getLineNumber()
{
return (mStream.getEventType() == XMLStreamReader.DTD)?
mStream.getLineNumber(): -1;
}
/**
* Return the column number where the current event ends,
* returns -1 if none is available.
*
* @return the current column number
*/
public int getColumnNumber()
{
return (mStream.getEventType() == XMLStreamReader.DTD)?
mStream.getColumnNumber(): -1;
}
/**
* Return the byte or character offset into the input source this location
* is pointing to. If the input source is a file or a byte stream then
* this is the byte offset into that stream, but if the input source is
* a character media then the offset is the character offset.
* Returns -1 if there is no offset available.
*
* @return the current offset
*/
public int getCharacterOffset()
{
return (mStream.getEventType() == XMLStreamReader.DTD)?
mStream.getCharacterOffset(): -1;
}
/**
* Returns the target of a processing instruction.
*
* @return the target or <code>null</code>
* @throws java.lang.IllegalStateException if this method is not valid in
* the current state.
*/
public String getPITarget()
{
if (getEventType() != PROCESSING_INSTRUCTION)
throw new IllegalStateException();
return mEQhead.name;
}
/**
* Returns the data section of a processing instruction.
*
* @return the data or <code>null</code>
* @throws java.lang.IllegalStateException if this method is not valid in
* the current state.
*/
public String getPIData()
{
if (getEventType() != PROCESSING_INSTRUCTION)
throw new IllegalStateException();
return mEQhead.value;
}
/**
* Returns the qualified name.
*
* @return the qualified name
* @throws java.lang.IllegalStateException if this state is not a valid
* text state.
*/
public String getName()
{
switch (getEventType()) {
case NOTATION_DECLARATION:
case ENTITY_DECLARATION:
case UNPARSED_ENTITY_DECLARATION:
case START_DTD:
return mEQhead.name;
default:
throw new IllegalStateException();
}
}
/**
* Returns the public identifier.
*
* @return the public identifier, or null if not available
* @throws java.lang.IllegalStateException if this method is not valid in
* the current state.
*/
public String getPublicIdentifier()
{
switch (getEventType()) {
case ENTITY_DECLARATION:
if (mEQhead.num >= 0)
return null;
case START_DTD:
case NOTATION_DECLARATION:
case UNPARSED_ENTITY_DECLARATION:
return mEQhead.list.name;
default:
throw new IllegalStateException();
}
}
/**
* Returns the system identifier.
*
* @return the system identifier, or null if not available
* @throws java.lang.IllegalStateException if this method is not valid in
* the current state.
*/
public String getSystemIdentifier()
{
switch (getEventType()) {
case ENTITY_DECLARATION:
if (mEQhead.num >= 0)
return null;
case START_DTD:
case NOTATION_DECLARATION:
case UNPARSED_ENTITY_DECLARATION:
return mEQhead.list.value;
default:
throw new IllegalStateException();
}
}
/**
* Returns the notation name.
*
* @return the notation name, or null if not available
* @throws java.lang.IllegalStateException if this method is not valid in
* the current state.
*/
public String getNotationName()
{
switch (getEventType()) {
case UNPARSED_ENTITY_DECLARATION:
return mEQhead.value;
default:
throw new IllegalStateException();
}
}
/**
* Terminates DTD processing, skipping all DTD related events up
* to {@link #END_DTD END_DTD}. This method does not close the
* underlying input source.
* If this method is called when current state is already
* {@link #END_DTD END_DTD}, this method does nothing.
* Once this method has been invoked,
* method {@link #hasNext() hasNext()} returns <code>false</code>,
* method {@link #getEventType() getEventType()} returns
* {@link #END_DTD END_DTD} and any other method call except
* {@link #next() next()} or {@link #getLocation() getLocation()}
* on this interface generates an
* <code>java.lang.IllegalStateException</code>.
*
* @throws XMLStreamException if there is an error processing the
* underlying DTD source
*/
public void close()
throws XMLStreamException
{
while (hasNext())
next();
}
/**
* Adds an event to the head of internal event queue.
*
* @param evt An event to add to the tail of the queue.
*/
/* pkg */ void eqAdd(Pair evt)
{
evt.next = null;
if (mEQtail != null)
mEQtail.next = evt;
else
mEQhead = evt;
mEQtail = evt;
}
/**
* Retrieves an event from internal event queue.
*
* @return An event or <code>null</code> if queue is empty.
*/
/* pkg */ Pair eqGet()
{
Pair evt = mEQhead;
if (evt != null) {
mEQhead = evt.next;
evt.next = null;
}
if (mEQhead == null)
mEQtail = null;
return evt;
}
}