/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the License at the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apereo.portal.xml.stream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
/**
* Buffers XML events for later re-reading
*
*/
public class BufferedXMLEventReader extends BaseXMLEventReader {
private final LinkedList<XMLEvent> eventBuffer = new LinkedList<XMLEvent>();
private int eventLimit = 0;
private ListIterator<XMLEvent> bufferReader = null;
/** Create new buffering reader, no buffering is done until {@link #mark(int)} is called. */
public BufferedXMLEventReader(XMLEventReader reader) {
super(reader);
}
/**
* Create new buffering reader. Calls {@link #mark(int)} with the specified event limit
*
* @see #mark(int)
*/
public BufferedXMLEventReader(XMLEventReader reader, int eventLimit) {
super(reader);
this.eventLimit = eventLimit;
}
/** @return A copy of the current buffer */
public List<XMLEvent> getBuffer() {
return new ArrayList<XMLEvent>(this.eventBuffer);
}
/* (non-Javadoc)
* @see org.apereo.portal.xml.stream.BaseXMLEventReader#internalNextEvent()
*/
@Override
protected XMLEvent internalNextEvent() throws XMLStreamException {
//If there is an iterator to read from reset was called, use the iterator
//until it runs out of events.
if (this.bufferReader != null) {
final XMLEvent event = this.bufferReader.next();
//If nothing left in the iterator, remove the reference and fall through to direct reading
if (!this.bufferReader.hasNext()) {
this.bufferReader = null;
}
return event;
}
//Get the next event from the underlying reader
final XMLEvent event = this.getParent().nextEvent();
//if buffering add the event
if (this.eventLimit != 0) {
this.eventBuffer.offer(event);
//If limited buffer size and buffer is too big trim the buffer.
if (this.eventLimit > 0 && this.eventBuffer.size() > this.eventLimit) {
this.eventBuffer.poll();
}
}
return event;
}
@Override
public boolean hasNext() {
return this.bufferReader != null || super.hasNext();
}
@Override
public XMLEvent peek() throws XMLStreamException {
if (this.bufferReader != null) {
final XMLEvent event = this.bufferReader.next();
this.bufferReader.previous(); //move the iterator back
return event;
}
return super.peek();
}
/** Same as calling {@link #mark(int)} with -1. */
public void mark() {
this.mark(-1);
}
/**
* Start buffering events
*
* @param eventLimit the maximum number of events to buffer. -1 will buffer all events, 0 will
* buffer no events.
*/
public void mark(int eventLimit) {
this.eventLimit = eventLimit;
//Buffering no events now, clear the buffer and buffered reader
if (this.eventLimit == 0) {
this.eventBuffer.clear();
this.bufferReader = null;
}
//Buffering limited set of events, lets trim the buffer if needed
else if (this.eventLimit > 0) {
//If there is an iterator check its current position and calculate the new iterator start position
int iteratorIndex = 0;
if (this.bufferReader != null) {
final int nextIndex = this.bufferReader.nextIndex();
iteratorIndex =
Math.max(0, nextIndex - (this.eventBuffer.size() - this.eventLimit));
}
//Trim the buffer until it is not larger than the limit
while (this.eventBuffer.size() > this.eventLimit) {
this.eventBuffer.poll();
}
//If there is an iterator re-create it using the newly calculated index
if (this.bufferReader != null) {
this.bufferReader = this.eventBuffer.listIterator(iteratorIndex);
}
}
}
/** Reset the reader to these start of the buffered events. */
public void reset() {
if (this.eventBuffer.isEmpty()) {
this.bufferReader = null;
} else {
this.bufferReader = this.eventBuffer.listIterator();
}
}
@Override
public void close() throws XMLStreamException {
this.mark(0);
super.close();
}
/** @return The number of events in the buffer. */
public int bufferSize() {
return this.eventBuffer.size();
}
/**
* If reading from the buffer after a {@link #reset()} call an {@link IllegalStateException}
* will be thrown.
*/
@Override
public void remove() {
if (this.bufferReader != null && this.bufferReader.hasNext()) {
throw new IllegalStateException("Cannot remove a buffered element");
}
super.remove();
}
}