package runtime; import java.util.HashMap; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.TreeSet; import org.relaxng.datatype.ValidationContext; /** * A state in the compiled automaton. * * <p> * Since states are so frequently created and discarded, * we use reference count to reuse State objects. * <p> * Reference Counting rule of thumb: * <ol> * <li>If a method wants to keep a refnerece to one of its parameter, * it needs to call addRef explicitly. * <li>If a method needs to return a State, it must first addRef the returned State. * <li>If a method returns a State, it's a caller's responsibility to release it. * * <li>partialResult is the only exception. * </ol> * * @author Kohsuke Kawaguchi (kk@kohsuke.org) */ public abstract class State { /** * Indicates how the state behaves to text tokens. * * <p> * This is an optimization hint to the validatelet. * * @return * 0 whitespace alone is allowed * 1 text is ignorable. calling the text method * is guaranteed to return the same state. * 2 state is sensitive to the contents of text token. */ public final int textSensitivity; /** * The cached return value from the endElementFast method. */ private State cachedEndElementFast = null; protected State( int _textSensitivity ) { this.textSensitivity = _textSensitivity; } /** * Performs a transition by a start element event. */ public abstract State startElement( int nameCode, AttributesSet attributes, State partialResult, StateFactory factory ); /** * Performs a transition by an end element event. * * @param attributes * attributes of the parent element of the element being closed. */ public abstract State endElement( AttributesSet attributes, State partialResult, StateFactory factory ); /** * Optimized endElement method, which is equivalent to * <code>endElement( AttributesSet.empty, State.emptySet, factory )</code>. */ public final State endElementFast( StateFactory factory ) { if( cachedEndElementFast==null ) cachedEndElementFast = endElement( AttributesSet.empty, State.emptySet, factory ); return cachedEndElementFast; } /** * Expands StateSet by taking applicable attribute transitions. */ public abstract State expand( AttributesSet attributes, State partialResult, StateFactory factory ); /** * Equivalent to * <code>expand(attributes,emptySet.addRef(),factory);</code> */ public final State expandFast( AttributesSet attributes, StateFactory factory ) { if( isExpandable() ) return expand( attributes, emptySet, factory ); else return this; } /** * Equivalent to the expand method but faster. */ public final State expandFast( AttributesSet attributes, State partialResult, StateFactory factory ) { if( isExpandable() ) return expand( attributes, partialResult, factory ); else return factory.makeChoice( partialResult, this ); } /** * Performs a transition by a text chunk. */ public abstract State text( String value, boolean ignorable, ValidationContext context, AttributesSet attributes, State partialResult, StateFactory factory ); public State wrapAfterByAfter( State newThen, State partialResult, StateFactory factory ) { throw new InternalError(this.getClass().toString()); // this can't happen for Interleave/SingleState. } public State wrapAfterByInterleaveLeft ( State lhs, Transition.Interleave alphabet, State partialResult, StateFactory factory ) { throw new InternalError(this.getClass().toString()); // this can't happen for Interleave/SingleState. } public State wrapAfterByInterleaveRight( State rhs, Transition.Interleave alphabet, State partialResult, StateFactory factory ) { throw new InternalError(this.getClass().toString()); // this can't happen for Interleave/SingleState. } /** * Returns true if the current state is a final state. */ public abstract boolean isFinal(); public static final int TEXT_WHITESPACE_ONLY = 0; public static final int TEXT_IGNORABLE = 1; public static final int TEXT_SENSITIVE = 2; /** * Returns true if the expand method can return some * other states. If this method returns false, the expand * method is guaranteed to be equivalent to * makeChoice( partialResult, this ); * * <p> * This is an optimization hint. */ public abstract boolean isExpandable(); /** * Returns true if the choice tree contains the given state. */ public boolean contains( State s ) { return this==s; } public final String toString() { return toString(0); } /** * @param parentPrecedence * Operator precedence of the parent state. * Bigger number means stronger precedence. * 1:AfterState, 2:ChoiceState, 3:InterleaveState */ public abstract String toString( int parentPrecedence ); protected static String parenthesis( String s ) { return '('+s+')'; } /** Singletone emptySet instance. */ protected static State emptySet = new Empty(); private static final class Empty extends State { protected Empty() { // derivative of <notAllowed/> is always <notAllowed/> super(TEXT_IGNORABLE); } public State endElement( AttributesSet attributes, State partialResult, StateFactory factory ) { return partialResult; } public State expand(AttributesSet attributes, State partialResult, StateFactory factory) { return partialResult; } public boolean isExpandable() { return true; } public boolean isFinal() { return false; } public State startElement( int nameCode, AttributesSet attributes, State partialResult, StateFactory factory ) { return partialResult; } public State text( String value, boolean ignorable, ValidationContext context, AttributesSet attributes, State partialResult, StateFactory factory) { return partialResult; } public State wrapAfterByAfter( State newThen, State partialResult, StateFactory factory ) { return partialResult; } public State wrapAfterByInterleaveLeft ( State lhs, Transition.Interleave alphabet, State partialResult, StateFactory factory ) { return partialResult; } public State wrapAfterByInterleaveRight( State rhs, Transition.Interleave alphabet, State partialResult, StateFactory factory ) { return partialResult; } public String toString( int p ) { return "#err"; } } public final static class After extends State { public final State child; public final State then; public After( State c, State t ) { super(c.textSensitivity); this.child=c; this.then=t; } public State startElement( int nameCode, AttributesSet attributes, State partialResult, StateFactory factory ) { State s = child.startElement( nameCode, attributes, emptySet, factory ); State r = s.wrapAfterByAfter( then, partialResult, factory ); return r; } public State endElement( AttributesSet attributes, State partialResult, StateFactory factory ) { if( child.isFinal() ) return then.expandFast( attributes, partialResult, factory ); else return partialResult; } public State expand( AttributesSet attributes, State partialResult, StateFactory factory ) { State s1 = child.expand( attributes, emptySet, factory ); // // optimization. // if(s1==child) { // s1; // we won't use s1. // return factory.makeChoice( partialResult, this ); // } // State s2 = factory.makeAfter( s1, then ); State r = factory.makeChoice( partialResult, s2 ); return r; // don't expand the "then" states because it needs different attributes. // we'll expand them at the endElement method. } public boolean isExpandable() { return child.isExpandable(); } public State text( String value, boolean ignorable, ValidationContext context, AttributesSet attributes, State partialResult, StateFactory factory ) { State s1 = child.text( value, ignorable, context, attributes, emptySet, factory ); State s2 = factory.makeAfter( s1, then ); State r = factory.makeChoice( partialResult, s2 ); return r; } public State wrapAfterByAfter( State newThen, State partialResult, StateFactory factory ) { State s1 = factory.makeAfter( then, newThen ); State s2 = factory.makeAfter( child, s1 ); State r = factory.makeChoice( partialResult, s2 ); return r; } public State wrapAfterByInterleaveLeft( State lhs, Transition.Interleave alphabet, State partialResult, StateFactory factory ) { State s1 = factory.makeInterleave( lhs, then, alphabet ); State s2 = factory.makeAfter( child, s1 ); State r = factory.makeChoice( partialResult, s2 ); return r; } public State wrapAfterByInterleaveRight( State rhs, Transition.Interleave alphabet, State partialResult, StateFactory factory ) { State s1 = factory.makeInterleave( then, rhs, alphabet ); State s2 = factory.makeAfter( child, s1 ); State r = factory.makeChoice( partialResult, s2 ); return r; } public boolean contains( State s ) { if(!(s instanceof After)) return false; After rhs = (After)s; // TODO needs more generalization return this.child.contains(rhs.child) && rhs.then.contains(this.then) && this.then.contains(rhs.then); } public boolean isFinal() { return child.isFinal(); } public String toString( int p ) { String s = child.toString(1)+" then "+then.toString(1); if( p>1 ) s = parenthesis(s); return s; } } public static final class Choice extends State { public final State lhs,rhs; private static int getTextSensitivity( State lhs, State rhs ) { int l = lhs.textSensitivity; int r = rhs.textSensitivity; if( l==TEXT_SENSITIVE || r==TEXT_SENSITIVE || l!=r ) return TEXT_SENSITIVE; return l; } public Choice( State l, State r ) { super(getTextSensitivity(l,r)); this.lhs=l; this.rhs=r; } public State startElement( int nameCode, AttributesSet attributes, State partialResult, StateFactory factory ) { State s1 = lhs.startElement(nameCode,attributes,partialResult,factory); State r = rhs.startElement(nameCode,attributes,s1,factory); return r; } public State endElement( AttributesSet attributes, State partialResult, StateFactory factory ) { State s1 = lhs.endElement( attributes, partialResult, factory ); State r = rhs.endElement( attributes, s1, factory ); return r; } public State expand( AttributesSet attributes, State partialResult, StateFactory factory ) { State s1 = lhs.expand( attributes, partialResult, factory ); State r = rhs.expand( attributes, s1, factory ); return r; } public boolean isExpandable() { return lhs.isExpandable() || rhs.isExpandable(); } public State text( String value, boolean ignorable, ValidationContext context, AttributesSet attributes, State partialResult, StateFactory factory ) { State s1 = lhs.text(value,ignorable,context,attributes,partialResult,factory); State r = rhs.text(value,ignorable,context,attributes,s1,factory); return r; } public State wrapAfterByAfter( State newThen, State partialResult, StateFactory factory ) { State s1 = lhs.wrapAfterByAfter( newThen, partialResult, factory ); State r = rhs.wrapAfterByAfter( newThen, s1, factory ); return r; } public State wrapAfterByInterleaveLeft ( State newLhs, Transition.Interleave alphabet, State partialResult, StateFactory factory ) { State s1 = lhs.wrapAfterByInterleaveLeft( newLhs, alphabet, partialResult, factory ); State r = rhs.wrapAfterByInterleaveLeft( newLhs, alphabet, s1, factory ); return r; } public State wrapAfterByInterleaveRight( State newRhs, Transition.Interleave alphabet, State partialResult, StateFactory factory ) { State s1 = lhs.wrapAfterByInterleaveRight( newRhs, alphabet, partialResult, factory ); State r = rhs.wrapAfterByInterleaveRight( newRhs, alphabet, s1, factory ); return r; } public boolean contains( State s ) { return lhs.contains(s) || rhs.contains(s); } public boolean isFinal() { return lhs.isFinal() || rhs.isFinal(); } public String toString( int p ) { String s = lhs.toString(2)+"|"+rhs.toString(2); if( p>2 ) s = parenthesis(s); return s; } } public static final class Interleave extends State { public final State lhs,rhs; public final Transition.Interleave alphabet; // TODO: it might be better to extend it. public Interleave( State l, State r, Transition.Interleave a ) { super( a.textToLeft ? l.textSensitivity : r.textSensitivity ); // caller needs to addRef this.lhs=l; this.rhs=r; this.alphabet=a; } public State startElement( int nameCode, AttributesSet attributes, State result, StateFactory factory ) { State l = lhs.startElement( nameCode, attributes, emptySet, factory ); State r = rhs.startElement( nameCode, attributes, emptySet, factory ); State x1 = r.wrapAfterByInterleaveLeft (lhs,alphabet, result, factory ); State x2 = l.wrapAfterByInterleaveRight(rhs,alphabet, x1, factory ); return x2; } public State endElement( AttributesSet attributes, State partialResult, StateFactory factory ) { // this method can never be called because // InterleaveState will not appear above AfterState. throw new InternalError(); } public State expand( AttributesSet attributes, State partialResult, StateFactory factory ) { State l = lhs.expand( attributes, emptySet, factory ); State r = rhs.expand( attributes, emptySet, factory ); State x1 = factory.makeInterleave( l, r, alphabet ); State x2 = factory.makeChoice( partialResult, x1 ); return x2; } public boolean isExpandable() { return lhs.isExpandable() || rhs.isExpandable(); } public State text( String value, boolean ignorable, ValidationContext context, AttributesSet attributes, State result, StateFactory factory ) { State i; if( alphabet.textToLeft ) { State t = lhs.text( value, ignorable, context, attributes, emptySet, factory ); i = factory.makeInterleave( t, rhs, alphabet ); } else { State t = rhs.text( value, ignorable, context, attributes, emptySet, factory ); i = factory.makeInterleave( lhs, t, alphabet ); } result = factory.makeChoice( result, i ); if( i instanceof Interleave && ((Interleave)i).canJoin() ) result = alphabet.join.expandFast( attributes, result, factory ); return result; } public boolean isFinal() { // two children must be joinable and the target state must be final return canJoin() && alphabet.join.isFinal; } private boolean canJoin() { return lhs.isFinal() && rhs.isFinal(); } public String toString( int p ) { String s = lhs.toString(3)+"&"+rhs.toString(3)+"->#"+alphabet.join.id; if( p>=2 ) s = parenthesis(s); return s; } } public static final class Single extends State { public Single( int _textSensitivity, boolean _isFinal, boolean _isPersistent, int _id ) { super( _textSensitivity ); this.isFinal = _isFinal; this.isPersistent = _isPersistent; this.id = _id; } public void dispose() { throw new InternalError(); } public final int id; // id of this state. used for debugging only. public final boolean isFinal, isPersistent; // considered to be final. public boolean isExpandable; // reference to the first transition. considered as immutable public Transition.Att aTr; public Transition.Data dTr; public Transition.Element eTr; public Transition.Interleave iTr; public Transition.List lTr; public Transition.NoAtt nTr; public Transition.Value vTr; // element transition with mask==-1 sorted by the order of test. public Transition.Element[] quickETr; public State startElement( int nameCode, AttributesSet attributes, State result, StateFactory factory ) { {// check quick element transition by a binary search int base = 0; int size = quickETr.length; Transition.Element e=null; outerWhile: while(size!=0) { int mid = size/2; e = quickETr[mid+base]; int test = e.test; if(test==nameCode) break; // found if(test<nameCode) { // go to right base += mid+1; size -= mid+1; } else { // go to left size = mid; } if( size <= 4 ) { // switch to linear search for( int i=0; i<size; i++ ) { e = quickETr[base+i]; if(e.test==nameCode) break outerWhile; // found } e = null; // not found break; } } // process neighbors as well for( ; e!=null; e=e.next ) result = e.startElement( result, attributes, factory ); } // then check the rest for( Transition.Element e=eTr; e!=null; e=e.next ) if( e.accepts(nameCode) ) result = e.startElement( result, attributes, factory ); return result; } public State endElement( AttributesSet attributes, State partialResult, StateFactory factory ) { // this method can never be called because // SingleState will not appear above AfterState. throw new InternalError(); } public State text( String value, boolean ignorable, ValidationContext context, AttributesSet attributes, State result, StateFactory factory ) { if(ignorable) { // this text is ignorable result = factory.makeChoice( result, this ); } for( Transition.Data da=dTr; da!=null; da=da.next ) { // data transition can be taken when it's accepted by the datatype AND // the except expression fails. if( da.datatype.isValid(value,context) ) { State s = da.left.text( value, ignorable, context, AttributesSet.empty, emptySet, factory ); boolean fin = s.isFinal(); if( !fin ) result = da.right.expandFast( attributes, result, factory ); } } for( Transition.Value va=vTr; va!=null; va=va.next ) { if( va.accepts(value,context) ) result = va.right.expand(attributes,result,factory); } for( Transition.List la=lTr; la!=null; la=la.next ) { StringTokenizer tokens = new StringTokenizer(value); // a list can't contain interleave, so no need to expand State child = la.left; while(tokens.hasMoreTokens() && child!=null) { String token = tokens.nextToken(); child = child.text( token, token.trim().length()==0, context, AttributesSet.empty, emptySet, factory ); } if( child.isFinal()) result = la.right.expand( attributes, result, factory ); } return result; } public State expand( AttributesSet attributes, State result, StateFactory factory ) { if( result.contains(this) ) return result; // no need to expand more if( isPersistent ) result = factory.makeChoice( result, this ); int attSize = attributes.size(); if( attSize!=0 ) { for( Transition.Att aa = aTr; aa!=null; aa=aa.next ) { if( attributes.matchs(aa,factory) ) result = aa.right.expandFast( attributes, result, factory ); } } for( Transition.NoAtt nea = nTr; nea!=null; nea=nea.next ) { int j; for( j=attSize-1; j>=0; j-- ) { int nc = attributes.getName(j); if( nea.accepts(nc) ) // this attribute is prohibited. break; } if(j==-1) result = nea.right.expandFast( attributes, result, factory ); } for( Transition.Interleave ia = iTr; ia!=null; ia=ia.next ) { result = factory.makeChoice( result, factory.makeInterleave( ia.left. expandFast( attributes, factory ), ia.right.expandFast( attributes, factory ), ia )); } return result; } public boolean contains( State s ) { return this==s; } public boolean isFinal() { return isFinal; } public boolean isExpandable() { return isExpandable; } public String toString( int p ) { if( aTr==null && dTr==null && eTr==null && iTr==null && lTr==null && nTr==null && vTr==null && quickETr==null && isFinal ) return "#eps"; return "#"+id; } } }