/* * @(#)$Id: StringToken.java,v 1.21 2001/10/19 23:59:21 Bear 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.verifier.regexp; import com.sun.msv.grammar.*; import com.sun.msv.datatype.xsd.StringType; import com.sun.msv.util.DatatypeRef; import org.relaxng.datatype.Datatype; import java.util.StringTokenizer; /** * chunk of string. * * @author <a href="mailto:kohsuke.kawaguchi@eng.sun.com">Kohsuke KAWAGUCHI</a> */ public class StringToken extends Token { public final String literal; public final IDContextProvider context; protected final ResidualCalculator resCalc; protected final boolean ignorable; /** * if this field is non-null, * this field will receive assigned DataType object. */ public DatatypeRef refType; protected boolean saturated = false; private static final Datatype[] ignoredType = new Datatype[0]; public StringToken( REDocumentDeclaration docDecl, String literal, IDContextProvider context ) { this(docDecl.resCalc,literal,context,null); } public StringToken( REDocumentDeclaration docDecl, String literal, IDContextProvider context, DatatypeRef refType ) { this(docDecl.resCalc,literal,context,refType); } public StringToken( ResidualCalculator resCalc, String literal, IDContextProvider context, DatatypeRef refType ) { this.resCalc = resCalc; this.literal = literal; this.context = context; this.refType = refType; this.ignorable = literal.trim().length()==0; if( ignorable && refType!=null ) refType.types = ignoredType; } /** DataExp can consume this token if its datatype can accept this string */ public boolean match( DataExp exp ) { if(!exp.dt.isValid( literal, context )) return false; // not accepted. if( exp.except!=Expression.nullSet ) { if( resCalc.calcResidual( exp.except, this )==Expression.epsilon ) // due to the constraint imposed on the body of the 'except' clause, // comparing the residual with the epsilon is OK and cheap. // but it might be better to use the isEpsilonReducible method // for the robustness. return false; // this token is accepted by its 'except' clause } // this type accepts me. if(refType!=null) assignType(exp.dt); // if the type has ID semantics, report it. if( exp.dt.getIdType()!=exp.dt.ID_TYPE_NULL && context!=null ) // context can be legally null when this datatype is not context dependent. context.onID( exp.dt, literal ); return true; } public boolean match( ValueExp exp ) { Object thisValue = exp.dt.createValue(literal,context); if(!exp.dt.sameValue( thisValue, exp.value )) return false; // this type accepts me. if(refType!=null) assignType(exp.dt); // if the type has ID semantics, report it. if( exp.dt.getIdType()!=exp.dt.ID_TYPE_NULL && context!=null ) // context can be legally null when this datatype is not context dependent. context.onID( exp.dt, literal ); return true; } /** ListExp can consume this token if its pattern accepts this string */ public boolean match( ListExp exp ) { StringTokenizer tokens = new StringTokenizer(literal); Expression residual = exp.exp; // if the application needs type information, // collect them from children. DatatypeRef dtRef = null; Datatype[] childTypes = null; int cnt=0; if( this.refType!=null ) { dtRef = new DatatypeRef(); childTypes = new Datatype[tokens.countTokens()]; } while( tokens.hasMoreTokens() ) { StringToken child = createChildStringToken(tokens.nextToken(),dtRef); residual = resCalc.calcResidual( residual, child ); if( residual==Expression.nullSet ) // the expression is failed to accept this item. return false; if( dtRef!=null ) { if( dtRef.types==null ) { // failed to assign type. bail out. saturated = true; refType.types = null; dtRef = null; } else { // type is successfully assigned for this child. if( dtRef.types.length!=1 ) // the current RELAX NG prohibits to nest <list> patterns. // Thus it's not possible for this child to return more than one type. throw new Error(); childTypes[cnt++] = dtRef.types[0]; } } } if(!residual.isEpsilonReducible()) // some expressions are still left. failed to accept this string. return false; // this <list> accepts this string. if( childTypes!=null ) { // assign datatype if(saturated) // a type is already assigned. That means this string has more than one type. // so bail out. refType.types=null; else refType.types = childTypes; saturated = true; } return true; } protected StringToken createChildStringToken( String literal, DatatypeRef dtRef ) { return new StringToken( resCalc, literal, context, dtRef ); } // anyString can match any string public boolean matchAnyString() { if(refType!=null) assignType(StringType.theInstance); return true; } private void assignType( Datatype dt ) { if(saturated) { if(refType.types!=null && (refType.types[0]!=dt || refType.types.length!=1)) // different types are assigned. roll back to null refType.types=null; } else { // this is the first assignment. remember this value. refType.types=new Datatype[]{dt}; saturated=true; } } /** checks if this token is ignorable. * * StringToken is ignorable when it matches [ \t\r\n]* */ boolean isIgnorable() { return ignorable; } }