/* * 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.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * This sort handler handles minOccurs and maxOccurs boundings on behalf of other schema particles. This sort handler * acts similarly to an xsd:choice handler. Lets say we have a single element with minOccurs=0 and maxOccurs=unbounded. * And let's say there are actually 5 copies of this element at this level. Then this sort handler presents 6 choices. * The first choice is the max number of elements available, 5. The other choices are 4, 3, 2, 1, and 0. When told * to reset itself, this sort handler will recalculate the max number of children on the next call to sort(), which * could be greater or less than the previous time it was reset due to previous handlers in the chain adjusting. */ public class XmlSchemaObjectPluralityXmlSortHandler extends XmlSortHandler { private final XmlSchemaObject _schemaObject; private List<XmlSortHandler> _children; private final long _minOccurs; private final long _maxOccurs; private final Constructor<? extends XmlSortHandler> _handlerConstructor; public XmlSchemaObjectPluralityXmlSortHandler( long minOccurs, long maxOccurs, XmlSchemaObject schemaObject, Class<? extends XmlSortHandler> handlerClass ) { super( schemaObject ); try { _handlerConstructor = handlerClass.getConstructor( schemaObject.getClass() ); } catch ( NoSuchMethodException ex ) { throw new RuntimeException( ex ); } _schemaObject = schemaObject; _minOccurs = minOccurs; _maxOccurs = maxOccurs; _children = null; } private XmlSortHandler makeChild() { try { return _handlerConstructor.newInstance( _schemaObject ); } catch ( Exception ex) { throw new RuntimeException( ex ); } } @Override public List<XmlElement> sortDirect( LinkedList<XmlElement> remainingChildren, boolean preferredOnly, List<XmlSchemaIndex> requiredSchemas, Set<XmlElement> mustMatch ) { XmlSortException exception = null; LinkedList<XmlElement> localRemainingChildren = remainingChildren; List<XmlElement> sortedChildren = null; if ( _children == null ) { // match as many children as possible _children = new ArrayList<XmlSortHandler>(); try { for ( int i = 0; i < _maxOccurs; i++ ) { final XmlSortHandler child = makeChild(); List<XmlElement> localSortedChildren = child.sort( localRemainingChildren, preferredOnly, requiredSchemas, Collections.<XmlElement>emptySet() ); while ( localSortedChildren.isEmpty() ) { // imagine trying to find how many possible legal matches can be made on the choice: // <choice minOccurs="0" maxOccurs="unbounded"> // <xsd:element name="Child" minOccurs="0" maxOccurs="unbounded"/> // </choice> // therefore, if the child hasn't actually matched any elements, we stop trying to match if ( ! child.selectNextChoice() ) { throw new XmlSortException(); } localSortedChildren = child.sort( localRemainingChildren, preferredOnly, requiredSchemas, mustMatch ); } if ( ! localSortedChildren.isEmpty() ) { if ( sortedChildren == null || sortedChildren.isEmpty() ) { sortedChildren = localSortedChildren; } else { sortedChildren.addAll( localSortedChildren ); } //noinspection ObjectEquality if ( localRemainingChildren == remainingChildren ) { 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 : localSortedChildren ) { localRemainingChildren.remove( localSortedChild ); } } _children.add( child ); } } catch ( XmlSortException ex ) { // ok - minOccurs will be checked below exception = ex; } } else { for ( XmlSortHandler child : _children ) { final List<XmlElement> localSortedChildren = child.sort( localRemainingChildren, preferredOnly, requiredSchemas, mustMatch ); if ( ! localSortedChildren.isEmpty() ) { if ( sortedChildren == null ) { sortedChildren = new ArrayList<XmlElement>(); } sortedChildren.addAll( localSortedChildren ); //noinspection ObjectEquality if ( localRemainingChildren == remainingChildren ) { 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 : localSortedChildren ) { localRemainingChildren.remove( localSortedChild ); } } } } if ( _children.size() < _minOccurs ) { throw new XmlSortException( "minOccurs not satisfied; expected at least " + _minOccurs + " but found " + _children.size() ); } return sortedChildren == null ? Collections.<XmlElement>emptyList() : sortedChildren; } @Override protected boolean selectNextChoiceDirect() { if ( selectNextChoice( 0 ) ) { return true; } else { // no children changed state if ( _children.size() > _minOccurs ) { _children.remove( _children.size() - 1 ); for ( XmlSortHandler child : _children ) { child.reset(); } return true; } } return false; } @Override public void _reset() { _children = null; } @Override protected void checkMissingRequiredElements( LinkedList<XmlElement> remainingChildren, List<XmlSchemaIndex> requiredSchemas ) { if ( _minOccurs > 0 ) { makeChild().checkMissingRequiredElements( remainingChildren, requiredSchemas ); } } @Override public void match( Set<XmlElement> children, List<XmlSchemaIndex> requiredSchemas ) { makeChild().match( children, requiredSchemas ); } private boolean selectNextChoice( int idx ) { if ( idx < _children.size() ) { final XmlSortHandler child = _children.get( idx ); if ( child.selectNextChoice() ) { return true; } else { // try next child if ( selectNextChoice( idx + 1 ) ) { child.reset(); return true; } } } return false; } }