/*
* 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.util.transform;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import org.citygml4j.builder.copy.CopyBuilder;
import org.citygml4j.model.citygml.CityGML;
import org.citygml4j.model.citygml.ade.ADEComponent;
import org.citygml4j.model.citygml.appearance.Appearance;
import org.citygml4j.model.common.base.ModelObject;
import org.citygml4j.model.common.copy.Copyable;
import org.citygml4j.model.gml.feature.AbstractFeature;
import org.citygml4j.model.gml.feature.FeatureArrayProperty;
import org.citygml4j.model.gml.feature.FeatureProperty;
import org.citygml4j.model.module.gml.GMLCoreModule;
import org.citygml4j.model.module.gml.XLinkModule;
import org.citygml4j.util.child.ChildInfo;
import org.citygml4j.util.gmlid.DefaultGMLIdManager;
import org.citygml4j.util.gmlid.GMLIdManager;
import org.citygml4j.util.walker.FeatureWalker;
import org.citygml4j.xml.schema.ElementDecl;
import org.citygml4j.xml.schema.Schema;
import org.citygml4j.xml.schema.SchemaHandler;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class FeatureSplitter {
private final Splitter splitter;
private final List<CityGML> result;
private final GMLIdManager gmlIdManager;
private final ChildInfo childInfo;
private final SplitCopyBuilder copyBuilder;
private FeatureSplitMode splitMode;
private Set<Class<? extends CityGML>> excludes;
private boolean keepInlineAppearance;
private boolean splitCopy;
public FeatureSplitter(SchemaHandler schemaHandler, GMLIdManager gmlIdManager) {
this.gmlIdManager = gmlIdManager;
result = new ArrayList<CityGML>();
splitter = new Splitter(schemaHandler);
childInfo = new ChildInfo();
copyBuilder = new SplitCopyBuilder();
splitMode = FeatureSplitMode.SPLIT_PER_FEATURE;
excludes = new HashSet<Class<? extends CityGML>>();
keepInlineAppearance = true;
splitCopy = false;
}
public FeatureSplitter() {
this(null, DefaultGMLIdManager.getInstance());
}
public FeatureSplitter(GMLIdManager gmlIdManager) {
this(null, gmlIdManager);
}
public FeatureSplitter(SchemaHandler schemaHandler) {
this(schemaHandler, DefaultGMLIdManager.getInstance());
}
public void reset() {
result.clear();
splitter.reset();
splitMode = FeatureSplitMode.SPLIT_PER_FEATURE;
excludes.clear();
keepInlineAppearance = true;
splitCopy = false;
}
public List<CityGML> split(Object object) {
result.clear();
if (splitCopy) {
object = copyBuilder.copy(object);
copyBuilder.visited.clear();
}
if (object instanceof AbstractFeature)
((AbstractFeature)object).accept(splitter);
else if (object instanceof ADEComponent)
splitter.visit((ADEComponent)object);
else if (object instanceof Element)
splitter.visit((Element)object, null);
splitter.reset();
return result;
}
public SchemaHandler getSchemaHandler() {
return splitter.getSchemaHandler();
}
public FeatureSplitMode getSplitMode() {
return splitMode;
}
public void setSplitMode(FeatureSplitMode splitMode) {
if (splitMode == null)
throw new IllegalArgumentException("split mode may not be null.");
this.splitMode = splitMode;
}
public void exlcude(Class<? extends CityGML> cityGMLClass) {
excludes.add(cityGMLClass);
}
public void clearExcludes() {
excludes.clear();
}
public Set<Class<? extends CityGML>> getExcludes() {
return excludes;
}
public void setExcludes(Set<Class<? extends CityGML>> excludes) {
if (excludes == null)
throw new IllegalArgumentException("set of excludes may not be null.");
this.excludes = excludes;
}
public void setKeepInlineAppearance(boolean keepInlineAppearance) {
this.keepInlineAppearance = keepInlineAppearance;
}
public boolean isKeepInlineAppearance() {
return keepInlineAppearance;
}
public boolean isSplitCopy() {
return splitCopy;
}
public void setSplitCopy(boolean splitCopy) {
this.splitCopy = splitCopy;
}
private class SplitCopyBuilder extends CopyBuilder {
private IdentityHashMap<Object, Object> visited = new IdentityHashMap<Object, Object>();
@Override
public Object copy(Object target) {
Object copy = visited.get(target);
if (copy != null)
return copy;
if (target instanceof AbstractFeature ||
target instanceof FeatureProperty<?> ||
target instanceof FeatureArrayProperty)
copy = ((Copyable)target).copy(this);
else if (target instanceof ADEComponent) {
copy = ((Copyable)target).copy(this);
ADEComponent tmp = (ADEComponent)copy;
tmp.setContent((Element)tmp.getContent().cloneNode(true));
}
else
copy = target;
if (copy != null)
visited.put(target, copy);
return copy;
}
}
private class Splitter extends FeatureWalker {
private final SchemaHandler schemaHandler;
Splitter(SchemaHandler schemaHandler) {
super(schemaHandler);
this.schemaHandler = schemaHandler;
}
@Override
public void visit(Appearance appearance) {
if (keepInlineAppearance && childInfo.getParentCityObject(appearance) != null)
return;
super.visit(appearance);
}
@Override
public void visit(AbstractFeature feature) {
if (!excludes.isEmpty())
for (Class<? extends CityGML> exclude : excludes)
if (exclude.isInstance(feature))
return;
ModelObject parent = feature.getParent();
boolean addToResult = false;
if (parent != null) {
if (parent instanceof FeatureProperty<?>) {
FeatureProperty<?> property = (FeatureProperty<?>)parent;
property.setHref('#' + getAndSetGmlId(feature));
property.unsetFeature();
addToResult = true;
}
else if (parent instanceof FeatureArrayProperty) {
FeatureArrayProperty featureArray = (FeatureArrayProperty)parent;
featureArray.unsetFeature(feature);
addToResult = true;
}
} else
addToResult = true;
if (addToResult && feature instanceof CityGML)
result.add((CityGML)feature);
super.visit(feature);
}
@Override
public void visit(ADEComponent adeComponent) {
if (!excludes.isEmpty())
for (Class<? extends CityGML> exclude : excludes)
if (exclude.isInstance(adeComponent))
return;
if (adeComponent.isSetContent() && schemaHandler != null &&
shouldWalk() && addToVisited(adeComponent.getContent())) {
boolean addToResult = false;
ModelObject parent = adeComponent.getParent();
if (parent != null) {
Schema schema = schemaHandler.getSchema(adeComponent.getNamespaceURI());
if (schema != null) {
ElementDecl elementDecl = schema.getElementDecl(adeComponent.getLocalName(), null);
if (elementDecl != null && splitElement(elementDecl)) {
if (parent instanceof FeatureProperty<?>) {
FeatureProperty<?> property = (FeatureProperty<?>)parent;
property.setHref('#' + getAndSetGmlId(adeComponent.getContent()));
property.unsetGenericADEComponent();
addToResult = true;
}
else if (parent instanceof FeatureArrayProperty) {
FeatureArrayProperty featureArray = (FeatureArrayProperty)parent;
featureArray.unsetGenericADEComponent(adeComponent);
addToResult = true;
}
}
}
} else
addToResult = true;
if (addToResult)
result.add(adeComponent);
// visit child elements
adeComponent(adeComponent.getContent(), null, null);
}
}
protected void adeComponent(Element element, Element parent, ElementDecl lastElement) {
Schema schema = schemaHandler.getSchema(element.getNamespaceURI());
if (schema != null) {
ElementDecl tmp = schema.getElementDecl(element.getLocalName(), lastElement);
if (tmp != null &&
parent != null &&
lastElement != null &&
splitElement(tmp, parent) &&
lastElement.hasXLinkAttribute()) {
parent.setAttributeNS(XLinkModule.v3_1_1.getNamespaceURI(), "href", '#' + getAndSetGmlId(element));
parent.removeChild(element);
if (parent.getTextContent().trim().length() == 0)
parent.setTextContent("");
result.add(new ADEComponent(element));
}
lastElement = tmp;
}
NodeList nodeList = element.getChildNodes();
List<Element> children = new ArrayList<Element>(nodeList.getLength());
for (int i = 0; i < nodeList.getLength(); ++i) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE)
children.add((Element)node);
}
for (Element child : children)
if (addToVisited(child))
adeComponent((Element)child, element, lastElement);
}
private boolean splitElement(ElementDecl elementDecl) {
boolean split = elementDecl.isFeature();
if (splitMode == FeatureSplitMode.SPLIT_PER_COLLECTION_MEMBER && split)
split = elementDecl.isGlobal() && elementDecl.substitutesFeature();
return split;
}
private boolean splitElement(ElementDecl elementDecl, Element parent) {
boolean split = splitElement(elementDecl);
if (split && keepInlineAppearance
&& parent.getNamespaceURI().startsWith("http://www.opengis.net/citygml")
&& parent.getLocalName().equals("appearance"))
split = false;
return split;
}
private String getAndSetGmlId(AbstractFeature feature) {
String gmlId = feature.getId();
if (gmlId == null) {
gmlId = gmlIdManager.generateUUID();
feature.setId(gmlId);
}
return gmlId;
}
private String getAndSetGmlId(Element element) {
String gmlId = element.getAttributeNS(GMLCoreModule.v3_1_1.getNamespaceURI(), "id");
if (gmlId.length() == 0)
gmlId = element.getAttribute("id");
if (gmlId.length() == 0) {
gmlId = gmlIdManager.generateUUID();
element.setAttributeNS(GMLCoreModule.v3_1_1.getNamespaceURI(), "id", gmlId);
}
return gmlId;
}
}
}