/** * 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; } }