/*
* citygml4j - The Open Source Java API for CityGML
* https://github.com/citygml4j
*
* Copyright 2013-2017 Claus Nagel <claus.nagel@gmail.com>
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.citygml4j.builder.jaxb.xml.io.reader;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.UnmarshallerHandler;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXResult;
import org.citygml4j.builder.jaxb.xml.io.reader.XMLElementChecker.ElementInfo;
import org.citygml4j.model.citygml.CityGML;
import org.citygml4j.model.citygml.CityGMLClass;
import org.citygml4j.model.citygml.appearance.AppearanceProperty;
import org.citygml4j.model.citygml.core.AbstractCityObject;
import org.citygml4j.model.citygml.core.CityModel;
import org.citygml4j.model.common.base.ModelObject;
import org.citygml4j.model.common.base.ModelType;
import org.citygml4j.model.gml.base.MetaDataProperty;
import org.citygml4j.model.gml.base.StringOrRef;
import org.citygml4j.model.gml.basicTypes.Code;
import org.citygml4j.model.gml.feature.AbstractFeature;
import org.citygml4j.model.gml.feature.BoundingShape;
import org.citygml4j.model.gml.feature.FeatureProperty;
import org.citygml4j.model.gml.feature.LocationProperty;
import org.citygml4j.model.module.gml.GMLCoreModule;
import org.citygml4j.util.internal.xml.TransformerChain;
import org.citygml4j.util.xml.SAXEventBuffer;
import org.citygml4j.util.xml.StAXStream2SAX;
import org.citygml4j.xml.io.reader.MissingADESchemaException;
import org.citygml4j.xml.io.reader.ParentInfo;
import org.citygml4j.xml.io.reader.UnmarshalException;
import org.citygml4j.xml.io.reader.XMLChunk;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
public class XMLChunkImpl implements XMLChunk {
private final AbstractJAXBReader jaxbReader;
private final XMLChunkImpl parentChunk;
private final SAXEventBuffer buffer;
private final StAXStream2SAX stax2sax;
private CityGML citygml;
private ParentInfo featureInfo;
private CityGMLClass type = CityGMLClass.UNDEFINED;
private QName firstElement;
private AtomicBoolean parentInfoResolved = new AtomicBoolean(false);
private AtomicBoolean citygmlResolved = new AtomicBoolean(false);
private AtomicBoolean typeResolved = new AtomicBoolean(false);
private boolean isFiltered = false;
private int depth = 0;
private boolean hasPassedXMLValidation = false;
XMLChunkImpl(AbstractJAXBReader chunkReader, XMLChunkImpl parentChunk, CityGMLClass type) {
this.jaxbReader = chunkReader;
this.parentChunk = parentChunk;
buffer = new SAXEventBuffer();
stax2sax = new StAXStream2SAX(buffer);
if (type != null && type != CityGMLClass.UNDEFINED) {
this.type = type;
typeResolved.set(true);
}
}
@Override
public boolean isSetParentInfo() {
return getParentInfo() != null;
}
@Override
public ParentInfo getParentInfo() {
return parentChunk != null ? parentChunk.unmarshalFeatureInfo() : null;
}
@Override
public boolean hasPassedXMLValidation() {
return hasPassedXMLValidation;
}
protected void removeTrailingCharacters() {
buffer.removeTrailingCharacters();
}
protected boolean isComplete() {
return depth == 0 && !buffer.isEmpty();
}
protected boolean isFiltered() {
return isFiltered;
}
protected void setIsFiltered(boolean isFiltered) {
this.isFiltered = isFiltered;
}
protected void setFirstElement(QName firstElement) {
this.firstElement = firstElement;
}
protected void addEvent(XMLStreamReader reader) throws XMLStreamException {
int event = reader.getEventType();
if (buffer.isEmpty() && event != XMLStreamConstants.START_ELEMENT)
throw new XMLStreamException("chunk expects a START_ELEMENT as first element.");
else if (isComplete() && (event == XMLStreamConstants.START_ELEMENT || event == XMLStreamConstants.END_ELEMENT))
throw new XMLStreamException("chunk does not accept further START_ELEMENT or END_ELEMENT events.");
stax2sax.bridgeEvent(reader);
switch (event) {
case XMLStreamConstants.START_ELEMENT:
depth++;
break;
case XMLStreamConstants.END_ELEMENT:
depth--;
break;
}
}
protected void addAttribute(String uri, String localName, String prefix, String type, String value) {
buffer.addAttribute(uri, localName, prefix, type, value);
}
@Override
public CityGMLClass getCityGMLClass() {
if (citygmlResolved.get())
return citygml.getCityGMLClass();
if (typeResolved.compareAndSet(false, true)) {
ElementInfo info = null;
if (jaxbReader.elementChecker.isCityGMLElement(firstElement))
info = jaxbReader.elementChecker.getCityGMLFeature(firstElement, true);
else {
try {
info = jaxbReader.elementChecker.getADEElementInfo(firstElement, null, true, true);
} catch (MissingADESchemaException e) {
//
}
}
if (info != null)
type = info.getType();
}
return type;
}
@Override
public void send(ContentHandler handler, boolean release) throws SAXException {
if (citygmlResolved.get())
throw new IllegalStateException("Unmarshalled chunks cannot be forwarded to a ContentHandler.");
buffer.send(handler, release);
}
private ParentInfo unmarshalFeatureInfo() {
if (!buffer.isEmpty() && parentInfoResolved.compareAndSet(false, true)) {
try {
final SAXEventBuffer tmp = new SAXEventBuffer();
ContentHandler handler = new ContentHandler() {
int depth = 0;
boolean captureElements = true;
public void startPrefixMapping(String prefix, String uri) throws SAXException {
tmp.startPrefixMapping(prefix, uri);
}
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if (depth == 1)
captureElements = jaxbReader.elementChecker.isParentInfoElement(uri, localName);
if (captureElements)
tmp.startElement(uri, localName, qName, atts);
depth++;
}
public void endElement(String uri, String localName, String qName) throws SAXException {
if (captureElements)
tmp.endElement(uri, localName, qName);
depth--;
}
public void characters(char[] ch, int start, int length) throws SAXException {
if (captureElements)
tmp.characters(ch, start, length);
}
public void endDocument() throws SAXException {
tmp.addEndElement();
if (captureElements)
tmp.addEndElement();
}
public void startDocument() throws SAXException { }
public void skippedEntity(String name) throws SAXException { }
public void setDocumentLocator(Locator locator) { }
public void processingInstruction(String target, String data) throws SAXException { }
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { }
public void endPrefixMapping(String prefix) throws SAXException { }
};
buffer.send(handler, false);
handler.endDocument();
CityGML citygml = unmarshal(tmp, false);
if (citygml instanceof AbstractFeature)
featureInfo = new FeatureInfoImpl((AbstractFeature)citygml);
} catch (UnmarshalException | MissingADESchemaException | SAXException e) {
//
}
}
return featureInfo;
}
@Override
public CityGML unmarshal() throws UnmarshalException, MissingADESchemaException {
if (citygmlResolved.compareAndSet(false, true))
citygml = unmarshal(buffer, jaxbReader.useValidation);
return citygml;
}
private CityGML unmarshal(SAXEventBuffer buffer, boolean useValidation) throws UnmarshalException, MissingADESchemaException {
try {
CityGML citygml = null;
QName fakeRoot = getFakeRoot();
Unmarshaller unmarshaller = jaxbReader.factory.builder.getJAXBContext().createUnmarshaller();
if (useValidation) {
hasPassedXMLValidation = true;
unmarshaller.setSchema(jaxbReader.validationSchemaHandler.getSchema());
if (jaxbReader.validationEventHandler != null) {
unmarshaller.setEventHandler(new ValidationEventHandler() {
public boolean handleEvent(ValidationEvent event) {
hasPassedXMLValidation = false;
return jaxbReader.validationEventHandler.handleEvent(event);
}
});
}
}
UnmarshallerHandler unmarshallerHandler = unmarshaller.getUnmarshallerHandler();
ContentHandler contentHandler = null;
if (jaxbReader.transformerChainFactory == null)
contentHandler = unmarshallerHandler;
else {
// apply transformation before unmarshalling
TransformerChain chain = jaxbReader.transformerChainFactory.buildChain();
chain.tail().setResult(new SAXResult(unmarshallerHandler));
contentHandler = chain.head();
}
// emulate start of a new document
contentHandler.startDocument();
if (fakeRoot != null)
contentHandler.startElement(fakeRoot.getNamespaceURI(), fakeRoot.getLocalPart(), "", new AttributesImpl());
// fire buffered sax events to content handler
buffer.send(contentHandler, true);
// emulate end of a document
if (fakeRoot != null)
contentHandler.endElement(fakeRoot.getNamespaceURI(), fakeRoot.getLocalPart(), "");
contentHandler.endDocument();
// unmarshal sax events
Object result = unmarshallerHandler.getResult();
unmarshaller = null;
unmarshallerHandler = null;
if (result instanceof JAXBElement<?>) {
ModelObject gml = jaxbReader.jaxbUnmarshaller.unmarshal((JAXBElement<?>)result);
if (gml.getModelType() == ModelType.CITYGML) {
if (gml instanceof AbstractFeature)
citygml = (CityGML)gml;
else if (gml instanceof AppearanceProperty)
citygml = ((AppearanceProperty)gml).getAppearance();
} else if (gml instanceof FeatureProperty<?>)
citygml = ((FeatureProperty<?>)gml).getGenericADEComponent();
}
return citygml;
} catch (JAXBException e) {
throw new UnmarshalException("Unmarshal exception caused by: ", e);
} catch (SAXException e) {
throw new UnmarshalException("Unmarshal exception caused by: ", e);
} catch (TransformerConfigurationException e) {
throw new UnmarshalException("Unmarshal exception caused by: ", e);
}
}
private QName getFakeRoot() {
if (firstElement.getLocalPart().equals("Appearance") &&
jaxbReader.elementChecker.isCityGMLElement(firstElement.getNamespaceURI()))
return new QName(firstElement.getNamespaceURI(), "appearanceMember");
else if (!jaxbReader.elementChecker.isCityGMLElement(firstElement.getNamespaceURI()))
return new QName(GMLCoreModule.v3_1_1.getNamespaceURI(), "featureProperty");
return null;
}
private final class FeatureInfoImpl implements ParentInfo {
private final AbstractFeature feature;
private FeatureInfoImpl(AbstractFeature feature) {
this.feature = feature;
}
public boolean isSetId() {
return feature.isSetId();
}
public String getId() {
return feature.getId();
}
public boolean isSetName() {
return feature.isSetName();
}
public List<Code> getName() {
return feature.getName();
}
public boolean isSetDescription() {
return feature.isSetDescription();
}
public StringOrRef getDescription() {
return feature.getDescription();
}
public boolean isSetMetaDataProperty() {
return feature.isSetMetaDataProperty();
}
public List<MetaDataProperty> getMetaDataProperty() {
return feature.getMetaDataProperty();
}
public boolean isSetBoundedBy() {
return feature.isSetBoundedBy();
}
public BoundingShape getBoundedBy() {
return feature.getBoundedBy();
}
public boolean isSetLocation() {
return feature.isSetLocation();
}
public LocationProperty getLocation() {
return feature.getLocation();
}
public boolean isSetAppearance() {
if (feature instanceof AbstractCityObject)
return ((AbstractCityObject)feature).isSetAppearance();
else if (feature instanceof CityModel)
return ((CityModel)feature).isSetAppearanceMember();
return false;
}
public List<? extends AppearanceProperty> getAppearance() {
if (feature instanceof AbstractCityObject)
return ((AbstractCityObject)feature).getAppearance();
else if (feature instanceof CityModel)
return ((CityModel)feature).getAppearanceMember();
return null;
}
public CityGMLClass getCityGMLClass() {
return (feature instanceof CityGML) ? ((CityGML)feature).getCityGMLClass() : CityGMLClass.UNDEFINED;
}
public boolean isSetParentInfo() {
return getParentInfo() != null;
}
public ParentInfo getParentInfo() {
return (parentChunk != null) ? parentChunk.unmarshalFeatureInfo() : null;
}
}
}