/* * @(#)$Id: RestrictionChecker.java,v 1.13 2001/11/28 01:47:56 kk122374 Exp $ * * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved. * * This software is the proprietary information of Sun Microsystems, Inc. * Use is subject to license terms. * */ package com.sun.msv.reader.trex.ng; import com.sun.msv.grammar.*; import com.sun.msv.grammar.util.ExpressionWalker; import com.sun.msv.grammar.util.NameClassCollisionChecker; import com.sun.msv.grammar.util.NameClassSimplifier; import org.xml.sax.Locator; import java.util.Vector; /** * Checks RELAX NG contextual restrictions defined in the section 7. * * <p> * ExpressionWalker is used to walk the content model thoroughly. * Depending on the current context, different walkers are used so that * we can detect contextual restrictions properly. * * <p> * For each ElementExp and AttributeExp, its name class is checked to detect * the constraint set out in the section 7.1.6. Also, a set is used to avoid * redundant checks. * * * @author <a href="mailto:kohsuke.kawaguchi@eng.sun.com">Kohsuke KAWAGUCHI</a> */ public class RestrictionChecker { public RestrictionChecker( RELAXNGReader _reader ) { this.reader = _reader; } /** * Traverses the grammar and performs the contextual check. */ public void check() { reader.getGrammar().visit(inStart); } /** Reader object to which errors are reported. */ private final RELAXNGReader reader; /** * The source location of this expression should be also reported in case of error. */ private Expression errorContext; private void reportError( Expression exp, String errorMsg ) { reportError(exp,errorMsg,null); } private void reportError( Expression exp, String errorMsg, Object[] args ) { reader.reportError( new Locator[]{ reader.getDeclaredLocationOf(exp), reader.getDeclaredLocationOf(errorContext) }, errorMsg, args ); } /** Visited ElementExp/AttributeExps. */ private final java.util.Set visitedExps = new java.util.HashSet(); /** Object that checks duplicate attributes in a content model. */ private DuplicateAttributesChecker attDupChecker; /** Object that checks conflicting elements in interleave. */ private DuplicateElementsChecker elemDupChecker; /* content model checker ===================== */ /** * The base class of all other context-specific checker. * This class performs the context switching. */ private class DefaultChecker extends ExpressionWalker { public void onElement( ElementExp exp ) { if( !visitedExps.add(exp) ) return; // check conflicting elements if(elemDupChecker!=null) elemDupChecker.add(exp); // push context element, final Expression oldContext = errorContext; final DuplicateAttributesChecker oldADC = attDupChecker; final DuplicateElementsChecker oldEDC = elemDupChecker; errorContext = exp; attDupChecker = new DuplicateAttributesChecker(); elemDupChecker = new DuplicateElementsChecker(); // it is important to use the expanded exp because // section 7 has to be applied after patterns are expanded. exp.contentModel.getExpandedExp(reader.pool).visit(inElement); errorContext = oldContext; attDupChecker = oldADC; elemDupChecker = oldEDC; } public void onAttribute( AttributeExp exp ) { if( !visitedExps.add(exp) ) return; // check duplicate attributes attDupChecker.add(exp); // check infinite name checkAttributeInfiniteName(exp); final Expression oldContext = errorContext; errorContext = exp; exp.exp.getExpandedExp(reader.pool).visit(inAttribute); errorContext = oldContext; } protected void checkAttributeInfiniteName( final AttributeExp exp ) { exp.nameClass.visit( new NameClassVisitor() { public Object onAnyName( AnyNameClass nc ) { return error(); } public Object onSimple( SimpleNameClass nc ) { return null; } public Object onNsName( NamespaceNameClass nc ) { return error(); } public Object onNot( NotNameClass nc ) { throw new Error(); } // should not be used public Object onDifference( DifferenceNameClass nc ) { nc.nc1.visit(this); nc.nc2.visit(this); return null; } public Object onChoice( ChoiceNameClass nc ) { nc.nc1.visit(this); nc.nc2.visit(this); return null; } private Object error() { reportError(exp, RELAXNGReader.ERR_NAKED_INFINITE_ATTRIBUTE_NAMECLASS ); return null; } }); } public void onList( ListExp exp ) { exp.exp.visit(inList); } public void onData( DataExp exp ) { exp.except.visit(inExcept); } public void onChoice( ChoiceExp exp ) { if(attDupChecker==null) // if a 'choice' appears at the top level, // there is no enclosing element, so no attDupChecker is present. super.onChoice(exp); else { int idx = attDupChecker.start(); exp.exp1.visit(this); attDupChecker.endLeftBranch(idx); exp.exp2.visit(this); attDupChecker.endRightBranch(); } } public void onInterleave( InterleaveExp exp ) { if(elemDupChecker==null) super.onInterleave(exp); else { int idx = elemDupChecker.start(); exp.exp1.visit(this); elemDupChecker.endLeftBranch(idx); exp.exp2.visit(this); elemDupChecker.endRightBranch(); } } public void onAnyString() { super.onAnyString(); } } /** * Used to visit children of the 'except' clause of data. */ private final ExpressionWalker inExcept = new DefaultChecker() { public void onAttribute( AttributeExp exp ) { reportError( exp, ERR_ATTRIBUTE_IN_EXCEPT ); } public void onElement( ElementExp exp ) { reportError( exp, ERR_ELEMENT_IN_EXCEPT ); } public void onList( ListExp exp ) { reportError( exp, ERR_LIST_IN_EXCEPT ); } public void onAnyString() { reportError( null, ERR_TEXT_IN_EXCEPT ); } public void onEpsilon() { reportError( null, ERR_EMPTY_IN_EXCEPT ); } public void onSequence( SequenceExp exp ) { reportError( exp, ERR_SEQUENCE_IN_EXCEPT ); } public void onInterleave( InterleaveExp exp ) { reportError( exp, ERR_INTERLEAVE_IN_EXCEPT ); } public void onOneOrMore( OneOrMoreExp exp ) { reportError( exp, ERR_ONEORMORE_IN_EXCEPT ); } }; /** * Used to visit children of group/interleave in oneOrMore in elements. */ private final ExpressionWalker inGroupInOneOrMoreInElement = new DefaultChecker() { public void onAttribute( AttributeExp exp ) { reportError( exp, ERR_REPEATED_GROUPED_ATTRIBUTE ); } }; /** * Used to visit children of oneOrMore in elements. */ private final ExpressionWalker inOneOrMoreInElement = new DefaultChecker() { public void onSequence( SequenceExp exp ) { exp.visit(inGroupInOneOrMoreInElement); } public void onInterleave( InterleaveExp exp ) { exp.visit(inGroupInOneOrMoreInElement); } protected void checkAttributeInfiniteName( AttributeExp exp ) { // attribute name class whose size is infinite // is allowed inside oneOrMore. } }; /** * Used to visit children of elements. */ private final ExpressionWalker inElement = new DefaultChecker() { public void onOneOrMore( OneOrMoreExp exp ) { exp.exp.visit(inOneOrMoreInElement); } }; /** * Used to visit children of attributes. */ private final ExpressionWalker inAttribute = new DefaultChecker(){ public void onElement( ElementExp exp ) { reportError( exp, ERR_ELEMENT_IN_ATTRIBUTE ); } public void onAttribute( AttributeExp exp ) { reportError( exp, ERR_ATTRIBUTE_IN_ATTRIBUTE ); } }; private class ListChecker extends DefaultChecker { public void onAttribute( AttributeExp exp ) { reportError( exp, ERR_ATTRIBUTE_IN_LIST ); } public void onElement( ElementExp exp ) { reportError( exp, ERR_ELEMENT_IN_LIST ); } public void onList( ListExp exp ) { reportError( exp, ERR_LIST_IN_LIST ); } public void onAnyString() { reportError( null, ERR_TEXT_IN_LIST ); } } /** * Used to visit children of interleaves in lists. */ private final ExpressionWalker inInterleaveInList = new ListChecker() { public void onData( DataExp exp ) { reportError( exp, ERR_DATA_IN_INTERLEAVE_IN_LIST ); } public void onValue( ValueExp exp ) { reportError( exp, ERR_VALUE_IN_INTERLEAVE_IN_LIST ); } }; /** * Used to visit children of lists. */ private final ExpressionWalker inList = new ListChecker() { public void onInterleave( InterleaveExp exp ) { inInterleaveInList.onInterleave(exp); } }; /** * Used to visit the start pattern. */ private final ExpressionWalker inStart = new DefaultChecker() { public void onAttribute( AttributeExp exp ) { reportError( exp, ERR_ATTRIBUTE_IN_START ); } public void onList( ListExp exp ) { reportError( exp, ERR_LIST_IN_START ); } public void onAnyString() { reportError( null, ERR_TEXT_IN_START ); } public void onEpsilon() { reportError( null, ERR_EMPTY_IN_START ); } public void onSequence( SequenceExp exp ) { reportError( exp, ERR_SEQUENCE_IN_START ); } public void onInterleave( InterleaveExp exp ) { reportError( exp, ERR_INTERLEAVE_IN_START ); } public void onData( DataExp exp ) { reportError( exp, ERR_DATA_IN_START ); } public void onValue( ValueExp exp ) { reportError( exp, ERR_DATA_IN_START ); } public void onOneOrMore( OneOrMoreExp exp ) { reportError( exp, ERR_ONEORMORE_IN_START ); } }; /* name class checker ================== */ class NameClassWalker implements NameClassVisitor { public Object onAnyName( AnyNameClass nc ) { return null; } public Object onSimple( SimpleNameClass nc ) { return null; } public Object onNsName( NamespaceNameClass nc ) { return null; } public Object onNot( NotNameClass nc ) { throw new Error(); } // should not be used public Object onDifference( DifferenceNameClass nc ) { nc.nc1.visit(this); if(nc.nc1 instanceof AnyNameClass) nc.nc2.visit(inAnyNameClass); else if(nc.nc1 instanceof NamespaceNameClass) nc.nc2.visit(inNsNameClass); else throw new Error(); // this is not possible in RELAX NG. return null; } public Object onChoice( ChoiceNameClass nc ) { nc.nc1.visit(this); nc.nc2.visit(this); return null; } } /** * Checks the contextual restriction on a name class. * * <p> * If an error is found, it is reported through GrammarReader. */ public void checkNameClass( NameClass nc ) { nc.visit(inNameClass); } /** * Used to visit name classes. */ private final NameClassWalker inNameClass = new NameClassWalker(); /** * Used to visit children of AnyNameClass */ private final NameClassVisitor inAnyNameClass = new NameClassWalker(){ public Object onAnyName( AnyNameClass nc ) { reportError(null,ERR_ANYNAME_IN_ANYNAME); return null; } }; /** * Used to visit children of NamespaceNameClass */ private final NameClassVisitor inNsNameClass = new NameClassWalker(){ public Object onAnyName( AnyNameClass nc ) { reportError(null,ERR_ANYNAME_IN_NSNAME); return null; } public Object onNsName( NamespaceNameClass nc ) { reportError(null,ERR_NSNAME_IN_NSNAME); return null; } }; /* duplicate attributes check ========================== */ protected abstract class DuplicateNameChecker { /** ElementExps will be added into this array. */ protected NameClassAndExpression[] exps = new NameClassAndExpression[16]; /** Number of items in the atts array. */ protected int expsLen=0; /** * areas. * * <p> * An area is a range of index designated by the start and end. * * Areas are stored as: * <pre>{ start, end, start, end, ... }</pre> * * <p> * The start method gives the index. The endLeftBranch method creates * an area by using the start index given by the start method. * The endRightBranch method will remove the area. * * <p> * When testing duplicate attributes, areas are created by ChoiceExp * and used to exclude test candidates (as two attributes can share the * same name if they are in different branches of choice.) * * <p> * When testing duplicate elements, areas are created by InterleaveExp * and used to include test candidates (as two elements cannot share * the same name if they are in different branches of interleave.) */ protected int[] areas = new int[8]; protected int areaLen=0; /** * Adds newly found element or attribute. */ public void add( NameClassAndExpression exp ) { check(exp); // perform duplication check // add it to the array if(exps.length==expsLen) { // expand buffer NameClassAndExpression[] n = new NameClassAndExpression[expsLen*2]; System.arraycopy(exps,0,n,0,expsLen); exps = n; } exps[expsLen++] = exp; } /** * tests a given exp against existing expressions (which are stored in * the exps field.) */ protected abstract void check( NameClassAndExpression exp ); public int start() { return expsLen; } public void endLeftBranch( int start ) { if( areas.length==areaLen ) { // expand buffer int[] n = new int[areaLen*2]; System.arraycopy(areas,0,n,0,areaLen); areas = n; } // create an area areas[areaLen++] = start; areas[areaLen++] = expsLen; } public void endRightBranch() { // remove an area areaLen-=2; } /** * Name class checker object. One object is reused throughout the test. */ private final NameClassCollisionChecker checker = new NameClassCollisionChecker(); /** Tests two name classes to see if they collide. */ protected void check( NameClassAndExpression newExp, NameClassAndExpression oldExp ) { if(checker.check( newExp.getNameClass(), oldExp.getNameClass() )) { // two attributes/elements collide NameClass intersection = NameClass.intersection( newExp.getNameClass(), oldExp.getNameClass() ); reader.reportError( new Locator[]{ reader.getDeclaredLocationOf(errorContext), // the parent element reader.getDeclaredLocationOf(newExp), reader.getDeclaredLocationOf(oldExp)}, getErrorMessage(), new Object[]{intersection.toString()} ); } } /** Gets the error message resource name. */ protected abstract String getErrorMessage(); } private class DuplicateElementsChecker extends DuplicateNameChecker { protected void check( NameClassAndExpression exp ) { // check this element with elements in the area for( int i=0; i<areaLen; i+=2 ) for( int j=areas[i]; j<areas[i+1]; j++ ) this.check(exp,exps[j]); } protected String getErrorMessage() { return ERR_DUPLICATE_ELEMENTS; } } private class DuplicateAttributesChecker extends DuplicateNameChecker { protected void check( NameClassAndExpression exp ) { // check the consistency with attributes NOT in the area. int j=0; for( int i=0; i<areaLen; i+=2 ) { while( j<areas[i] ) this.check(exp,exps[j++]); j=areas[i+1]; } while(j<expsLen) this.check(exp,exps[j++]); } protected String getErrorMessage() { return ERR_DUPLICATE_ATTRIBUTES; } } // error messages private static final String ERR_ATTRIBUTE_IN_EXCEPT = "RELAXNGReader.AttributeInExcept"; private static final String ERR_ELEMENT_IN_EXCEPT = "RELAXNGReader.ElementInExcept"; private static final String ERR_LIST_IN_EXCEPT = "RELAXNGReader.ListInExcept"; private static final String ERR_TEXT_IN_EXCEPT = "RELAXNGReader.TextInExcept"; private static final String ERR_EMPTY_IN_EXCEPT = "RELAXNGReader.EmptyInExcept"; private static final String ERR_SEQUENCE_IN_EXCEPT = "RELAXNGReader.SequenceInExcept"; private static final String ERR_INTERLEAVE_IN_EXCEPT = "RELAXNGReader.InterleaveInExcept"; private static final String ERR_ONEORMORE_IN_EXCEPT = "RELAXNGReader.OneOrMoreInExcept"; private static final String ERR_REPEATED_GROUPED_ATTRIBUTE = "RELAXNGReader.RepeatedGroupedAttribute"; private static final String ERR_ELEMENT_IN_ATTRIBUTE = "RELAXNGReader.ElementInAttribute"; private static final String ERR_ATTRIBUTE_IN_ATTRIBUTE = "RELAXNGReader.AttributeInAttribute"; private static final String ERR_ATTRIBUTE_IN_LIST = "RELAXNGReader.AttributeInList"; private static final String ERR_ELEMENT_IN_LIST = "RELAXNGReader.ElementInList"; private static final String ERR_LIST_IN_LIST = "RELAXNGReader.ListInList"; private static final String ERR_TEXT_IN_LIST = "RELAXNGReader.TextInList"; private static final String ERR_ATTRIBUTE_IN_START = "RELAXNGReader.AttributeInStart"; private static final String ERR_LIST_IN_START = "RELAXNGReader.ListInStart"; private static final String ERR_TEXT_IN_START = "RELAXNGReader.TextInStart"; private static final String ERR_EMPTY_IN_START = "RELAXNGReader.EmptyInStart"; private static final String ERR_SEQUENCE_IN_START = "RELAXNGReader.SequenceInStart"; private static final String ERR_INTERLEAVE_IN_START = "RELAXNGReader.InterleaveInStart"; private static final String ERR_DATA_IN_START = "RELAXNGReader.DataInStart"; private static final String ERR_ONEORMORE_IN_START = "RELAXNGReader.OneOrMoreInStart"; private static final String ERR_TEXT_IN_INTERLEAVE = "RELAXNGReader.TextInInterleave"; private static final String ERR_DATA_IN_INTERLEAVE_IN_LIST = "RELAXNGReader.DataInInterleaveInList"; private static final String ERR_VALUE_IN_INTERLEAVE_IN_LIST = "RELAXNGReader.ValueInInterleaveInList"; private static final String ERR_ANYNAME_IN_ANYNAME = "RELAXNGReader.AnyNameInAnyName"; private static final String ERR_ANYNAME_IN_NSNAME = "RELAXNGReader.AnyNameInNsName"; private static final String ERR_NSNAME_IN_NSNAME = "RELAXNGReader.NsNameInNsName"; private static final String ERR_DUPLICATE_ATTRIBUTES = "RELAXNGReader.DuplicateAttributes"; private static final String ERR_DUPLICATE_ELEMENTS = "RELAXNGReader.DuplicateElements"; }