/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.xml.xsd.typeprovider.xmlsorter;
import gw.internal.xml.xsd.typeprovider.XmlSchemaIndex;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaAll;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaAny;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaChoice;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaComplexContent;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaComplexContentExtension;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaComplexContentRestriction;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaComplexType;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaElement;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaGroup;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaObject;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaParticle;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaSequence;
import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaType;
import gw.util.Pair;
import gw.xml.XmlElement;
import gw.xml.XmlSortException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Sorts children of an element in order to make the ordering match an XML schema.
*/
public final class XmlSorter {
private static final Map<Class<? extends XmlSchemaObject>,Class<? extends XmlSortHandler>> _handlers = new HashMap<Class<? extends XmlSchemaObject>, Class<? extends XmlSortHandler>>();
static {
_handlers.put( XmlSchemaChoice.class, XmlSchemaChoiceXmlSortHandler.class );
_handlers.put( XmlSchemaComplexContent.class, XmlSchemaComplexContentXmlSortHandler.class );
_handlers.put( XmlSchemaComplexContentExtension.class, XmlSchemaComplexContentExtensionXmlSortHandler.class );
_handlers.put( XmlSchemaComplexContentRestriction.class, XmlSchemaComplexContentRestrictionXmlSortHandler.class );
_handlers.put( XmlSchemaComplexType.class, XmlSchemaComplexTypeXmlSortHandler.class );
_handlers.put( XmlSchemaElement.class, XmlSchemaElementXmlSortHandler.class );
_handlers.put( XmlSchemaGroup.class, XmlSchemaGroupXmlSortHandler.class );
_handlers.put( XmlSchemaSequence.class, XmlSchemaSequenceXmlSortHandler.class );
_handlers.put( XmlSchemaAll.class, XmlSchemaAllXmlSortHandler.class ); // uses same handler as xsd:sequence
_handlers.put( XmlSchemaAny.class, XmlSchemaAnyXmlSortHandler.class );
}
private XmlSorter() {}
public static XmlSortHandler createHandler( XmlSchemaObject schemaObject ) {
Class<? extends XmlSchemaObject> schemaObjectClass = schemaObject.getClass();
Class<? extends XmlSortHandler> handlerClass = _handlers.get( schemaObjectClass );
if ( handlerClass == null ) {
throw new RuntimeException( "No handler found for " + schemaObjectClass );
}
try {
final Pair<Long, Long> minMaxOccurs = getMinMaxOccurs( schemaObject );
if ( minMaxOccurs.getFirst() != 1 || minMaxOccurs.getSecond() != 1 ) {
return new XmlSchemaObjectPluralityXmlSortHandler( minMaxOccurs.getFirst(), minMaxOccurs.getSecond(), schemaObject, handlerClass );
}
else {
return handlerClass.getConstructor( schemaObjectClass ).newInstance( schemaObject );
}
}
catch ( Exception ex ) {
throw new RuntimeException( ex );
}
}
public static Pair<Long,Long> getMinMaxOccurs( XmlSchemaObject schemaObject ) {
final Pair<Long, Long> minMaxOccurs;
if ( schemaObject instanceof XmlSchemaParticle ) {
XmlSchemaParticle particle = (XmlSchemaParticle) schemaObject;
minMaxOccurs = new Pair<Long, Long>( particle.getMinOccurs(), particle.getMaxOccurs() );
}
else {
minMaxOccurs = new Pair<Long, Long>( 1L, 1L );
}
return minMaxOccurs;
}
public static List<XmlElement> sort( XmlSchemaType xmlSchemaObject, LinkedList<XmlElement> remainingChildren, final List<XmlSchemaIndex> requiredSchemas ) {
final XmlSortHandler rootHandler = xmlSchemaObject == null ? null : createHandler( xmlSchemaObject );
XmlSortHandler metaHandler = new XmlSortHandler( xmlSchemaObject ) {
public XmlSortException _xmlSortException;
@Override
protected List<XmlElement> sortDirect( LinkedList<XmlElement> remainingChildren, boolean preferredOnly, List<XmlSchemaIndex> requiredSchemas, Set<XmlElement> mustMatch ) {
List<XmlElement> sortedChildren;
try {
sortedChildren = rootHandler == null ? Collections.<XmlElement>emptyList() : rootHandler.sort( remainingChildren, preferredOnly, requiredSchemas, mustMatch );
}
catch ( XmlSortException ex ) {
throw _xmlSortException == null ? ex : _xmlSortException; // prefer the "extra elements" exception over the actual exception
}
if ( remainingChildren.size() != sortedChildren.size() ) {
LinkedList<XmlElement> localRemainingChildren = new LinkedList<XmlElement>( remainingChildren );
// this is NOT equivalent to removeAll() when multiples of the same child exist in the content list
for ( XmlElement localSortedChild : sortedChildren ) {
localRemainingChildren.remove( localSortedChild );
}
_xmlSortException = new XmlSortException( "Extra elements found: " + localRemainingChildren );
throw _xmlSortException;
}
return sortedChildren;
}
@Override
protected boolean selectNextChoiceDirect() {
//noinspection SimplifiableConditionalExpression
return rootHandler == null ? false : rootHandler.selectNextChoice();
}
@Override
public void _reset() {
if ( rootHandler != null ) {
rootHandler.reset();
}
}
@Override
protected void checkMissingRequiredElements( LinkedList<XmlElement> remainingChildren, List<XmlSchemaIndex> requiredSchemas ) {
if ( rootHandler != null ) {
rootHandler.checkMissingRequiredElements( remainingChildren, requiredSchemas );
}
}
@Override
public void match( Set<XmlElement> children, List<XmlSchemaIndex> requiredSchemas ) {
if ( rootHandler != null ) {
rootHandler.match( children, requiredSchemas );
}
}
};
metaHandler.checkMissingRequiredElements( remainingChildren, requiredSchemas );
try {
return metaHandler.sort( remainingChildren, true, requiredSchemas, new HashSet<XmlElement>( remainingChildren ) );
}
catch ( XmlSortException ex ) {
// try non-preferred
metaHandler.reset();
return metaHandler.sort( remainingChildren, false, requiredSchemas, new HashSet<XmlElement>( remainingChildren ) );
}
}
}