package org.exolab.castor.xml; import java.util.LinkedList; import java.util.Queue; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; /** * This class is used to record the locator values in every SAX event. The * recorded locators then get compared to the locator values from the * SAXEventFromStaxProducer. * * <p> * If they are different then there an assertion is going fail * </p> * * @implements ContentHandler * * @author <a href="mailto:philipp DOT erlacher AT gmail DOT com">Philipp * Erlacher</a> * */ public class DocumentLocatorRecorder implements ContentHandler { /** * Logger from commons-logging. */ private static final Log LOG = LogFactory .getLog(BaseSax2EventFromStaxProducer.class); /** * a fifo to record locations */ private Queue<LocationElement> locations = null; /** * indicatios if we are in recordMode */ private Boolean recordMode; public void setReplayMode() { recordMode = false; } /** * the SAX locator object */ private Locator locator; public DocumentLocatorRecorder() { super(); recordMode = true; locations = new LinkedList<LocationElement>(); } /** * calls {@link #handleEvent()} */ public void characters(char[] ch, int start, int length) throws SAXException { LOG.debug("endDocument"); handleEvent(); } /** * calls {@link #handleEvent()} */ public void endDocument() throws SAXException { LOG.debug("endDocument"); handleEvent(); } /** * calls {@link #handleEvent()} */ public void endElement(String uri, String localName, String qName) throws SAXException { LOG.debug("endElement"); handleEvent(); } /** * calls {@link #handleEvent()} */ public void endPrefixMapping(String prefix) throws SAXException { LOG.debug("endPrefixMapping"); handleEvent(); } /** * calls {@link #handleEvent()} */ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { LOG.debug("ignorableWhitespace"); handleEvent(); } /** * calls {@link #handleEvent()} */ public void processingInstruction(String target, String data) throws SAXException { LOG.debug("processingInstruction"); handleEvent(); } /** * don't handle this event. Check {@link http * ://jira.codehaus.org/browse/CASTOR-2962} for more information */ public void setDocumentLocator(Locator locator) { LOG.debug("setDocumentLocator"); this.locator = locator; } /** * calls {@link #handleEvent()} */ public void skippedEntity(String name) throws SAXException { LOG.debug("skippedEntity"); handleEvent(); } /** * don't handle this event. Check {@link http * ://jira.codehaus.org/browse/CASTOR-2962} for more information */ public void startDocument() throws SAXException { LOG.debug("startDocument"); } /** * calls {@link #handleEvent()} */ public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { LOG.debug("startElement"); handleEvent(); } /** * calls {@link #handleEvent()} */ public void startPrefixMapping(String prefix, String uri) throws SAXException { LOG.debug("startPrefixMapping"); handleEvent(); } /** * if in recordMode then record current location otherwise compare current * location to expected location */ private void handleEvent() { if (inRecordMode()) recordLocation(getCurrentLocationElement()); else compareWithLocationElement(getCurrentLocationElement()); } /** * are we in record mode ? * * @return true if we are, false otherwise */ private boolean inRecordMode() { return recordMode; } /** * record a location * * @param locationElement * location to record */ private void recordLocation(LocationElement locationElement) { LOG.debug("record " + locationElement.getColumnNumber() + ":" + locationElement.getLineNumber()); locations.add(locationElement); } /** * @return Locator the current location as a new LocationElement */ private LocationElement getCurrentLocationElement() { return new LocationElement(locator); } /** * compares the expected location with the current location. If they are not * equal then an Assertion fails * * @param currentLocator * the current location */ private void compareWithLocationElement(LocationElement currentLocation) throws AssertionError { LOG.debug("comparison: "); if(locations.poll().equals(currentLocation)) return; throw new AssertionError("Error !"); } /** * A Wrapper for a locator element. * * @author <a href="mailto:philipp DOT erlacher AT gmail DOT com">Philipp * Erlacher</a> * */ private class LocationElement implements Locator { private String systemId; private String publicId; private int lineNumber; private int columnNumber; public LocationElement(Locator locator) { systemId = locator.getSystemId(); publicId = locator.getPublicId(); lineNumber = locator.getLineNumber(); columnNumber = locator.getColumnNumber(); } public String getSystemId() { return systemId; } public String getPublicId() { return publicId; } public int getLineNumber() { return lineNumber; } public int getColumnNumber() { return columnNumber; } @Override public boolean equals(Object that) { // check for self-comparison if (this == that) return true; if (!(that instanceof LocationElement)) return false; LocationElement locator = (LocationElement) that; // it seems that there is a implementation difference of columnNumber; // in the previous version I used +1 wasn't necessary but here it is // [PE] if (!((columnNumber == locator.getColumnNumber() + 1) || (columnNumber == locator.getColumnNumber()))) return false; if (lineNumber != locator.getLineNumber()) return false; if (!nonEmpty(publicId).equals(nonEmpty(locator.getPublicId()))) return false; if (!nonEmpty(systemId).equals(nonEmpty(locator.getSystemId()))) return false; return true; } private String nonEmpty(String string) { return string == null ? "" : string; } }; }