/**
* 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.Deque;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
/**
* Base class for {@link XMLEventReader}s that want to modify or remove events from the reader
* stream. If a {@link StartElement} event is removed the subclass's {@link #filterEvent(XMLEvent,
* boolean)} will not see any events until after the matching {@link EndElement} event.
*
*/
public abstract class FilteringXMLEventReader extends BaseXMLEventReader {
private final Deque<QName> prunedElements = new LinkedList<QName>();
private XMLEvent peekedEvent = null;
public FilteringXMLEventReader(XMLEventReader reader) {
super(reader);
}
@Override
protected final XMLEvent internalNextEvent() throws XMLStreamException {
return this.internalNext(false);
}
@Override
public boolean hasNext() {
try {
return peekedEvent != null || (super.hasNext() && this.peek() != null);
} catch (XMLStreamException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (NoSuchElementException e) {
return false;
}
}
@Override
public final XMLEvent peek() throws XMLStreamException {
if (peekedEvent != null) {
return peekedEvent;
}
peekedEvent = internalNext(true);
return peekedEvent;
}
protected final XMLEvent internalNext(boolean peek) throws XMLStreamException {
XMLEvent event = null;
if (peekedEvent != null) {
event = peekedEvent;
peekedEvent = null;
return event;
}
do {
event = super.getParent().nextEvent();
//If there are pruned elements in the queue filtering events is still needed
if (!prunedElements.isEmpty()) {
//If another start element add it to the queue
if (event.isStartElement()) {
final StartElement startElement = event.asStartElement();
prunedElements.push(startElement.getName());
}
//If end element pop the newest name of the queue and double check that the start/end elements match up
else if (event.isEndElement()) {
final QName startElementName = prunedElements.pop();
final EndElement endElement = event.asEndElement();
final QName endElementName = endElement.getName();
if (!startElementName.equals(endElementName)) {
throw new IllegalArgumentException(
"Malformed XMLEvent stream. Expected end element for "
+ startElementName
+ " but found end element for "
+ endElementName);
}
}
event = null;
} else {
final XMLEvent filteredEvent = this.filterEvent(event, peek);
//If the event is being removed and it is a start element all elements until the matching
//end element need to be removed as well
if (filteredEvent == null && event.isStartElement()) {
final StartElement startElement = event.asStartElement();
final QName name = startElement.getName();
prunedElements.push(name);
}
event = filteredEvent;
}
} while (event == null);
return event;
}
/**
* @param event The current event
* @param peek If the event is from a {@link #peek()} call
* @return The event to return, if null is returned the event is dropped from the stream and the
* next event will be used.
*/
protected abstract XMLEvent filterEvent(XMLEvent event, boolean peek);
}