blob: 4f4a5ec14b5391943019f322159544d96ebe5860 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.oxm.record.deferred;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.persistence.internal.oxm.record.ExtendedContentHandler;
import org.eclipse.persistence.internal.oxm.record.UnmarshalRecord;
import org.eclipse.persistence.internal.oxm.record.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
/**
* <p><b>Purpose</b>: ContentHandler to store events until we know if we are dealing with a simple, complex or empty element.
* <p><b>Responsibilities</b>:<ul>
* <li> Store events until will know if the element is simple, complex or empty
* <li> Return control to the original unmarshalRecord
* </ul>
*/
public abstract class DeferredContentHandler implements ExtendedContentHandler, LexicalHandler {
private int levelIndex;
private List<SAXEvent> events;
private UnmarshalRecord parent;
private boolean startOccurred;
private boolean charactersOccurred;
private boolean attributesOccurred;
protected DeferredContentHandler(UnmarshalRecord parentRecord) {
levelIndex = 0;
events = new ArrayList<>();
this.parent = parentRecord;
}
@Override
public void setNil(boolean isNil) {}
protected abstract void processEmptyElement() throws SAXException;
protected abstract void processComplexElement() throws SAXException;
protected abstract void processSimpleElement() throws SAXException;
protected void processEmptyElementWithAttributes() throws SAXException {
processEmptyElement();
}
protected void executeEvents(UnmarshalRecord unmarshalRecord) throws SAXException {
for (int i = 0; i < events.size(); i++) {
SAXEvent nextEvent = events.get(i);
nextEvent.processEvent(unmarshalRecord);
}
XMLReader parentXMLReader = parent.getXMLReader();
if (parentXMLReader.getContentHandler().equals(this)) {
parentXMLReader.setContentHandler(unmarshalRecord);
parentXMLReader.setLexicalHandler(unmarshalRecord);
}
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
StartPrefixMappingEvent event = new StartPrefixMappingEvent(prefix, uri);
events.add(event);
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
EndPrefixMappingEvent event = new EndPrefixMappingEvent(prefix);
events.add(event);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
levelIndex++;
//Copy attributes because some parsers reuse the Attributes object across start element events
Attributes copiedAttrs = buildAttributeList(atts);
StartElementEvent event = new StartElementEvent(uri, localName, qName, copiedAttrs);
events.add(event);
if (startOccurred) {
//we know it's complex and non-null
processComplexElement();
return;
}
startOccurred = true;
}
protected Attributes buildAttributeList(Attributes attrs) throws SAXException {
int attrsLength = attrs.getLength();
AttributeList attributes = new AttributeList(attrsLength);
for (int i = 0; i < attrsLength; i++) {
String qName = attrs.getQName(i);
String uri = attrs.getURI(i);
attributes.addAttribute(attrs.getLocalName(i), qName, uri, attrs.getType(i), attrs.getValue(i), i);
if(!javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI.equals(uri) && (null != qName && !qName.startsWith(javax.xml.XMLConstants.XMLNS_ATTRIBUTE))) {
attributesOccurred = true;
}
}
return attributes;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
levelIndex--;
EndElementEvent event = new EndElementEvent(uri, localName, qName);
events.add(event);
if (charactersOccurred) {
//we know it is a simple element
processSimpleElement();
} else if(startOccurred){
//we know it is an empty element
if(attributesOccurred) {
processEmptyElementWithAttributes();
} else {
processEmptyElement();
}
}
if ((levelIndex == 0) && (parent != null)) {
XMLReader xmlReader = parent.getXMLReader();
xmlReader.setContentHandler(parent);
xmlReader.setLexicalHandler(parent);
}
}
@Override
public void setDocumentLocator(Locator locator) {
DocumentLocatorEvent event = new DocumentLocatorEvent(locator);
events.add(event);
}
@Override
public void startDocument() throws SAXException {
StartDocumentEvent event = new StartDocumentEvent();
events.add(event);
}
@Override
public void endDocument() throws SAXException {
EndDocumentEvent event = new EndDocumentEvent();
events.add(event);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
charactersOccurred = true;
CharactersEvent event = new CharactersEvent(ch, start, length);
events.add(event);
}
@Override
public void characters(CharSequence characters) {
charactersOccurred = true;
CharactersEvent event = new CharactersEvent(characters);
events.add(event);
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
IgnorableWhitespaceEvent event = new IgnorableWhitespaceEvent(ch, start, length);
events.add(event);
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
ProcessingInstructionEvent event = new ProcessingInstructionEvent(target, data);
events.add(event);
}
@Override
public void skippedEntity(String name) throws SAXException {
SkippedEntityEvent event = new SkippedEntityEvent(name);
events.add(event);
}
@Override
public void startDTD(String name, String publicId, String systemId) throws SAXException {
StartDTDEvent event = new StartDTDEvent(name, publicId, systemId);
events.add(event);
}
@Override
public void endDTD() throws SAXException {
EndDTDEvent event = new EndDTDEvent();
events.add(event);
}
@Override
public void startEntity(String name) throws SAXException {
StartEntityEvent event = new StartEntityEvent(name);
events.add(event);
}
@Override
public void endEntity(String name) throws SAXException {
EndEntityEvent event = new EndEntityEvent(name);
events.add(event);
}
@Override
public void startCDATA() throws SAXException {
StartCDATAEvent event = new StartCDATAEvent();
events.add(event);
}
@Override
public void endCDATA() throws SAXException {
EndCDATAEvent event = new EndCDATAEvent();
events.add(event);
}
@Override
public void comment(char[] ch, int start, int length) throws SAXException {
CommentEvent event = new CommentEvent(ch, start, length);
events.add(event);
}
protected UnmarshalRecord getParent() {
return parent;
}
protected List getEvents() {
return events;
}
// Made static final for performance reasons.
/**
* Implementation of Attributes - used to pass along a given node's attributes
* to the startElement method of the reader's content handler.
*/
private static final class AttributeList implements org.xml.sax.Attributes {
private String[] localNames;
private String[] uris;
private String[] values;
private String[] types;
private ArrayList<String> qNames;
public AttributeList(int size) {
qNames = new ArrayList(size);
localNames = new String[size];
uris = new String[size];
types= new String[size];
values = new String[size];
}
public void addAttribute(String localName, String qName, String uri, String type, String value, int index) {
qNames.add(index, qName);
localNames[index] = localName;
uris[index] = uri;
types[index] = type;
values[index] = value;
}
@Override
public String getQName(int index) {
return qNames.get(index);
}
@Override
public String getType(String namespaceUri, String localName) {
for(int i=0;i <localNames.length; i++){
String nextLocalName = localNames[i];
if(nextLocalName != null && localName!= null && localName.equals(nextLocalName)){
String uriAtIndex = uris[i];
if(uriAtIndex == null) {
uriAtIndex="";
}
if(uriAtIndex.equals(namespaceUri)){
return types[i];
}
}
}
return null;
}
@Override
public String getType(int index) {
return types[index];
}
@Override
public String getType(String qname) {
return types[getIndex(qname)];
}
@Override
public int getIndex(String qname) {
return qNames.indexOf(qname);
}
@Override
public int getIndex(String uri, String localName) {
for(int i=0;i <localNames.length; i++){
String nextLocalName = localNames[i];
if(nextLocalName != null && localName!= null && localName.equals(nextLocalName)){
String uriAtIndex = uris[i];
if(uriAtIndex == null) {
uriAtIndex="";
}
if(uriAtIndex.equals(uri)){
return i;
}
}
}
return -1;
}
@Override
public int getLength() {
return localNames.length;
}
@Override
public String getLocalName(int index) {
return localNames[index];
}
@Override
public String getURI(int index) {
return uris[index];
}
@Override
public String getValue(int index) {
return values[index];
}
@Override
public String getValue(String qname) {
return values[getIndex(qname)];
}
@Override
public String getValue(String uri, String localName) {
for(int i=0;i <localNames.length; i++){
String nextLocalName = localNames[i];
if(nextLocalName != null && localName!= null && localName.equals(nextLocalName)){
String uriAtIndex = uris[i];
//handle null/empty namespace case
if(uriAtIndex == null) {
uriAtIndex="";
}
if(uriAtIndex.equals(uri)){
return values[i];
}
}
}
return null;
}
}
}