/**
* Copyright (C) 2005 Orbeon, Inc.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
/*
* @(#)$Id: IDConstraintChecker.java,v 1.2 2007/03/28 18:50:39 ebruchez 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 org.orbeon.oxf.xforms.msv;
import org.orbeon.msv.grammar.xmlschema.ElementDeclExp;
import org.orbeon.msv.grammar.xmlschema.IdentityConstraint;
import org.orbeon.msv.grammar.xmlschema.KeyRefConstraint;
import org.orbeon.msv.util.LightStack;
import org.orbeon.msv.util.StartTagInfo;
import org.orbeon.msv.verifier.Acceptor;
import org.orbeon.oxf.xforms.ErrorInfo;
import org.orbeon.msv.relaxng.datatype.Datatype;
import org.orbeon.msv.relaxng.datatype.ValidationContext;
/**
* Verifier with XML Schema-related enforcement.
*
* <p>
* This class can be used in the same way as {@link Verifier}.
* This class also checks XML Schema's identity constraint.
*
* @author <a href="mailto:kohsuke.kawaguchi@eng.sun.com">Kohsuke KAWAGUCHI</a>
*/
public class IDConstraintChecker implements ValidationContext {
/** active mathcers. */
protected final java.util.ArrayList matchers = new java.util.ArrayList();
protected void add( Matcher matcher ) {
matchers.add(matcher);
}
protected void remove( Matcher matcher ) {
matchers.remove(matcher);
}
/**
* a map from <code>SelectorMatcher</code> to set of <code>KeyValue</code>s.
*
* One SelectorMatcher correponds to one scope of the identity constraint.
*/
private final java.util.Map keyValues = new java.util.HashMap();
/**
* a map from keyref <code>SelectorMatcher</code> to key/unique
* <code>SelectorMatcher</code>.
*
* Given a keyref scope, this map stores which key scope should it refer to.
*/
private final java.util.Map referenceScope = new java.util.HashMap();
/**
* a map from <code>IdentityConstraint</code> to a <code>LightStack</code> of
* <code>SelectorMatcher</code>.
*
* Each stack top keeps the currently active scope for the given IdentityConstraint.
*/
private final java.util.Map activeScopes = new java.util.HashMap();
protected SelectorMatcher getActiveScope( IdentityConstraint c ) {
LightStack s = (LightStack)activeScopes.get(c);
if(s==null) return null;
if(s.size()==0) return null;
return (SelectorMatcher)s.top();
}
protected void pushActiveScope( IdentityConstraint c, SelectorMatcher matcher ) {
LightStack s = (LightStack)activeScopes.get(c);
if(s==null)
activeScopes.put(c,s=new LightStack());
s.push(matcher);
}
protected void popActiveScope( IdentityConstraint c, SelectorMatcher matcher ) {
LightStack s = (LightStack)activeScopes.get(c);
if(s==null)
// since it's trying to pop, there must be a non-empty stack.
throw new Error();
if(s.pop()!=matcher)
// trying to pop a non-active scope.
throw new Error();
}
/**
* adds a new KeyValue to the value set.
* @return true if this is a new value.
*/
protected boolean addKeyValue( SelectorMatcher scope, KeyValue value ) {
java.util.Set keys = (java.util.Set)keyValues.get(scope);
if(keys==null)
keyValues.put(scope, keys = new java.util.HashSet());
return keys.add(value);
}
/**
* gets the all <code>KeyValue</code>s that were added within the specified scope.
*/
protected KeyValue[] getKeyValues( SelectorMatcher scope ) {
java.util.Set keys = (java.util.Set)keyValues.get(scope);
if(keys==null)
return new KeyValue[0];
return (KeyValue[])keys.toArray(new KeyValue[keys.size()]);
}
public void startDocument() {
keyValues.clear();
}
public void endDocument() {
// keyref check
java.util.Map.Entry[] scopes = (java.util.Map.Entry[])
keyValues.entrySet().toArray(new java.util.Map.Entry[keyValues.size()]);
if(org.orbeon.msv.driver.textui.Debug.debug)
System.out.println("key/keyref check: there are "+keyValues.size()+" scope(s)");
for( int i=0; i<scopes.length; i++ ) {
final SelectorMatcher key = (SelectorMatcher)scopes[i].getKey();
final java.util.Set value = (java.util.Set)scopes[i].getValue();
if( key.idConst instanceof KeyRefConstraint ) {
// get the set of corresponding keys.
java.util.Set keys = (java.util.Set)keyValues.get( referenceScope.get(key) );
KeyValue[] keyrefs = (KeyValue[])
value.toArray(new KeyValue[value.size()]);
for( int j=0; j<keyrefs.length; j++ ) {
if( keys==null || !keys.contains(keyrefs[j]) )
// this keyref doesn't have a corresponding key.
reportError( keyrefs[j].element, ERR_UNDEFINED_KEY,
new Object[]{
key.idConst.namespaceURI,
key.idConst.localName} );
}
}
}
}
public void onNextAcceptorReady( StartTagInfo sti, Acceptor next, final org.orbeon.dom.Element elt ) {
// call matchers
int len = matchers.size();
for( int i=0; i<len; i++ ) {
Matcher m = (Matcher)matchers.get(i);
m.startElement( elt );
}
// introduce newly found identity constraints.
Object e = next.getOwnerType();
if( e instanceof ElementDeclExp.XSElementExp ) {
ElementDeclExp.XSElementExp exp = (ElementDeclExp.XSElementExp)e;
if( exp.identityConstraints!=null ) {
int m = exp.identityConstraints.size();
for( int i=0; i<m; i++ )
add( new SelectorMatcher( this,
(IdentityConstraint)exp.identityConstraints.get(i),
elt ) );
// SelectorMathcers will register themselves as active scopes
// in their constructor.
// augment the referenceScope field by adding newly introduced keyrefs.
for( int i=0; i<m; i++ ) {
IdentityConstraint c = (IdentityConstraint)
exp.identityConstraints.get(i);
if(c instanceof KeyRefConstraint) {
SelectorMatcher keyScope =
getActiveScope( ((KeyRefConstraint)c).key );
if(keyScope==null)
; // there is no active scope of the key scope now.
referenceScope.put(
getActiveScope(c),
keyScope );
}
}
}
}
}
public void feedAttribute
(Acceptor child, final org.orbeon.dom.Attribute att, final Datatype[] result ) {
final int len = matchers.size();
// call matchers for attributes.
for( int i=0; i<len; i++ ) {
Matcher m = (Matcher)matchers.get(i);
m.onAttribute( att,
(result==null || result.length==0)?null:result[0] );
}
}
public void endElement(final org.orbeon.dom.Element elt, final Datatype[] lastType )
{
// getLastCharacterType may sometimes return null. For example,
// 1) this element should be empty and there was only whitespace characters.
Datatype dt;
if( lastType==null || lastType.length==0 ) dt = null;
else dt = lastType[0];
// call matchers
int len = matchers.size();
for( int i=len-1; i>=0; i-- ) {
// Matcher may remove itself from the vector.
// Therefore, to make it work correctly, we have to
// enumerate Matcher in reverse direction.
((Matcher)matchers.get(i)).endElement( dt );
}
}
private java.util.LinkedList errorInfo;
public ErrorInfo clearErrorInfo() {
final ErrorInfo ret = errorInfo == null || errorInfo.size() == 0
? null : ( ErrorInfo )errorInfo.removeLast();
return ret;
}
protected void reportError(final org.orbeon.dom.Element elt, String propKey, Object[] args ) {
// final String suffix = elt == null ? "" : " at " + elt.getUniquePath();
// TODO: elt.getUniquePath();
final String suffix = elt == null ? "" : " at " + elt.getQualifiedName();
final String msg = localizeMessage(propKey,args) + suffix;
if ( errorInfo == null ) errorInfo = new java.util.LinkedList();
final ErrorInfo errInf = new ErrorInfo( elt, msg );
errorInfo.addLast( errInf );
}
public static String localizeMessage( String propertyName, Object arg ) {
return localizeMessage( propertyName, new Object[]{arg} );
}
public static String localizeMessage( String propertyName, Object[] args ) {
String format = java.util.PropertyResourceBundle.getBundle(
"org.orbeon.oxf.xforms.msv.Messages").getString(propertyName);
return java.text.MessageFormat.format(format, args );
}
public static final String ERR_UNMATCHED_KEY_FIELD =
"IdentityConstraint.UnmatchedKeyField"; // arg :3
public static final String ERR_NOT_UNIQUE =
"IdentityConstraint.NotUnique"; // arg:2
public static final String ERR_NOT_UNIQUE_DIAG =
"IdentityConstraint.NotUnique.Diag"; // arg:2
public static final String ERR_DOUBLE_MATCH =
"IdentityConstraint.DoubleMatch"; // arg:3
public static final String ERR_UNDEFINED_KEY =
"IdentityConstraint.UndefinedKey"; // arg:2
public String resolveNamespacePrefix(String arg0) {
// TODO Auto-generated method stub
return null;
}
public String getBaseUri() {
// TODO Auto-generated method stub
return null;
}
public boolean isUnparsedEntity(String arg0) {
// TODO Auto-generated method stub
return false;
}
public boolean isNotation(String arg0) {
// TODO Auto-generated method stub
return false;
}
}