/* * 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.XmlSchemaElement; import gw.xml.XmlElement; import gw.xml.XmlSortException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.xml.namespace.QName; /** * A sort handler that matches a single element. Any minOccurs/maxOccurs is handled by XmlSchemaObjectPluralityXmlSortHandler. */ public class XmlSchemaElementXmlSortHandler extends XmlSortHandler { private final XmlSchemaElement _element; private final Set<QName> _triedQNames = new HashSet<QName>(); private QName _lastQName; private boolean _moreMatches = true; public XmlSchemaElementXmlSortHandler( XmlSchemaElement element ) { super( element ); _element = element; } @Override public List<XmlElement> sortDirect( LinkedList<XmlElement> remainingChildren, boolean preferredOnly, List<XmlSchemaIndex> requiredSchemas, Set<XmlElement> mustMatch ) { boolean isRef = _element.getRefName() != null; QName elementQName = isRef ? _element.getRefName() : _element.getQName(); ArrayList<XmlElement> ret = null; _moreMatches = false; for ( XmlElement child : remainingChildren ) { QName childQName = child.getQName(); if ( _triedQNames.contains( childQName ) && ! childQName.equals( _lastQName ) ) { continue; } if ( ret == null ) { boolean match; if ( ! isRef ) { // if not a ref, cannot be a substitution group - so only check for an exact match match = childQName.equals( elementQName ); } else { match = matchSubstitutionGroups( elementQName, childQName, requiredSchemas ); } if ( match ) { _lastQName = childQName; ret = new ArrayList<XmlElement>( 1 ); ret.add( child ); // can't use Collections.singletonList since this list gets modified later } else if ( preferredOnly ) { throw new XmlSortException(); } } else if ( ! childQName.equals( _lastQName ) ) { // the substitution group matching logic can be cpu intensive, so let's make sure we're dealing with an element ref first if ( isRef && matchSubstitutionGroups( elementQName, childQName, requiredSchemas ) ) { _moreMatches = true; break; } else { _triedQNames.add( childQName ); } } } if ( ret != null ) { return ret; } if ( _triedQNames.contains( elementQName ) ) { throw new XmlSortException(); } else { throw new XmlSortException( Collections.singletonList( elementQName ) ); } } private boolean matchSubstitutionGroups( QName elementQName, QName childQName, List<XmlSchemaIndex> requiredSchemas ) { // check substitution groups while ( childQName != null ) { if ( ! _triedQNames.contains( childQName ) && childQName.equals( elementQName ) ) { return true; } childQName = getSubstitutionGroup( childQName, requiredSchemas ); } return false; } private QName getSubstitutionGroup( QName childQName, List<XmlSchemaIndex> requiredSchemas ) { for ( XmlSchemaIndex schemaIndex : requiredSchemas ) { XmlSchemaElement childElementSpec = schemaIndex.getXmlSchemaElementByQNameIfValid( childQName ); if ( childElementSpec != null ) { return childElementSpec.getSubstitutionGroup(); } } return null; } @Override protected boolean selectNextChoiceDirect() { if ( _lastQName != null ) { _triedQNames.add( _lastQName ); _lastQName = null; return _moreMatches; } return false; } @Override public void _reset() { _lastQName = null; _triedQNames.clear(); _moreMatches = true; } @Override protected void checkMissingRequiredElements( LinkedList<XmlElement> remainingChildren, List<XmlSchemaIndex> requiredSchemas ) { boolean isRef = _element.getRefName() != null; QName elementQName = isRef ? _element.getRefName() : _element.getQName(); for ( XmlElement child : remainingChildren ) { QName childQName = child.getQName(); if ( _triedQNames.contains( childQName ) ) { continue; } boolean match; if ( ! isRef ) { // if not a ref, cannot be a substitution group - so only check for an exact match match = childQName.equals( elementQName ); } else { match = matchSubstitutionGroups( elementQName, childQName, requiredSchemas ); } if ( match ) { return; } _triedQNames.add( childQName ); // save bad match for sort algorithm } throw new XmlSortException( Collections.singletonList( elementQName ) ); } @Override public void match( Set<XmlElement> children, List<XmlSchemaIndex> requiredSchemas ) { Iterator<XmlElement> iter = children.iterator(); while ( iter.hasNext() ) { XmlElement child = iter.next(); boolean isRef = _element.getRefName() != null; QName elementQName = isRef ? _element.getRefName() : _element.getQName(); QName childQName = child.getQName(); boolean match = false; if ( ! isRef ) { // if not a ref, cannot be a substitution group - so only check for an exact match match = childQName.equals( elementQName ); } else { // check substitution groups while ( childQName != null ) { if ( ! _triedQNames.contains( childQName ) && childQName.equals( elementQName ) ) { match = true; break; } childQName = getSubstitutionGroup( childQName, requiredSchemas ); } } if ( match ) { iter.remove(); } } } }