All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xmlbeans.impl.schema.StscChecker Maven / Gradle / Ivy

There is a newer version: 5.0.71
Show newest version
/*   Copyright 2004 The Apache Software Foundation
 *
 *   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.apache.xmlbeans.impl.schema;

import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.SchemaParticle;
import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.SchemaLocalElement;
import org.apache.xmlbeans.SchemaIdentityConstraint;
import org.apache.xmlbeans.SchemaAttributeModel;
import org.apache.xmlbeans.SchemaLocalAttribute;
import org.apache.xmlbeans.SchemaGlobalElement;
import org.apache.xmlbeans.XmlID;
import org.apache.xmlbeans.XmlAnySimpleType;
import org.apache.xmlbeans.XmlErrorCodes;
import org.apache.xmlbeans.XmlNOTATION;
import org.apache.xmlbeans.XmlString;
import org.apache.xmlbeans.impl.common.XBeanDebug;
import org.apache.xmlbeans.impl.common.QNameHelper;

import javax.xml.namespace.QName;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.math.BigInteger;

public class StscChecker
{
    public static void checkAll()
    {
        // walk the tree of types
        StscState state = StscState.get();

        List allSeenTypes = new ArrayList();
        allSeenTypes.addAll(Arrays.asList(state.documentTypes()));
        allSeenTypes.addAll(Arrays.asList(state.attributeTypes()));
        allSeenTypes.addAll(Arrays.asList(state.redefinedGlobalTypes()));
        allSeenTypes.addAll(Arrays.asList(state.globalTypes()));

        for (int i = 0; i < allSeenTypes.size(); i++)
        {
            SchemaType gType = (SchemaType)allSeenTypes.get(i);
            if (!state.noPvr() &&  // option to turn off particle restriction checking
                !gType.isDocumentType()) // Don't check doc types for restriction. 
            {
                checkRestriction((SchemaTypeImpl)gType);
            }
            checkFields((SchemaTypeImpl)gType);
            allSeenTypes.addAll(Arrays.asList(gType.getAnonymousTypes()));
        }

        checkSubstitutionGroups(state.globalElements());
    }
    
    /**
     * The following code checks rule #5 of http://www.w3.org/TR/xmlschema-1/#coss-ct
     * as well as attribute + element default/fixed validity. 

* Checks that xs:NOTATION is not used directly */ public static void checkFields(SchemaTypeImpl sType) { if (sType.isSimpleType()) return; XmlObject location = sType.getParseObject(); SchemaAttributeModel sAttrModel = sType.getAttributeModel(); if (sAttrModel != null) { SchemaLocalAttribute[] sAttrs = sAttrModel.getAttributes(); QName idAttr = null; for (int i = 0; i < sAttrs.length; i++) { XmlObject attrLocation = ((SchemaLocalAttributeImpl)sAttrs[i])._parseObject; if (XmlID.type.isAssignableFrom(sAttrs[i].getType())) { if (idAttr == null) { idAttr = sAttrs[i].getName(); } else { StscState.get().error(XmlErrorCodes.ATTR_GROUP_PROPERTIES$TWO_IDS, new Object[]{ QNameHelper.pretty(idAttr), sAttrs[i].getName() }, attrLocation != null ? attrLocation : location); } if (sAttrs[i].getDefaultText() != null) { StscState.get().error(XmlErrorCodes.ATTR_PROPERTIES$ID_FIXED_OR_DEFAULT, null, attrLocation != null ? attrLocation : location); } } else if (XmlNOTATION.type.isAssignableFrom(sAttrs[i].getType())) { if (sAttrs[i].getType().getBuiltinTypeCode() == SchemaType.BTC_NOTATION) { StscState.get().recover(XmlErrorCodes.ATTR_NOTATION_TYPE_FORBIDDEN, new Object[]{ QNameHelper.pretty(sAttrs[i].getName()) }, attrLocation != null ? attrLocation : location); } else { if (sAttrs[i].getType().getSimpleVariety() == SchemaType.UNION) { SchemaType[] members = sAttrs[i].getType().getUnionConstituentTypes(); for (int j = 0; j < members.length; j++) if (members[j].getBuiltinTypeCode() == SchemaType.BTC_NOTATION) StscState.get().recover(XmlErrorCodes.ATTR_NOTATION_TYPE_FORBIDDEN, new Object[]{ QNameHelper.pretty(sAttrs[i].getName()) }, attrLocation != null ? attrLocation : location); } // Check that the Schema in which this is present doesn't have a targetNS boolean hasNS; if (sType.isAttributeType()) hasNS = sAttrs[i].getName().getNamespaceURI().length() > 0; else { SchemaType t = sType; while (t.getOuterType() != null) t = t.getOuterType(); if (t.isDocumentType()) hasNS = t.getDocumentElementName().getNamespaceURI().length() > 0; else hasNS = t.getName().getNamespaceURI().length() > 0; } if (hasNS) StscState.get().warning(XmlErrorCodes.ATTR_COMPATIBILITY_TARGETNS, new Object[] {QNameHelper.pretty(sAttrs[i].getName()) }, attrLocation != null ? attrLocation : location); } } else { String valueConstraint = sAttrs[i].getDefaultText(); if (valueConstraint != null) { try { XmlAnySimpleType val = sAttrs[i].getDefaultValue(); if (!val.validate()) throw new Exception(); SchemaPropertyImpl sProp = (SchemaPropertyImpl)sType.getAttributeProperty(sAttrs[i].getName()); if (sProp != null && sProp.getDefaultText() != null) { sProp.setDefaultValue(new XmlValueRef(val)); } } catch (Exception e) { // move to 'fixed' or 'default' attribute on the attribute definition String constraintName = (sAttrs[i].isFixed() ? "fixed" : "default"); XmlObject constraintLocation = location; if (attrLocation != null) { constraintLocation = attrLocation.selectAttribute("", constraintName); if (constraintLocation == null) constraintLocation = attrLocation; } StscState.get().error(XmlErrorCodes.ATTR_PROPERTIES$CONSTRAINT_VALID, new Object[] { QNameHelper.pretty(sAttrs[i].getName()), constraintName, valueConstraint, QNameHelper.pretty(sAttrs[i].getType().getName()) }, constraintLocation); } } } } } checkElementDefaults(sType.getContentModel(), location, sType); } /** * Checks the default values of elements.

* Also checks that the type of elements is not one of ID, IDREF, IDREFS, ENTITY, ENTITIES or * NOTATION as per XMLSchema part 2. * @param model * @param location * @param parentType */ private static void checkElementDefaults(SchemaParticle model, XmlObject location, SchemaType parentType) { if (model == null) return; switch (model.getParticleType()) { case SchemaParticle.SEQUENCE: case SchemaParticle.CHOICE: case SchemaParticle.ALL: SchemaParticle[] children = model.getParticleChildren(); for (int i = 0; i < children.length; i++) { checkElementDefaults(children[i], location, parentType); } break; case SchemaParticle.ELEMENT: String valueConstraint = model.getDefaultText(); if (valueConstraint != null) { if (model.getType().isSimpleType() || model.getType().getContentType() == SchemaType.SIMPLE_CONTENT) { try { XmlAnySimpleType val = model.getDefaultValue(); XmlOptions opt = new XmlOptions(); opt.put(XmlOptions.VALIDATE_TEXT_ONLY); if (!val.validate(opt)) throw new Exception(); SchemaPropertyImpl sProp = (SchemaPropertyImpl)parentType.getElementProperty(model.getName()); if (sProp != null && sProp.getDefaultText() != null) { sProp.setDefaultValue(new XmlValueRef(val)); } } catch (Exception e) { // move to 'fixed' or 'default' attribute on the element definition String constraintName = (model.isFixed() ? "fixed" : "default"); XmlObject constraintLocation = location.selectAttribute("", constraintName); StscState.get().error(XmlErrorCodes.ELEM_PROPERTIES$CONSTRAINT_VALID, new Object[] { QNameHelper.pretty(model.getName()), constraintName, valueConstraint, QNameHelper.pretty(model.getType().getName()) }, (constraintLocation==null ? location : constraintLocation)); } } else if (model.getType().getContentType() == SchemaType.MIXED_CONTENT) { if (!model.getType().getContentModel().isSkippable()) { String constraintName = (model.isFixed() ? "fixed" : "default"); XmlObject constraintLocation = location.selectAttribute("", constraintName); StscState.get().error(XmlErrorCodes.ELEM_DEFAULT_VALID$MIXED_AND_EMPTIABLE, new Object[] { QNameHelper.pretty(model.getName()), constraintName, valueConstraint }, (constraintLocation==null ? location : constraintLocation)); } else { // Element Default Valid (Immediate): cos-valid-default.2.2.2 // no need to validate the value; type is a xs:string SchemaPropertyImpl sProp = (SchemaPropertyImpl)parentType.getElementProperty(model.getName()); if (sProp != null && sProp.getDefaultText() != null) { sProp.setDefaultValue(new XmlValueRef(XmlString.type.newValue(valueConstraint))); } } } else if (model.getType().getContentType() == SchemaType.ELEMENT_CONTENT) { XmlObject constraintLocation = location.selectAttribute("", "default"); StscState.get().error(XmlErrorCodes.ELEM_DEFAULT_VALID$SIMPLE_TYPE_OR_MIXED, new Object[] { QNameHelper.pretty(model.getName()), valueConstraint, "element" }, (constraintLocation==null ? location : constraintLocation)); } else if (model.getType().getContentType() == SchemaType.EMPTY_CONTENT) { XmlObject constraintLocation = location.selectAttribute("", "default"); StscState.get().error(XmlErrorCodes.ELEM_DEFAULT_VALID$SIMPLE_TYPE_OR_MIXED, new Object[] { QNameHelper.pretty(model.getName()), valueConstraint, "empty" }, (constraintLocation==null ? location : constraintLocation)); } } // Checks if the type is one of the "attribute-specific" types String warningType = null; if (BuiltinSchemaTypeSystem.ST_ID.isAssignableFrom(model.getType())) warningType = BuiltinSchemaTypeSystem.ST_ID.getName().getLocalPart(); else if (BuiltinSchemaTypeSystem.ST_IDREF.isAssignableFrom(model.getType())) warningType = BuiltinSchemaTypeSystem.ST_IDREF.getName().getLocalPart(); else if (BuiltinSchemaTypeSystem.ST_IDREFS.isAssignableFrom(model.getType())) warningType = BuiltinSchemaTypeSystem.ST_IDREFS.getName().getLocalPart(); else if (BuiltinSchemaTypeSystem.ST_ENTITY.isAssignableFrom(model.getType())) warningType = BuiltinSchemaTypeSystem.ST_ENTITY.getName().getLocalPart(); else if (BuiltinSchemaTypeSystem.ST_ENTITIES.isAssignableFrom(model.getType())) warningType = BuiltinSchemaTypeSystem.ST_ENTITIES.getName().getLocalPart(); else if (BuiltinSchemaTypeSystem.ST_NOTATION.isAssignableFrom(model.getType())) { if (model.getType().getBuiltinTypeCode() == SchemaType.BTC_NOTATION) { StscState.get().recover(XmlErrorCodes.ELEM_NOTATION_TYPE_FORBIDDEN, new Object[]{ QNameHelper.pretty(model.getName()) }, ((SchemaLocalElementImpl) model)._parseObject == null ? location : ((SchemaLocalElementImpl) model)._parseObject.selectAttribute("", "type")); } else { if (model.getType().getSimpleVariety() == SchemaType.UNION) { SchemaType[] members = model.getType().getUnionConstituentTypes(); for (int i = 0; i < members.length; i++) if (members[i].getBuiltinTypeCode() == SchemaType.BTC_NOTATION) StscState.get().recover(XmlErrorCodes.ELEM_NOTATION_TYPE_FORBIDDEN, new Object[]{ QNameHelper.pretty(model.getName()) }, ((SchemaLocalElementImpl) model)._parseObject == null ? location : ((SchemaLocalElementImpl) model)._parseObject.selectAttribute("", "type")); } warningType = BuiltinSchemaTypeSystem.ST_NOTATION.getName().getLocalPart(); } // Check that the Schema in which this is present doesn't have a targetNS boolean hasNS; SchemaType t = parentType; while (t.getOuterType() != null) t = t.getOuterType(); if (t.isDocumentType()) hasNS = t.getDocumentElementName().getNamespaceURI().length() > 0; else hasNS = t.getName().getNamespaceURI().length() > 0; if (hasNS) StscState.get().warning(XmlErrorCodes.ELEM_COMPATIBILITY_TARGETNS, new Object[] {QNameHelper.pretty(model.getName()) }, ((SchemaLocalElementImpl) model)._parseObject == null ? location : ((SchemaLocalElementImpl) model)._parseObject); } if (warningType != null) StscState.get().warning(XmlErrorCodes.ELEM_COMPATIBILITY_TYPE, new Object[] { QNameHelper.pretty(model.getName()), warningType }, ((SchemaLocalElementImpl) model)._parseObject == null ? location : ((SchemaLocalElementImpl) model)._parseObject.selectAttribute("", "type")); break; default: // nothing to do. break; } } /** * The following code only checks rule #5 of http://www.w3.org/TR/xmlschema-1/#derivation-ok-restriction * (Everything else can and should be done in StscResolver, because we can give more detailed line # info there */ public static boolean checkRestriction(SchemaTypeImpl sType) { if (sType.getDerivationType() == SchemaType.DT_RESTRICTION && !sType.isSimpleType()) { StscState state = StscState.get(); // we don't remember very precise line number information, but it's better than nothin. XmlObject location = sType.getParseObject(); SchemaType baseType = sType.getBaseType(); if (baseType.isSimpleType()) { state.error(XmlErrorCodes.SCHEMA_COMPLEX_TYPE$COMPLEX_CONTENT, new Object[] { QNameHelper.pretty(baseType.getName()) }, location); return false; } // 5 The appropriate case among the following must be true: switch (sType.getContentType()) { case SchemaType.SIMPLE_CONTENT: // 5.1 If the {content type} of the complex type definition is a simple type definition, then one of the following must be true: switch (baseType.getContentType()) { case SchemaType.SIMPLE_CONTENT: // 5.1.1 The {content type} of the {base type definition} must be a simple type definition of which the {content type} is a �valid restriction� as defined in Derivation Valid (Restriction, Simple) (�3.14.6). SchemaType cType = sType.getContentBasedOnType(); if (cType != baseType) { // We have to check that the contentType is legally derived // from the base simple type in the hierarchy SchemaType bType = baseType; while (bType != null && !bType.isSimpleType()) bType = bType.getContentBasedOnType(); if (bType != null && !bType.isAssignableFrom(cType)) { state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$SC_NOT_DERIVED, null, location); return false; } } break; case SchemaType.MIXED_CONTENT: // 5.1.2 The {base type definition} must be mixed and have a particle which is �emptiable� as defined in Particle Emptiable (�3.9.6). if (baseType.getContentModel() != null && !baseType.getContentModel().isSkippable()) { state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$SC_AND_MIXED_EMPTIABLE, null, location); return false; } break; default: state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$SC_AND_SIMPLE_TYPE_OR_MIXED, null, location); return false; } break; case SchemaType.EMPTY_CONTENT: // 5.2 If the {content type} of the complex type itself is empty , then one of the following must be true: switch (baseType.getContentType()) { case SchemaType.EMPTY_CONTENT: // 5.2.1 The {content type} of the {base type definition} must also be empty. break; case SchemaType.MIXED_CONTENT: case SchemaType.ELEMENT_CONTENT: // 5.2.2 The {content type} of the {base type definition} must be elementOnly or mixed and have a particle which is �emptiable� as defined in Particle Emptiable (�3.9.6). if (baseType.getContentModel() != null && !baseType.getContentModel().isSkippable()) { state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$EMPTY_AND_ELEMENT_OR_MIXED_EMPTIABLE, null, location); return false; } break; default: state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$EMPTY_AND_NOT_SIMPLE, null, location); return false; } break; case SchemaType.MIXED_CONTENT: // 5.3 If the {content type} of the {base type definition} is mixed... if (baseType.getContentType() != SchemaType.MIXED_CONTENT) { state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$ELEMENT_OR_MIXED_AND_MIXED, null, location); return false; } // FALLTHROUGH case SchemaType.ELEMENT_CONTENT: // 5.3 ... or the {content type} of the complex type definition itself is element-only,... if (baseType.getContentType() == SchemaType.EMPTY_CONTENT) { state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$ELEMENT_OR_MIXED_AND_EMPTY, null, location); return false; } if (baseType.getContentType() == SchemaType.SIMPLE_CONTENT) { state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$ELEMENT_OR_MIXED_AND_SIMPLE, null, location); return false; } // 5.3 ... then the particle of the complex type definition itself must be a �valid restriction� of the particle of the {content type} of the {base type definition} SchemaParticle baseModel = baseType.getContentModel(); SchemaParticle derivedModel = sType.getContentModel(); if ( derivedModel == null && sType.getDerivationType()==SchemaType.DT_RESTRICTION ) { // it is ok to have an empty contentModel if it's a restriction // see Particle Valid (Restriction) (3.9.6) all three bulets 2.2.1 return true; } else if (baseModel == null || derivedModel == null) { XBeanDebug.logStackTrace("Null models that weren't caught by EMPTY_CONTENT: " + baseType + " (" + baseModel + "), " + sType + " (" + derivedModel + ")"); state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$ELEMENT_OR_MIXED_AND_VALID, null, location); return false; } // 5.3 ... as defined in Particle Valid (Restriction) (�3.9.6). List errors = new ArrayList(); boolean isValid = isParticleValidRestriction(baseModel, derivedModel, errors, location); if (!isValid) { // we only add the last error, because isParticleValidRestriction may add errors // to the collection that it later changes its mind about, or it may (inadvertently) // forget to describe an error into the collection.... if (errors.size() == 0) state.error(XmlErrorCodes.COMPLEX_TYPE_RESTRICTION$ELEMENT_OR_MIXED_AND_VALID, null, location); else state.getErrorListener().add(errors.get(errors.size() - 1)); //state.getErrorListener().addAll(errors); return false; // KHK: should return false, right? } } } return true; } /** * This function takes in two schema particle types, a baseModel, and a derived model and returns true if the * derivedModel can be egitimately be used for restriction. Errors are put into the errors collections. * @param baseModel - The base schema particle * @param derivedModel - The derived (restricted) schema particle * @param errors - Invalid restriction errors are put into this collection * @param context * @return boolean, true if valid restruction, false if invalid restriction * @ */ public static boolean isParticleValidRestriction(SchemaParticle baseModel, SchemaParticle derivedModel, Collection errors, XmlObject context) { boolean restrictionValid = false; // 1 They are the same particle. if (baseModel.equals(derivedModel)) { restrictionValid = true; } else { // Implement table defined in schema spec on restrictions at: // http://www.w3.org/TR/xmlschema-1/#cos-particle-restrict switch (baseModel.getParticleType()) { case SchemaParticle.ELEMENT: switch (derivedModel.getParticleType()) { case SchemaParticle.ELEMENT: restrictionValid = nameAndTypeOK((SchemaLocalElement) baseModel, (SchemaLocalElement) derivedModel, errors, context); break; case SchemaParticle.WILDCARD: case SchemaParticle.ALL: case SchemaParticle.CHOICE: case SchemaParticle.SEQUENCE: errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION$INVALID_RESTRICTION, new Object[] { printParticle(derivedModel), printParticle(baseModel) }, context)); restrictionValid = false; break; default: assert false : XBeanDebug.logStackTrace("Unknown schema type for Derived Type"); } break; case SchemaParticle.WILDCARD: switch (derivedModel.getParticleType()) { case SchemaParticle.ELEMENT: restrictionValid = nsCompat(baseModel, (SchemaLocalElement) derivedModel, errors, context); break; case SchemaParticle.WILDCARD: restrictionValid = nsSubset(baseModel, derivedModel, errors, context); break; case SchemaParticle.ALL: restrictionValid = nsRecurseCheckCardinality(baseModel, derivedModel, errors, context); break; case SchemaParticle.CHOICE: restrictionValid = nsRecurseCheckCardinality(baseModel, derivedModel, errors, context); break; case SchemaParticle.SEQUENCE: restrictionValid = nsRecurseCheckCardinality(baseModel, derivedModel, errors, context); break; default: assert false : XBeanDebug.logStackTrace("Unknown schema type for Derived Type"); } break; case SchemaParticle.ALL: switch (derivedModel.getParticleType()) { case SchemaParticle.ELEMENT: restrictionValid = recurseAsIfGroup(baseModel, derivedModel, errors, context); break; case SchemaParticle.WILDCARD: case SchemaParticle.CHOICE: errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION$INVALID_RESTRICTION, new Object[] { printParticle(derivedModel), printParticle(baseModel) }, context)); restrictionValid = false; break; case SchemaParticle.ALL: restrictionValid = recurse(baseModel, derivedModel, errors, context); break; case SchemaParticle.SEQUENCE: restrictionValid = recurseUnordered(baseModel, derivedModel, errors, context); break; default: assert false : XBeanDebug.logStackTrace("Unknown schema type for Derived Type"); } break; case SchemaParticle.CHOICE: switch (derivedModel.getParticleType()) { case SchemaParticle.ELEMENT: restrictionValid = recurseAsIfGroup(baseModel, derivedModel, errors, context); break; case SchemaParticle.WILDCARD: case SchemaParticle.ALL: errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION$INVALID_RESTRICTION, new Object[] { printParticle(derivedModel), printParticle(baseModel) }, context)); restrictionValid = false; break; case SchemaParticle.CHOICE: restrictionValid = recurseLax(baseModel, derivedModel, errors, context); break; case SchemaParticle.SEQUENCE: restrictionValid = mapAndSum(baseModel, derivedModel, errors, context); break; default: assert false : XBeanDebug.logStackTrace("Unknown schema type for Derived Type"); } break; case SchemaParticle.SEQUENCE: switch (derivedModel.getParticleType()) { case SchemaParticle.ELEMENT: restrictionValid = recurseAsIfGroup(baseModel, derivedModel, errors, context); break; case SchemaParticle.WILDCARD: case SchemaParticle.ALL: case SchemaParticle.CHOICE: errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION$INVALID_RESTRICTION, new Object[] { printParticle(derivedModel), printParticle(baseModel) }, context)); restrictionValid = false; break; case SchemaParticle.SEQUENCE: restrictionValid = recurse(baseModel, derivedModel, errors, context); break; default: assert false : XBeanDebug.logStackTrace("Unknown schema type for Derived Type"); } break; default: assert false : XBeanDebug.logStackTrace("Unknown schema type for Base Type"); } } return restrictionValid; } private static boolean mapAndSum(SchemaParticle baseModel, SchemaParticle derivedModel, Collection errors, XmlObject context) { // mapAndSum is call if base: CHOICE, derived: SEQUENCE assert baseModel.getParticleType() == SchemaParticle.CHOICE; assert derivedModel.getParticleType() == SchemaParticle.SEQUENCE; boolean mapAndSumValid = true; // Schema Component Constraint: Particle Derivation OK (Sequence:Choice -- MapAndSum) // For a sequence group particle to be a �valid restriction� of a choice group particle all of the following // must be true: // 1 There is a complete functional mapping from the particles in the {particles} of R to the particles in the // {particles} of B such that each particle in the {particles} of R is a �valid restriction� of the particle in // the {particles} of B it maps to as defined by Particle Valid (Restriction) (�3.9.6). // interpretation: each particle child in derived should have a match in base. // 2 The pair consisting of the product of the {min occurs} of R and the length of its {particles} and unbounded // if {max occurs} is unbounded otherwise the product of the {max occurs} of R and the length of its {particles} // is a valid restriction of B's occurrence range as defined by Occurrence Range OK (�3.9.6). // NOTE: This clause is in principle more restrictive than absolutely necessary, but in practice will cover // all the likely cases, and is much easier to specify than the fully general version. // NOTE: This case allows the "unfolding" of iterated disjunctions into sequences. It may be particularly useful // when the disjunction is an implicit one arising from the use of substitution groups. // Map step - for each member of the derived model's particle children search base model's particle children // for match SchemaParticle[] derivedParticleArray = derivedModel.getParticleChildren(); SchemaParticle[] baseParticleArray = baseModel.getParticleChildren(); for (int i = 0; i < derivedParticleArray.length; i++) { SchemaParticle derivedParticle = derivedParticleArray[i]; boolean foundMatch = false; for (int j = 0; j < baseParticleArray.length; j++) { SchemaParticle baseParticle = baseParticleArray[j]; // recurse to check if there is a match if (isParticleValidRestriction(baseParticle, derivedParticle, errors, context)) { // if there is a match then no need to check base particles anymore foundMatch = true; break; } } if (!foundMatch) { mapAndSumValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_MAP_AND_SUM$MAP, new Object[] { printParticle(derivedParticle) }, context)); // KHK: if we don't return false now, this error may get swallowed by an error produced below return false; //break; } } // Sum step BigInteger derivedRangeMin = derivedModel.getMinOccurs().multiply(BigInteger.valueOf(derivedModel.getParticleChildren().length)); BigInteger derivedRangeMax = null; BigInteger UNBOUNDED = null; if (derivedModel.getMaxOccurs() == UNBOUNDED) { derivedRangeMax = null; } else { derivedRangeMax = derivedModel.getMaxOccurs().multiply(BigInteger.valueOf(derivedModel.getParticleChildren().length)); } // Now check derivedRange (derivedRangeMin and derivedRangeMax) against base model occurrence range // Schema Component Constraint: Occurrence Range OK // For a particle's occurrence range to be a valid restriction of another's occurrence range all of the following must be true: // 1 Its {min occurs} is greater than or equal to the other's {min occurs}. // 2 one of the following must be true: // 2.1 The other's {max occurs} is unbounded. // 2.2 Both {max occurs} are numbers, and the particle's is less than or equal to the other's. if (derivedRangeMin.compareTo(baseModel.getMinOccurs()) < 0) { mapAndSumValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_MAP_AND_SUM$SUM_MIN_OCCURS_GTE_MIN_OCCURS, new Object[] { derivedRangeMin.toString(), baseModel.getMinOccurs().toString() }, context)); } else if (baseModel.getMaxOccurs() != UNBOUNDED && (derivedRangeMax == UNBOUNDED || derivedRangeMax.compareTo(baseModel.getMaxOccurs()) > 0)) { mapAndSumValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_MAP_AND_SUM$SUM_MAX_OCCURS_LTE_MAX_OCCURS, new Object[] { derivedRangeMax == UNBOUNDED ? "unbounded" : derivedRangeMax.toString(), baseModel.getMaxOccurs().toString() }, context)); } return mapAndSumValid; } private static boolean recurseAsIfGroup(SchemaParticle baseModel, SchemaParticle derivedModel, Collection errors, XmlObject context) { // recurseAsIfGroup is called if: // base: ALL, derived: ELEMENT // base: CHOICE, derived: ELEMENT // base: SEQUENCE, derived: ELEMENT assert (baseModel.getParticleType() == SchemaParticle.ALL && derivedModel.getParticleType() == SchemaParticle.ELEMENT) || (baseModel.getParticleType() == SchemaParticle.CHOICE && derivedModel.getParticleType() == SchemaParticle.ELEMENT) || (baseModel.getParticleType() == SchemaParticle.SEQUENCE && derivedModel.getParticleType() == SchemaParticle.ELEMENT); // Schema Component Constraint: Particle Derivation OK (Elt:All/Choice/Sequence -- RecurseAsIfGroup) // For an element declaration particle to be a �valid restriction� of a group particle // (all, choice or sequence) a group particle of the variety corresponding to B's, with {min occurs} and // {max occurs} of 1 and with {particles} consisting of a single particle the same as the element declaration // must be a �valid restriction� of the group as defined by Particle Derivation OK // (All:All,Sequence:Sequence -- Recurse) (�3.9.6), Particle Derivation OK (Choice:Choice -- RecurseLax) // (�3.9.6) or Particle Derivation OK (All:All,Sequence:Sequence -- Recurse) (�3.9.6), depending on whether // the group is all, choice or sequence // interpretation: make a fake group of the right type, with min occurs and max occurs of 1 SchemaParticleImpl asIfPart = new SchemaParticleImpl(); asIfPart.setParticleType(baseModel.getParticleType()); asIfPart.setMinOccurs(BigInteger.ONE); asIfPart.setMaxOccurs(BigInteger.ONE); asIfPart.setParticleChildren(new SchemaParticle[] { derivedModel }); // the recurse return isParticleValidRestriction(baseModel, asIfPart, errors, context); } private static boolean recurseLax(SchemaParticle baseModel, SchemaParticle derivedModel, Collection errors, XmlObject context) { // recurseLax is called if base: CHOICE, derived: CHOICE assert baseModel.getParticleType() == SchemaParticle.CHOICE && derivedModel.getParticleType() == SchemaParticle.CHOICE; boolean recurseLaxValid = true; //Schema Component Constraint: Particle Derivation OK (Choice:Choice -- RecurseLax) // For a choice group particle to be a �valid restriction� of another choice group particle all of the // following must be true: // 1 R's occurrence range is a valid restriction of B's occurrence range as defined by Occurrence // Range OK (�3.9.6); // 2 There is a complete �order-preserving� functional mapping from the particles in the {particles} of R // to the particles in the {particles} of B such that each particle in the {particles} of R is a // �valid restriction� of the particle in the {particles} of B it maps to as defined by // Particle Valid (Restriction) (�3.9.6). // NOTE: Although the �validation� semantics of a choice group does not depend on the order of its particles, // derived choice groups are required to match the order of their base in order to simplify // checking that the derivation is OK. // interpretation: check derived choices for match in order, must get an in order match on a base particle, // don't need to check if base particles are skippable. a lot like recurse if (!occurrenceRangeOK(baseModel, derivedModel, errors, context)) { return false; } // cycle thru both derived particle children and base particle children looking for matches // if the derived particle does not match the base particle then base particle can be skipped SchemaParticle[] derivedParticleArray = derivedModel.getParticleChildren(); SchemaParticle[] baseParticleArray = baseModel.getParticleChildren(); int i = 0, j = 0; for (; i < derivedParticleArray.length && j < baseParticleArray.length;) { SchemaParticle derivedParticle = derivedParticleArray[i]; SchemaParticle baseParticle = baseParticleArray[j]; // try to match the two particles by recursing if (isParticleValidRestriction(baseParticle, derivedParticle, errors, context)) { // cool found a match, increment both indexes i++; j++; } else { // did not match, increment the base particle array index only // Ok, let's skip this base particle, increment base particle array index only j++; } } // ok, got to the end of one of the arrays // if at end of base particle array and not at the end of derived particle array then remaining derived // particles must not match if (i < derivedParticleArray.length) { recurseLaxValid = false; //String message = "Found derived particles that are not matched in the base content model."; //errors.add(XmlError.forObject(formatDerivedMappingError(message, baseModel, derivedModel), context)); errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_RECURSE_LAX$MAP, new Object[] { printParticles(baseParticleArray, i) }, context)); } return recurseLaxValid; } private static boolean recurseUnordered(SchemaParticle baseModel, SchemaParticle derivedModel, Collection errors, XmlObject context) { // recurseUnorder is called when base: ALL and derived: SEQ assert baseModel.getParticleType() == SchemaParticle.ALL && derivedModel.getParticleType() == SchemaParticle.SEQUENCE; boolean recurseUnorderedValid = true; // Schema Component Constraint: Particle Derivation OK (Sequence:All -- RecurseUnordered) // For a sequence group particle to be a �valid restriction� of an all group particle all of the // following must be true: // 1 R's occurrence range is a valid restriction of B's occurrence range as defined by // Occurrence Range OK (�3.9.6). // 2 There is a complete functional mapping from the particles in the {particles} of R to the particles // in the {particles} of B such that all of the following must be true: // 2.1 No particle in the {particles} of B is mapped to by more than one of the particles in // the {particles} of R; // 2.2 Each particle in the {particles} of R is a �valid restriction� of the particle in the {particles} of B // it maps to as defined by Particle Valid (Restriction) (�3.9.6); // 2.3 All particles in the {particles} of B which are not mapped to by any particle in the {particles} // of R are �emptiable� as defined by Particle Emptiable (�3.9.6). // NOTE: Although this clause allows reordering, because of the limits on the contents of all groups the // checking process can still be deterministic. // 1, 2.2, and 2.3 are the same as recurse, so do 2.1 and then call recurse if (!occurrenceRangeOK(baseModel, derivedModel, errors, context)) { return false; } // read baseParticle array QNames into hashmap SchemaParticle[] baseParticles = baseModel.getParticleChildren(); HashMap baseParticleMap = new HashMap(10); Object MAPPED = new Object(); // Initialize the hashmap for (int i = 0; i < baseParticles.length; i++) baseParticleMap.put(baseParticles[i].getName(), baseParticles[i]); // go thru the sequence (derived model's children) and check off from base particle map SchemaParticle[] derivedParticles = derivedModel.getParticleChildren(); for (int i = 0; i < derivedParticles.length; i++) { Object baseParticle = baseParticleMap.get(derivedParticles[i].getName()); if (baseParticle == null) { recurseUnorderedValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_RECURSE_UNORDERED$MAP, new Object[] { printParticle(derivedParticles[i]) }, context )); break; } else { // got a match if (baseParticle == MAPPED) { // whoa, this base particle has already been matched (see 2.1 above) recurseUnorderedValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_RECURSE_UNORDERED$MAP_UNIQUE, new Object[] { printParticle(derivedParticles[i]) }, context )); break; } else { SchemaParticle matchedBaseParticle = (SchemaParticle)baseParticle; if (derivedParticles[i].getMaxOccurs() == null || derivedParticles[i].getMaxOccurs().compareTo(BigInteger.ONE) > 0) { // no derived particles can have a max occurs greater than 1 recurseUnorderedValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_RECURSE_UNORDERED$MAP_MAX_OCCURS_1, new Object[] { printParticle(derivedParticles[i]), printMaxOccurs(derivedParticles[i].getMinOccurs()) }, context)); break; } if (!isParticleValidRestriction(matchedBaseParticle, derivedParticles[i], errors, context)) { // already have an error recurseUnorderedValid = false; break; } // everything is cool, got a match, update to MAPPED baseParticleMap.put(derivedParticles[i].getName(), MAPPED); } } } // if everything is cool so far then check to see if any base particles are not matched if (recurseUnorderedValid) { // get all the hashmap keys and loop thru looking for NOT_MAPPED Set baseParticleCollection = baseParticleMap.keySet(); for (Iterator iterator = baseParticleCollection.iterator(); iterator.hasNext();) { QName baseParticleQName = (QName) iterator.next(); if (baseParticleMap.get(baseParticleQName) != MAPPED && !((SchemaParticle)baseParticleMap.get(baseParticleQName)).isSkippable()) { // this base particle was not mapped and is not "particle emptiable" (skippable) recurseUnorderedValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_RECURSE_UNORDERED$UNMAPPED_ARE_EMPTIABLE, new Object[] { printParticle((SchemaParticle)baseParticleMap.get(baseParticleQName)) }, context)); } } } return recurseUnorderedValid; } private static boolean recurse(SchemaParticle baseModel, SchemaParticle derivedModel, Collection errors, XmlObject context) { // recurse is called when base: ALL derived: ALL or base: SEQUENCE derived: SEQUENCE boolean recurseValid = true; // For an all or sequence group particle to be a �valid restriction� of another group particle with the same // {compositor} all of the following must be true: // 1 R's occurrence range is a valid restriction of B's occurrence range as defined by // Occurrence Range OK (�3.9.6). // 2 There is a complete �order-preserving� functional mapping from the particles in the {particles} of R to // the particles in the {particles} of B such that all of the following must be true: // 2.1 Each particle in the {particles} of R is a �valid restriction� of the particle in the {particles} // of B it maps to as defined by Particle Valid (Restriction) (�3.9.6). // 2.2 All particles in the {particles} of B which are not mapped to by any particle in the {particles} // of R are �emptiable� as defined by Particle Emptiable (�3.9.6). // NOTE: Although the �validation� semantics of an all group does not depend on the order of its particles, // derived all groups are required to match the order of their base in order to simplify checking that // the derivation is OK. // [Definition:] A complete functional mapping is order-preserving if each particle r in the domain R maps // to a particle b in the range B which follows (not necessarily immediately) the particle in the range B // mapped to by the predecessor of r, if any, where "predecessor" and "follows" are defined with respect to // the order of the lists which constitute R and B. if (!occurrenceRangeOK(baseModel, derivedModel, errors, context)) { // error message is formatted in occurrencRangeOK ... return false; } // cycle thru both derived particle children and base particle children looking for matches // if the derived particle does not match the base particle then base particle can be skipped if it is // skippable (same as "particle emptiable") otherwise is an invalid restriction. // after the derived particles have been cycled if there are any base particles left over then they // must be skippable or invalid restriction SchemaParticle[] derivedParticleArray = derivedModel.getParticleChildren(); SchemaParticle[] baseParticleArray = baseModel.getParticleChildren(); int i = 0, j = 0; for (; i < derivedParticleArray.length && j < baseParticleArray.length;) { SchemaParticle derivedParticle = derivedParticleArray[i]; SchemaParticle baseParticle = baseParticleArray[j]; // try to match the two particles by recursing if (isParticleValidRestriction(baseParticle, derivedParticle, errors, context)) { // cool found a match, increment both indexes i++; j++; } else { // did not match, increment the base particle array index only // that's ok if the base particle is skippable if (baseParticle.isSkippable()) { // Ok, let's skip this base particle, increment base particle array index only j++; } else { // whoa, particles are not valid restrictions and base is not skippable - ERROR recurseValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_RECURSE$MAP_VALID, new Object[] { printParticle(derivedParticle), printParticle(derivedModel), printParticle(baseParticle), printParticle(baseModel) }, context)); break; } } } // ok, got to the end of one of the arrays // if at end of base particle array and not at the end of derived particle array then remaining derived // particles must not match if (i < derivedParticleArray.length) { recurseValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_RECURSE$MAP, new Object[] { printParticle(derivedModel), printParticle(baseModel), printParticles(derivedParticleArray, i) }, context)); } else { // if at end of derived particle array and not at end of base particle array then chck remaining // base particles to assure they are skippable if (j < baseParticleArray.length) { ArrayList particles = new ArrayList(baseParticleArray.length); for (int k = j; k < baseParticleArray.length; k++) { if (!baseParticleArray[k].isSkippable()) { particles.add(baseParticleArray[k]); } } if (particles.size() > 0) { recurseValid = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_RECURSE$UNMAPPED_ARE_EMPTIABLE, new Object[] { printParticle(baseModel), printParticle(derivedModel), printParticles(particles)}, context)); } } } return recurseValid; } private static boolean nsRecurseCheckCardinality(SchemaParticle baseModel, SchemaParticle derivedModel, Collection errors, XmlObject context) { // nsRecurseCheckCardinality is called when: // base: ANY, derived: ALL // base: ANY, derived: CHOICE // base: ANY, derived: SEQUENCE assert baseModel.getParticleType() == SchemaParticle.WILDCARD; assert (derivedModel.getParticleType() == SchemaParticle.ALL) || (derivedModel.getParticleType() == SchemaParticle.CHOICE) || (derivedModel.getParticleType() == SchemaParticle.SEQUENCE); boolean nsRecurseCheckCardinality = true; // For a group particle to be a �valid restriction� of a wildcard particle all of the following must be true: // 1 Every member of the {particles} of the group is a �valid restriction� of the wildcard as defined by Particle Valid (Restriction) (�3.9.6). // Note: not positive what this means. Interpreting to mean that every particle of the group must adhere to wildcard derivation rules // in a recursive manner // Loop thru the children particles of the group and invoke the appropriate function to check for wildcard restriction validity // BAU - an errata should be submitted on this clause of the spec, because the // spec makes no sense, as the xstc particlesR013.xsd test exemplifies. // what we _should_ so is an "as if" on the wildcard, allowing it minOccurs="0" maxOccurs="unbounded" // before recursing SchemaParticleImpl asIfPart = new SchemaParticleImpl(); asIfPart.setParticleType(baseModel.getParticleType()); asIfPart.setWildcardProcess(baseModel.getWildcardProcess()); asIfPart.setWildcardSet(baseModel.getWildcardSet()); asIfPart.setMinOccurs(BigInteger.ZERO); asIfPart.setMaxOccurs(null); asIfPart.setTransitionRules(baseModel.getWildcardSet(), true); asIfPart.setTransitionNotes(baseModel.getWildcardSet(), true); SchemaParticle[] particleChildren = derivedModel.getParticleChildren(); for (int i = 0; i < particleChildren.length; i++) { SchemaParticle particle = particleChildren[i]; switch (particle.getParticleType()) { case SchemaParticle.ELEMENT: // Check for valid Wildcard/Element derivation nsRecurseCheckCardinality = nsCompat(asIfPart, (SchemaLocalElement) particle, errors, context); break; case SchemaParticle.WILDCARD: // Check for valid Wildcard/Wildcard derivation nsRecurseCheckCardinality = nsSubset(asIfPart, particle, errors, context); break; case SchemaParticle.ALL: case SchemaParticle.CHOICE: case SchemaParticle.SEQUENCE: // Check for valid Wildcard/Group derivation nsRecurseCheckCardinality = nsRecurseCheckCardinality(asIfPart, particle, errors, context); break; } // If any particle is invalid then break the loop if (!nsRecurseCheckCardinality) { break; } } // 2 The effective total range of the group, as defined by Effective Total Range (all and sequence) (�3.8.6) // (if the group is all or sequence) or Effective Total Range (choice) (�3.8.6) (if it is choice) is a valid // restriction of B's occurrence range as defined by Occurrence Range OK (�3.9.6). if (nsRecurseCheckCardinality) { nsRecurseCheckCardinality = checkGroupOccurrenceOK(baseModel, derivedModel, errors, context); } return nsRecurseCheckCardinality; } private static boolean checkGroupOccurrenceOK(SchemaParticle baseModel, SchemaParticle derivedModel, Collection errors, XmlObject context) { boolean groupOccurrenceOK = true; BigInteger minRange = BigInteger.ZERO; BigInteger maxRange = BigInteger.ZERO; switch (derivedModel.getParticleType()) { case SchemaParticle.ALL: case SchemaParticle.SEQUENCE: minRange = getEffectiveMinRangeAllSeq(derivedModel); maxRange = getEffectiveMaxRangeAllSeq(derivedModel); break; case SchemaParticle.CHOICE: minRange = getEffectiveMinRangeChoice(derivedModel); maxRange = getEffectiveMaxRangeChoice(derivedModel); break; } // Check min occurs for validity // derived min occurs is valid if its {min occurs} is greater than or equal to the other's {min occurs}. if (minRange.compareTo(baseModel.getMinOccurs()) < 0) { groupOccurrenceOK = false; errors.add(XmlError.forObject(XmlErrorCodes.OCCURRENCE_RANGE$MIN_GTE_MIN, new Object[] { printParticle(derivedModel), printParticle(baseModel) }, context)); } // Check max occurs for validity // one of the following must be true: // The base model's {max occurs} is unbounded. // or both {max occurs} are numbers, and the particle's is less than or equal to the other's BigInteger UNBOUNDED = null; if (baseModel.getMaxOccurs() != UNBOUNDED) { if (maxRange == UNBOUNDED) { groupOccurrenceOK = false; errors.add(XmlError.forObject(XmlErrorCodes.OCCURRENCE_RANGE$MAX_LTE_MAX, new Object[] { printParticle(derivedModel), printParticle(baseModel) }, context)); } else { if (maxRange.compareTo(baseModel.getMaxOccurs()) > 0) { groupOccurrenceOK = false; errors.add(XmlError.forObject(XmlErrorCodes.OCCURRENCE_RANGE$MAX_LTE_MAX, new Object[] { printParticle(derivedModel), printParticle(baseModel) }, context)); } } } return groupOccurrenceOK; } private static BigInteger getEffectiveMaxRangeChoice(SchemaParticle derivedModel) { BigInteger maxRange = BigInteger.ZERO; BigInteger UNBOUNDED = null; // Schema Component Constraint: Effective Total Range (choice) // The effective total range of a particle whose {term} is a group whose {compositor} is choice // is a pair of minimum and maximum, as follows: // MAXIMUM // 1) unbounded if the {max occurs} of any wildcard or element declaration particle in the group's {particles} or // the maximum part of the effective total range of any of the group particles in the group's {particles} is // unbounded (note: seems to be the same as Max Range All or Sequence), // or 2) if any of those is non-zero and the {max occurs} of the particle itself is unbounded, // otherwise 3) the product of the particle's {max occurs} and the maximum of the {max occurs} of every // wildcard or element declaration particle in the group's {particles} and the *maximum* (note: this is the difference // between MaxRange Choice ans MaxRange All or Sequence) part of the // effective total range of each of the group particles in the group's {particles} // (or 0 if there are no {particles}). boolean nonZeroParticleChildFound = false; BigInteger maxOccursInWildCardOrElement = BigInteger.ZERO; BigInteger maxOccursInGroup = BigInteger.ZERO; SchemaParticle[] particleChildren = derivedModel.getParticleChildren(); for (int i = 0; i < particleChildren.length; i++) { SchemaParticle particle = particleChildren[i]; switch (particle.getParticleType()) { case SchemaParticle.WILDCARD: case SchemaParticle.ELEMENT: // if unbounded then maxoccurs will be null if (particle.getMaxOccurs() == UNBOUNDED) { maxRange = UNBOUNDED; } else { if (particle.getIntMaxOccurs() > 0) { // show tht at least one non-zero particle is found for later test nonZeroParticleChildFound = true; if (particle.getMaxOccurs().compareTo(maxOccursInWildCardOrElement) > 0) { maxOccursInWildCardOrElement = particle.getMaxOccurs(); } } } break; case SchemaParticle.ALL: case SchemaParticle.SEQUENCE: maxRange = getEffectiveMaxRangeAllSeq(particle); if (maxRange != UNBOUNDED) { // keep highest maxoccurs found if (maxRange.compareTo(maxOccursInGroup) > 0) { maxOccursInGroup = maxRange; } } break; case SchemaParticle.CHOICE: maxRange = getEffectiveMaxRangeChoice(particle); if (maxRange != UNBOUNDED) { // keep highest maxoccurs found if (maxRange.compareTo(maxOccursInGroup) > 0) { maxOccursInGroup = maxRange; } } break; } // if an unbounded has been found then we are done if (maxRange == UNBOUNDED) { break; } } // 1) unbounded if the {max occurs} of any wildcard or element declaration particle in the group's {particles} or // the maximum part of the effective total range of any of the group particles in the group's {particles} is // unbounded if (maxRange != UNBOUNDED) { // 2) if any of those is non-zero and the {max occurs} of the particle itself is unbounded if (nonZeroParticleChildFound && derivedModel.getMaxOccurs() == UNBOUNDED) { maxRange = UNBOUNDED; } else { // 3) the product of the particle's {max occurs} and the maximum of the {max occurs} of every // wildcard or element declaration particle in the group's {particles} and the *maximum* // part of the effective total range of each of the group particles in the group's {particles} maxRange = derivedModel.getMaxOccurs().multiply(maxOccursInWildCardOrElement.add(maxOccursInGroup)); } } return maxRange; } private static BigInteger getEffectiveMaxRangeAllSeq(SchemaParticle derivedModel) { BigInteger maxRange = BigInteger.ZERO; BigInteger UNBOUNDED = null; // Schema Component Constraint: Effective Total Range (all and sequence) // The effective total range of a particle whose {term} is a group whose {compositor} is all or sequence is a // pair of minimum and maximum, as follows: // MAXIMUM // 1) unbounded if the {max occurs} of any wildcard or element declaration particle in the group's {particles} or // the maximum part of the effective total range of any of the group particles in the group's {particles} is // unbounded, or 2) if any of those is non-zero and the {max occurs} of the particle itself is unbounded, otherwise // 3) the product of the particle's {max occurs} and the *sum* of the {max occurs} of every wildcard or element // declaration particle in the group's {particles} and the maximum part of the effective total range of each of // the group particles in the group's {particles} (or 0 if there are no {particles}). boolean nonZeroParticleChildFound = false; BigInteger maxOccursTotal = BigInteger.ZERO; BigInteger maxOccursInGroup = BigInteger.ZERO; SchemaParticle[] particleChildren = derivedModel.getParticleChildren(); for (int i = 0; i < particleChildren.length; i++) { SchemaParticle particle = particleChildren[i]; switch (particle.getParticleType()) { case SchemaParticle.WILDCARD: case SchemaParticle.ELEMENT: // if unbounded then maxoccurs will be null if (particle.getMaxOccurs() == UNBOUNDED) { maxRange = UNBOUNDED; } else { if (particle.getIntMaxOccurs() > 0) { // show tht at least one non-zero particle is found for later test nonZeroParticleChildFound = true; maxOccursTotal = maxOccursTotal.add(particle.getMaxOccurs()); } } break; case SchemaParticle.ALL: case SchemaParticle.SEQUENCE: maxRange = getEffectiveMaxRangeAllSeq(particle); if (maxRange != UNBOUNDED) { // keep highest maxoccurs found if (maxRange.compareTo(maxOccursInGroup) > 0) { maxOccursInGroup = maxRange; } } break; case SchemaParticle.CHOICE: maxRange = getEffectiveMaxRangeChoice(particle); if (maxRange != UNBOUNDED) { // keep highest maxoccurs found if (maxRange.compareTo(maxOccursInGroup) > 0) { maxOccursInGroup = maxRange; } } break; } // if an unbounded has been found then we are done if (maxRange == UNBOUNDED) { break; } } // 1) unbounded if the {max occurs} of any wildcard or element declaration particle in the group's {particles} or // the maximum part of the effective total range of any of the group particles in the group's {particles} is // unbounded if (maxRange != UNBOUNDED) { // 2) if any of those is non-zero and the {max occurs} of the particle itself is unbounded if (nonZeroParticleChildFound && derivedModel.getMaxOccurs() == UNBOUNDED) { maxRange = UNBOUNDED; } else { // 3) the product of the particle's {max occurs} and the sum of the {max occurs} of every wildcard or element // declaration particle in the group's {particles} and the maximum part of the effective total range of each of // the group particles in the group's {particles} maxRange = derivedModel.getMaxOccurs().multiply(maxOccursTotal.add(maxOccursInGroup)); } } return maxRange; } private static BigInteger getEffectiveMinRangeChoice(SchemaParticle derivedModel) { // Schema Component Constraint: Effective Total Range (choice) // The effective total range of a particle whose {term} is a group whose {compositor} is choice is a pair of // minimum and maximum, as follows: // MINIMUM // The product of the particle's {min occurs} // and the *minimum* of the {min occurs} of every wildcard or element // declaration particle in the group's {particles} and the minimum part of the effective total range of each of // the group particles in the group's {particles} (or 0 if there are no {particles}). SchemaParticle[] particleChildren = derivedModel.getParticleChildren(); if (particleChildren.length == 0) return BigInteger.ZERO; BigInteger minRange = null; // get the minimum of every wildcard or element // total up the effective total range for each group for (int i = 0; i < particleChildren.length; i++) { SchemaParticle particle = particleChildren[i]; switch (particle.getParticleType()) { case SchemaParticle.WILDCARD: case SchemaParticle.ELEMENT: if (minRange == null || minRange.compareTo(particle.getMinOccurs()) > 0) { minRange = particle.getMinOccurs(); } break; case SchemaParticle.ALL: case SchemaParticle.SEQUENCE: BigInteger mrs = getEffectiveMinRangeAllSeq(particle); if (minRange == null || minRange.compareTo(mrs) > 0) { minRange = mrs; } break; case SchemaParticle.CHOICE: BigInteger mrc = getEffectiveMinRangeChoice(particle); if (minRange == null || minRange.compareTo(mrc) > 0) { minRange = mrc; } break; } } if (minRange == null) minRange = BigInteger.ZERO; // calculate the total minRange = derivedModel.getMinOccurs().multiply(minRange); return minRange; } private static BigInteger getEffectiveMinRangeAllSeq(SchemaParticle derivedModel) { BigInteger minRange = BigInteger.ZERO; // Schema Component Constraint: Effective Total Range (all and sequence) // The effective total range of a particle whose {term} is a group whose {compositor} is all or sequence is a // pair of minimum and maximum, as follows: // MINIMUM // The product of the particle's {min occurs} // and the *sum* of the {min occurs} of every wildcard or element // declaration particle in the group's {particles} // and the minimum part of the effective total range of each // of the group particles in the group's {particles} (or 0 if there are no {particles}). SchemaParticle[] particleChildren = derivedModel.getParticleChildren(); BigInteger particleTotalMinOccurs = BigInteger.ZERO; for (int i = 0; i < particleChildren.length; i++) { SchemaParticle particle = particleChildren[i]; switch (particle.getParticleType()) { case SchemaParticle.WILDCARD: case SchemaParticle.ELEMENT: particleTotalMinOccurs = particleTotalMinOccurs.add(particle.getMinOccurs()); break; case SchemaParticle.ALL: case SchemaParticle.SEQUENCE: particleTotalMinOccurs = particleTotalMinOccurs.add(getEffectiveMinRangeAllSeq(particle)); break; case SchemaParticle.CHOICE: particleTotalMinOccurs = particleTotalMinOccurs.add(getEffectiveMinRangeChoice(particle)); break; } } minRange = derivedModel.getMinOccurs().multiply(particleTotalMinOccurs); return minRange; } private static boolean nsSubset(SchemaParticle baseModel, SchemaParticle derivedModel, Collection errors, XmlObject context) { // nsSubset is called when base: ANY, derived: ANY assert baseModel.getParticleType() == SchemaParticle.WILDCARD; assert derivedModel.getParticleType() == SchemaParticle.WILDCARD; boolean nsSubset = false; // For a wildcard particle to be a �valid restriction� of another wildcard particle all of the following must be true: // 1 R's occurrence range must be a valid restriction of B's occurrence range as defined by Occurrence Range OK (�3.9.6). if (occurrenceRangeOK(baseModel, derivedModel, errors, context)) { // 2 R's {namespace constraint} must be an intensional subset of B's {namespace constraint} as defined // by Wildcard Subset (�3.10.6). if (baseModel.getWildcardSet().inverse().isDisjoint(derivedModel.getWildcardSet())) { nsSubset = true; } else { nsSubset = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_NS_SUBST$WILDCARD_SUBSET, new Object[] { printParticle(derivedModel), printParticle(baseModel) }, context)); } } else { nsSubset = false; // error already produced by occurrenceRangeOK //errors.add(XmlError.forObject(formatNSIsNotSubsetError(baseModel, derivedModel), context)); } return nsSubset; } private static boolean nsCompat(SchemaParticle baseModel, SchemaLocalElement derivedElement, Collection errors, XmlObject context) { // nsCompat is called when base: ANY, derived: ELEMENT assert baseModel.getParticleType() == SchemaParticle.WILDCARD; boolean nsCompat = false; // For an element declaration particle to be a �valid restriction� of a wildcard particle all of the following must be true: // 1 The element declaration's {target namespace} is �valid� with respect to the wildcard's {namespace constraint} // as defined by Wildcard allows Namespace Name (�3.10.4). if (baseModel.getWildcardSet().contains(derivedElement.getName())) { // 2 R's occurrence range is a valid restriction of B's occurrence range as defined by Occurrence Range OK (�3.9.6). if (occurrenceRangeOK(baseModel, (SchemaParticle) derivedElement, errors, context)) { nsCompat = true; } else { // error already produced by occurrenceRangeOK //errors.add(XmlError.forObject(formatOccurenceRangeMinError(baseModel, (SchemaParticle) derivedElement), context)); } } else { nsCompat = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_DERIVATION_NS_COMPAT$WILDCARD_VALID, new Object[] { printParticle((SchemaParticle)derivedElement), printParticle(baseModel) }, context)); } return nsCompat; } private static boolean nameAndTypeOK(SchemaLocalElement baseElement, SchemaLocalElement derivedElement, Collection errors, XmlObject context) { // nameAndTypeOK called when base: ELEMENT and derived: ELEMENT // Schema Component Constraint: Particle Restriction OK (Elt:Elt -- NameAndTypeOK) // 1 The declarations' {name}s and {target namespace}s are the same. if (!((SchemaParticle)baseElement).canStartWithElement(derivedElement.getName())) { errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION_NAME_AND_TYPE$NAME, new Object[] { printParticle((SchemaParticle)derivedElement), printParticle((SchemaParticle)baseElement) }, context)); return false; } // 2 Either B's {nillable} is true or R's {nillable} is false. if (!baseElement.isNillable() && derivedElement.isNillable()) { errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION_NAME_AND_TYPE$NILLABLE, new Object[] { printParticle((SchemaParticle)derivedElement), printParticle((SchemaParticle)baseElement) }, context)); return false; } // 3 R's occurrence range is a valid restriction of B's occurrence range as defined by Occurrence Range OK (�3.9.6). if (!occurrenceRangeOK((SchemaParticle) baseElement, (SchemaParticle) derivedElement, errors, context)) { // error already produced return false; } // 4 either B's declaration's {value constraint} is absent, or is not fixed, // or R's declaration's {value constraint} is fixed with the same value. if (!checkFixed(baseElement, derivedElement, errors, context)) { // error already produced return false; } // 5 R's declaration's {identity-constraint definitions} is a subset of B's declaration's {identity-constraint definitions}, if any. if (!checkIdentityConstraints(baseElement, derivedElement, errors, context)) { // error already produced return false; } // 7 R's {type definition} is validly derived given {extension, list, union} from B's {type definition} as // defined by Type Derivation OK (Complex) (�3.4.6) or Type Derivation OK (Simple) (�3.14.6), as appropriate. if (!typeDerivationOK(baseElement.getType(), derivedElement.getType(), errors, context)) { // error already produced return false; } // 6 R's declaration's {disallowed substitutions} is a superset of B's declaration's {disallowed substitutions}. if (!blockSetOK(baseElement, derivedElement, errors, context)) { // error already produced return false; } return true; } private static boolean blockSetOK(SchemaLocalElement baseElement, SchemaLocalElement derivedElement, Collection errors, XmlObject context) { if (baseElement.blockRestriction() && !derivedElement.blockRestriction()) { errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION_NAME_AND_TYPE$DISALLOWED_SUBSTITUTIONS, new Object[] { printParticle((SchemaParticle)derivedElement), "restriction", printParticle((SchemaParticle)baseElement) }, context)); return false; } if (baseElement.blockExtension() && !derivedElement.blockExtension()) { errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION_NAME_AND_TYPE$DISALLOWED_SUBSTITUTIONS, new Object[] { printParticle((SchemaParticle)derivedElement), "extension", printParticle((SchemaParticle)baseElement) }, context)); return false; } if (baseElement.blockSubstitution() && !derivedElement.blockSubstitution()) { errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION_NAME_AND_TYPE$DISALLOWED_SUBSTITUTIONS, new Object[] { printParticle((SchemaParticle)derivedElement), "substitution", printParticle((SchemaParticle)baseElement) }, context)); return false; } return true; } private static boolean typeDerivationOK(SchemaType baseType, SchemaType derivedType, Collection errors, XmlObject context){ boolean typeDerivationOK = false; // 1 If B and D are not the same type definition, then the {derivation method} of D must not be in the subset. // 2 One of the following must be true: // 2.1 B and D must be the same type definition. // 2.2 B must be D's {base type definition}. // 2.3 All of the following must be true: // 2.3.1 D's {base type definition} must not be the �ur-type definition�. // 2.3.2 The appropriate case among the following must be true: // 2.3.2.1 If D's {base type definition} is complex, then it must be validly derived from B given the subset as defined by this constraint. // 2.3.2.2 If D's {base type definition} is simple, then it must be validly derived from B given the subset as defined in Type Derivation OK (Simple) (�3.14.6). // This line will check if derivedType is a subType of baseType (should handle all of the 2.xx checks above) if (baseType.isAssignableFrom(derivedType)) { // Ok derived type is subtype but need to make sure that all of the derivations between the two types are by // Restriction. typeDerivationOK = checkAllDerivationsForRestriction(baseType, derivedType, errors, context); } else { // derived type is not a sub-type of base type typeDerivationOK = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION_NAME_AND_TYPE$TYPE_VALID, new Object[] { printType(derivedType), printType(baseType) }, context)); } return typeDerivationOK; } private static boolean checkAllDerivationsForRestriction(SchemaType baseType, SchemaType derivedType, Collection errors, XmlObject context) { boolean allDerivationsAreRestrictions = true; SchemaType currentType = derivedType; // XMLBEANS-66: if baseType is a union, check restriction is of one of the constituant types Set possibleTypes = null; if (baseType.getSimpleVariety() == SchemaType.UNION) possibleTypes = new HashSet(Arrays.asList(baseType.getUnionConstituentTypes())); // run up the types hierarchy from derived Type to base Type and make sure that all are derived by // restriction. If any are not then this is not a valid restriction. while (!baseType.equals(currentType) && possibleTypes != null && !possibleTypes.contains(currentType)) { if (currentType.getDerivationType() == SchemaType.DT_RESTRICTION) { currentType = currentType.getBaseType(); } else { allDerivationsAreRestrictions = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION_NAME_AND_TYPE$TYPE_RESTRICTED, new Object[] { printType(derivedType), printType(baseType), printType(currentType) }, context)); break; } } return allDerivationsAreRestrictions; } private static boolean checkIdentityConstraints(SchemaLocalElement baseElement, SchemaLocalElement derivedElement, Collection errors, XmlObject context) { // 5 R's declaration's {identity-constraint definitions} is a subset of B's declaration's {identity-constraint definitions}, if any. boolean identityConstraintsOK = true; SchemaIdentityConstraint[] baseConstraints = baseElement.getIdentityConstraints(); SchemaIdentityConstraint[] derivedConstraints = derivedElement.getIdentityConstraints(); // cycle thru derived's identity constraints and check each to assure they in the array of base constraints for (int i = 0; i < derivedConstraints.length; i++) { SchemaIdentityConstraint derivedConstraint = derivedConstraints[i]; if (checkForIdentityConstraintExistence(baseConstraints, derivedConstraint)) { identityConstraintsOK = false; errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION_NAME_AND_TYPE$IDENTITY_CONSTRAINTS, new Object[] { printParticle((SchemaParticle)derivedElement), printParticle((SchemaParticle)baseElement) }, context)); break; } } return identityConstraintsOK; } private static boolean checkForIdentityConstraintExistence(SchemaIdentityConstraint[] baseConstraints, SchemaIdentityConstraint derivedConstraint) { // spin thru the base identity constraints check to see if derived constraint exists boolean identityConstraintExists = false; for (int i = 0; i < baseConstraints.length; i++) { SchemaIdentityConstraint baseConstraint = baseConstraints[i]; if (baseConstraint.getName().equals(derivedConstraint.getName())) { identityConstraintExists = true; break; } } return identityConstraintExists; } private static boolean checkFixed(SchemaLocalElement baseModel, SchemaLocalElement derivedModel, Collection errors, XmlObject context) { // 4 either B's declaration's {value constraint} is absent, or is not fixed, // or R's declaration's {value constraint} is fixed with the same value. boolean checkFixed = false; if (baseModel.isFixed()) { if (baseModel.getDefaultText().equals(derivedModel.getDefaultText())) { // R's declaration's {value constraint} is fixed with the same value. checkFixed = true; } else { // The derived element has a fixed value that is different than the base element errors.add(XmlError.forObject(XmlErrorCodes.PARTICLE_RESTRICTION_NAME_AND_TYPE$FIXED, new Object[] { printParticle((SchemaParticle)derivedModel), derivedModel.getDefaultText(), printParticle((SchemaParticle)baseModel), baseModel.getDefaultText() }, context)); checkFixed = false; } } else { // B's declaration's {value constraint} is absent, or is not fixed, checkFixed = true; } return checkFixed; } private static boolean occurrenceRangeOK(SchemaParticle baseParticle, SchemaParticle derivedParticle, Collection errors, XmlObject context) { boolean occurrenceRangeOK = false; // Note: in the following comments (from the schema spec) other is the baseModel // 1 Its {min occurs} is greater than or equal to the other's {min occurs}. if (derivedParticle.getMinOccurs().compareTo(baseParticle.getMinOccurs()) >= 0) { // 2 one of the following must be true: // 2.1 The other's {max occurs} is unbounded. if (baseParticle.getMaxOccurs() == null) { occurrenceRangeOK = true; } else { // 2.2 Both {max occurs} are numbers, and the particle's is less than or equal to the other's. if (derivedParticle.getMaxOccurs() != null && baseParticle.getMaxOccurs() != null && derivedParticle.getMaxOccurs().compareTo(baseParticle.getMaxOccurs()) <= 0) { occurrenceRangeOK = true; } else { occurrenceRangeOK = false; errors.add(XmlError.forObject(XmlErrorCodes.OCCURRENCE_RANGE$MAX_LTE_MAX, new Object[] { printParticle(derivedParticle), printMaxOccurs(derivedParticle.getMaxOccurs()), printParticle(baseParticle), printMaxOccurs(baseParticle.getMaxOccurs()) }, context)); } } } else { occurrenceRangeOK = false; errors.add(XmlError.forObject(XmlErrorCodes.OCCURRENCE_RANGE$MIN_GTE_MIN, new Object[] { printParticle(derivedParticle), derivedParticle.getMinOccurs().toString(), printParticle(baseParticle), baseParticle.getMinOccurs().toString() }, context)); } return occurrenceRangeOK; } private static String printParticles(List parts) { return printParticles((SchemaParticle[])parts.toArray(new SchemaParticle[parts.size()])); } private static String printParticles(SchemaParticle[] parts) { return printParticles(parts, 0, parts.length); } private static String printParticles(SchemaParticle[] parts, int start) { return printParticles(parts, start, parts.length); } private static String printParticles(SchemaParticle[] parts, int start, int end) { StringBuffer buf = new StringBuffer(parts.length * 30); for (int i = start; i < end; ) { buf.append(printParticle(parts[i])); if (++i != end) buf.append(", "); } return buf.toString(); } private static String printParticle(SchemaParticle part) { switch (part.getParticleType()) { case SchemaParticle.ALL: return ""; case SchemaParticle.CHOICE: return ""; case SchemaParticle.ELEMENT: return ""; case SchemaParticle.SEQUENCE: return ""; case SchemaParticle.WILDCARD: return ""; default : return "??"; } } private static String printMaxOccurs(BigInteger bi) { if (bi == null) return "unbounded"; return bi.toString(); } private static String printType(SchemaType type) { if (type.getName() != null) return QNameHelper.pretty(type.getName()); return type.toString(); } private static void checkSubstitutionGroups(SchemaGlobalElement[] elts) { StscState state = StscState.get(); for (int i = 0 ; i < elts.length ; i++) { SchemaGlobalElement elt = elts[i]; SchemaGlobalElement head = elt.substitutionGroup(); if (head != null) { SchemaType headType = head.getType(); SchemaType tailType = elt.getType(); XmlObject parseTree = ((SchemaGlobalElementImpl)elt)._parseObject; if (! headType.isAssignableFrom(tailType)) { state.error(XmlErrorCodes.ELEM_PROPERTIES$SUBSTITUTION_VALID, new Object[] {QNameHelper.pretty(elt.getName()), QNameHelper.pretty(head.getName())}, parseTree); } else if (head.finalExtension() && head.finalRestriction()) { state.error(XmlErrorCodes.ELEM_PROPERTIES$SUBSTITUTION_FINAL, new Object[] {QNameHelper.pretty(elt.getName()), QNameHelper.pretty(head.getName()), "#all"}, parseTree); } else if (! headType.equals(tailType)) { if (head.finalExtension() && tailType.getDerivationType() == SchemaType.DT_EXTENSION) { state.error(XmlErrorCodes.ELEM_PROPERTIES$SUBSTITUTION_FINAL, new Object[] {QNameHelper.pretty(elt.getName()), QNameHelper.pretty(head.getName()), "extension"}, parseTree); } else if (head.finalRestriction() && tailType.getDerivationType() == SchemaType.DT_RESTRICTION) { state.error(XmlErrorCodes.ELEM_PROPERTIES$SUBSTITUTION_FINAL, new Object[] {QNameHelper.pretty(elt.getName()), QNameHelper.pretty(head.getName()), "restriction"}, parseTree); } } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy