/*
* 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.XmlSchemaObject;
import gw.xml.XmlElement;
import gw.xml.XmlSortException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.xml.namespace.QName;
/**
* A handler representing a portion of an XML schema, used for sorting the direct children of an XML element in order
* to make the ordering of those children match the schema.
*/
public abstract class XmlSortHandler {
@SuppressWarnings( { "UnusedDeclaration", "FieldCanBeLocal" } )
private final XmlSchemaObject _schemaObject;
private boolean _sortCalledSinceLastSelect;
private boolean _matchesEmpty;
public XmlSortHandler( XmlSchemaObject schemaObject ) {
_schemaObject = schemaObject;
}
protected abstract List<XmlElement> sortDirect( LinkedList<XmlElement> remainingChildren, boolean preferredOnly, List<XmlSchemaIndex> requiredSchemas, Set<XmlElement> mustMatch );
protected abstract boolean selectNextChoiceDirect();
public final void reset() {
_reset();
}
protected abstract void _reset();
public final List<XmlElement> sort( LinkedList<XmlElement> remainingChildren, boolean preferredOnly, List<XmlSchemaIndex> requiredSchemas, Set<XmlElement> mustMatch ) {
if ( _matchesEmpty && remainingChildren.isEmpty() ) {
return Collections.emptyList();
}
HashSet<XmlElement> localMustMatch = new HashSet<XmlElement>( mustMatch );
match( localMustMatch, requiredSchemas );
if ( ! localMustMatch.isEmpty() ) {
// try sorting 1 time to get a real exception
_sortCalledSinceLastSelect = true;
sortDirect( remainingChildren, false, requiredSchemas, mustMatch );
// no exception from sort routine, so let's throw one
List<QName> unmatchedQNames = new ArrayList<QName>();
for ( XmlElement unmatchedElement : localMustMatch ) {
unmatchedQNames.add( unmatchedElement.getQName() );
}
if ( unmatchedQNames.size() == 1 ) {
throw new XmlSortException( "Unexpected child element: " + unmatchedQNames.get( 0 ) );
}
else {
throw new XmlSortException( "Unexpected child elements: " + unmatchedQNames );
}
}
List<QName> qnames = null;
String exceptionMessage = null;
while ( true ) {
try {
_sortCalledSinceLastSelect = true;
List<XmlElement> sortedChildren = sortDirect( remainingChildren, preferredOnly, requiredSchemas, mustMatch );
if ( sortedChildren.isEmpty() ) {
_matchesEmpty = true;
if ( remainingChildren.isEmpty() ) {
return Collections.emptyList();
}
// optimization - try to match children before matching a lack of children
throw new XmlSortException( "Matched empty" );
}
return sortedChildren;
}
catch ( XmlSortException ex ) {
if ( qnames == null ) {
qnames = new ArrayList<QName>();
}
if ( exceptionMessage == null && ex.getMessage() != null ) {
exceptionMessage = ex.getMessage();
}
qnames.addAll( ex.getQNames() );
if ( ! selectNextChoice() ) {
if ( _matchesEmpty ) {
return Collections.emptyList();
}
if ( qnames.isEmpty() ) {
throw new XmlSortException( exceptionMessage );
}
else {
throw new XmlSortException( qnames );
}
}
}
}
}
protected abstract void checkMissingRequiredElements( LinkedList<XmlElement> remainingChildren, List<XmlSchemaIndex> requiredSchemas );
public final boolean selectNextChoice() {
// if sort was not called since the last selectNextChoice() call, you could not have been part of the failure,
// and are not obligated to change your state
if ( _sortCalledSinceLastSelect ) {
_sortCalledSinceLastSelect = false;
return selectNextChoiceDirect();
}
else {
return false;
}
}
public abstract void match( Set<XmlElement> children, List<XmlSchemaIndex> requiredSchemas );
public XmlSchemaObject getSchemaObject() {
return _schemaObject;
}
}