com.sun.msv.reader.trex.ng.RestrictionChecker Maven / Gradle / Ivy
/*
* Copyright (c) 2001-2013 Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.sun.msv.reader.trex.ng;
import org.xml.sax.Locator;
import com.sun.msv.grammar.AnyNameClass;
import com.sun.msv.grammar.AttributeExp;
import com.sun.msv.grammar.ChoiceExp;
import com.sun.msv.grammar.ChoiceNameClass;
import com.sun.msv.grammar.DataExp;
import com.sun.msv.grammar.DifferenceNameClass;
import com.sun.msv.grammar.ElementExp;
import com.sun.msv.grammar.Expression;
import com.sun.msv.grammar.InterleaveExp;
import com.sun.msv.grammar.ListExp;
import com.sun.msv.grammar.NameClass;
import com.sun.msv.grammar.NameClassAndExpression;
import com.sun.msv.grammar.NameClassVisitor;
import com.sun.msv.grammar.NamespaceNameClass;
import com.sun.msv.grammar.NotNameClass;
import com.sun.msv.grammar.OneOrMoreExp;
import com.sun.msv.grammar.SequenceExp;
import com.sun.msv.grammar.SimpleNameClass;
import com.sun.msv.grammar.ValueExp;
import com.sun.msv.grammar.util.ExpressionWalker;
import com.sun.msv.grammar.util.NameClassCollisionChecker;
/**
* Checks RELAX NG contextual restrictions defined in the section 7.
*
*
* 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.
*
*
* 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 Kohsuke KAWAGUCHI
*/
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.
*
*
* 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.
*
*
* An area is a range of index designated by the start and end.
*
* Areas are stored as:
*
{ start, end, start, end, ... }
*
*
* 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.
*
*
* 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.)
*
*
* 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