Package com.sun.msv.generator

Source Code of com.sun.msv.generator.Generator

/*
* @(#)$Id: Generator.java 1478 2002-12-23 23:17:33Z kk122374 $
*
* 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.generator;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import org.relaxng.datatype.Datatype;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

import com.sun.msv.datatype.xsd.NmtokenType;
import com.sun.msv.datatype.xsd.StringType;
import com.sun.msv.datatype.xsd.XSDatatype;
import com.sun.msv.grammar.AttributeExp;
import com.sun.msv.grammar.BinaryExp;
import com.sun.msv.grammar.ChoiceExp;
import com.sun.msv.grammar.ConcurExp;
import com.sun.msv.grammar.DataExp;
import com.sun.msv.grammar.ElementExp;
import com.sun.msv.grammar.Expression;
import com.sun.msv.grammar.ExpressionPool;
import com.sun.msv.grammar.ExpressionVisitorVoid;
import com.sun.msv.grammar.InterleaveExp;
import com.sun.msv.grammar.ListExp;
import com.sun.msv.grammar.MixedExp;
import com.sun.msv.grammar.NameClass;
import com.sun.msv.grammar.OneOrMoreExp;
import com.sun.msv.grammar.OtherExp;
import com.sun.msv.grammar.ReferenceExp;
import com.sun.msv.grammar.SequenceExp;
import com.sun.msv.grammar.ValueExp;
import com.sun.msv.grammar.util.ExpressionPrinter;
import com.sun.msv.util.StringPair;
import com.sun.xml.util.XmlChars;

/**
* generates an XML DOM instance that conforms to the given schema.
*
* @author <a href="mailto:kohsuke.kawaguchi@eng.sun.com">Kohsuke KAWAGUCHI</a>
*/
public class Generator implements ExpressionVisitorVoid {
 
  /** generation parameters */
  private final GeneratorOption opts;
  private final ExpressionPool pool;
  private final Document domDoc;
  /** current generated node */
  private Node node;
 
  /** current nest level (depth of elements). */
  private int depth = 0;
 
  /** this flag is set to true once an error is generated. */
  private boolean errorGenerated = false;

  /** returns true if generator should cut back. */
  protected boolean cutBack() { return depth>5; }
 
  /** ID tokens that are used */
  private final Set ids = new HashSet();
  /** Text nodes of IDREFs that should be "patched" by IDs. */
  private final Set idrefs = new HashSet();
 
  /** all ElementExps in the grammar. */
  private final ElementExp[] elementDecls;
  /** all AttributeExps in the grammar. */
  private final AttributeExp[] attributeDecls;
 
  /** generates instance by using default settings. */
  public static void generate( Expression exp, Document emptyDoc ) {
    generate( exp, emptyDoc, new GeneratorOption() );
  }
 
  /** generates instance by custom settings. */
  public static void generate( Expression exp, Document emptyDoc, GeneratorOption opts ) {
    Generator g;
   
    for( int i=0; i<10; i++ ) {
      // make it empty.
      while( emptyDoc.hasChildNodes())
        emptyDoc.removeChild(emptyDoc.getFirstChild());
     
      do {
        while( emptyDoc.getFirstChild()!=null ) // delete any existing children
          emptyDoc.removeChild( emptyDoc.getFirstChild() );
       
        g = new Generator(exp,emptyDoc,opts);
        exp.visit(g);
        // if error ratio is specified and no error is generated, do it again.
      }while( !g.errorGenerated && opts.errorSpecified() );
   
   
      Object[] ids = g.ids.toArray();
      if( ids.length==0 && g.idrefs.size()!=0 )
        continue// IDREF is generated but no ID is generated.
              // try again.
   
      // patch IDREF.
      Iterator itr = g.idrefs.iterator();
      while( itr.hasNext() ) {
        Text node = (Text)itr.next();
        node.setData( (String)ids[opts.random.nextInt(ids.length)] );
      }
      return;
    }
   
    throw new Error("no ID");
  }
 
  protected Generator( Expression exp, Document emptyDoc, GeneratorOption opts ) {
    opts.fillInByDefault();
    this.opts = opts;
    this.pool = opts.pool;
    node = domDoc = emptyDoc;
   
    // collect element and attribute decls.
    Set[] s= ElementDeclCollector.collect(exp);
    elementDecls = new ElementExp[s[0].size()];
    s[0].toArray(elementDecls);
    attributeDecls = new AttributeExp[s[1].size()];
    s[1].toArray(attributeDecls);
  }
 
  /** annotate DOM by adding a comment that an error is generated. */
  private void noteError( String error ) {
    errorGenerated = true;
    if( !opts.insertComment return;
   
    Node com = domDoc.createComment("  "+error+"  ");
   
    Node n = node;
    if( n.getNodeType()==Node.ATTRIBUTE_NODE ) {
      n = ((Attr)n).getOwnerElement();
      n.insertBefore( com, n.getFirstChild() );
    } else {
      n.appendChild(com);
    }
  }
 
 
  public void onEpsilon() {}
  public void onNullSet() { throw new Error(); // assertion failed
 
  public void onSequence( SequenceExp exp ) {
    if(!(exp.exp1 instanceof AttributeExp)
    && !(exp.exp2 instanceof AttributeExp) ) {
      // sequencing error of attribute is meaningless.
      if( opts.random.nextDouble() < opts.probSeqError ) {
        // generate sequencing error
        noteError("swap sequence to "+
              ExpressionPrinter.printSmallest(exp.exp2)+","+
              ExpressionPrinter.printSmallest(exp.exp1) );
        exp.exp2.visit(this);
        exp.exp1.visit(this);
        return;
      }
    }
   
    // generate valid instance.
    exp.exp1.visit(this);
    exp.exp2.visit(this);
  }
 
  public void onInterleave( InterleaveExp ip ) {
    // collect children
    Vector vec = getChildren(ip);
   
    Node old = node;
    // generate XML fragment for each child.
    for( int i=0; i<vec.size(); i++ ) {
      node = domDoc.createElement("dummy");
      ((Expression)vec.get(i)).visit(this);
     
      vec.set(i,node);
    }
    node = old;
   
    // interleave them.
    while( vec.size()!=0 ) {
      int idx = opts.random.nextInt(vec.size());
      Element e = (Element)vec.get(idx);
      if(!e.hasChildNodes()) {
        // this one has no more child.
        vec.remove(idx);
       
        // copy attributes
        // note that removing an attribute
        // will change the index of the rest.
        NamedNodeMap m = e.getAttributes();
        for( int i=0; i<m.getLength(); i++ ) {
          Attr a = (Attr)m.item(0);
         
          // due to the bug(?) of DOM (or Xercs), we cannot use
          // removeAttributeNode.
          e.removeAttributeNS(a.getNamespaceURI(),a.getLocalName());
         
          if( !((Element)node).hasAttribute(a.getName()) )
            // due to the error generation, two attributes may collide.
            ((Element)node).setAttributeNodeNS(a);
        }
       
        continue;
      }
      node.appendChild( e.getFirstChild() );
    }
  }
 
  public void onChoice( ChoiceExp cp ) {
    // "A*" is modeled as (epsilon|A+)
    if( cp.exp1==Expression.epsilon && cp.exp2 instanceof OneOrMoreExp ) {
      onZeroOrMore( (OneOrMoreExp)cp.exp2 );
      return;
    }
    if( cp.exp2==Expression.epsilon && cp.exp1 instanceof OneOrMoreExp ) {
      onZeroOrMore( (OneOrMoreExp)cp.exp1 );
      return;
    }
   
    if( cutBack() && cp.isEpsilonReducible() )  return// cut back
   
   
    // gather candidates
    Vector vec = getChildren(cp);

    if( opts.random.nextDouble() < opts.probGreedyChoiceError ) {
      // greedy choice error. visit twice.
      Expression[] es = new Expression[2];
      for( int i=0; i<2; i++ )
        do {
          es[i] = (Expression)vec.get(opts.random.nextInt(vec.size()));
        }while(es[i]==Expression.epsilon);

      noteError("greedy choice "+
            ExpressionPrinter.printSmallest(es[0])+ " & "+
            ExpressionPrinter.printSmallest(es[1]));
     
      for( int i=0; i<2; i++ )
        es[i].visit(this);
      return;
    }
   
    // randomly select one candidate.
    ((Expression)vec.get(opts.random.nextInt(vec.size()))).visit(this);
  }
 
  public void onMixed( MixedExp exp ) {
    // convert it to interleave so that we can generate some pcdata.
    pool.createInterleave(
      pool.createZeroOrMore(Expression.anyString),
      exp.exp ).visit(this);
  }
 
  public void onList( ListExp exp ) {
    Node oldNode = node;
   
    Element child = domDoc.createElement("dummy");
    node = child;
   
    // generate children
    exp.exp.visit(this);
   
    // several TextNode should have been appended to Element.
    // so copy them to the parent with separators.
    Node text;
    while( (text = node.getFirstChild())!=null ) {
      // append a delimiter
      oldNode.appendChild( domDoc.createTextNode(" ") );
      // append a token
      node.removeChild(text);
      oldNode.appendChild(text);
    }
  }
 
  public void onRef( ReferenceExp exp ) {
    exp.exp.visit(this);
  }
 
  public void onOther( OtherExp exp ) {
    exp.exp.visit(this);
  }
 
  public void onAttribute( AttributeExp exp ) {
    if( opts.random.nextDouble() < opts.probMutatedAttrError ) {
      // mutated element error. generate a random attribute and ignore this declaration.
      noteError("mutated attribute "+exp.nameClass);
      onAttribute( attributeDecls[opts.random.nextInt(attributeDecls.length)] );
      return;
    }
   
    if( opts.random.nextDouble() < opts.probMissingAttrError ) {
      // missing attribute error. skip generating this instance.
      noteError("missing attribute "+exp.nameClass);
      return;
    }
   
    if( opts.random.nextDouble() < opts.probSlipInAttrError ) {
      // slip-in error. generate random attribute.
      AttributeExp a = attributeDecls[opts.random.nextInt(attributeDecls.length)];
      noteError("slip-in attribute "+a.nameClass);
      onAttribute( a );
    }
   
   
    // generate attribute name
    StringPair name;
    int retry=0;
    do {
      name = getName(exp.nameClass);
    }while( ((Element)node).getAttributeNodeNS(name.namespaceURI,name.localName)!=null
      &&  retry++<100/*abort after several retries*/ );

    if( opts.random.nextDouble() < opts.probAttrNameTypo ) {
      noteError("attribute name typo: "+name.localName);
      name = generateTypo(name);
    }
   
    // It is possible
    // that this attribute is already added as a result of
    // generating an error.
    Attr a = domDoc.createAttributeNS( name.namespaceURI, name.localName );
    ((Element)node).setAttributeNodeNS(a);

    Node old = node;
    node = a;
    exp.exp.visit(this)// generate attribute value
    node = old;
  }
 
  public void onElement( ElementExp exp ) {
   
    if( node instanceof Document && ((Document)node).getDocumentElement()!=null ) {
      // document has the root element. so abort.
      return;
    }
   
    if( opts.random.nextDouble() < opts.probMutatedElemError ) {
      // mutated element error. generate a random element and ignore this declaration.
      noteError("mutated element");
      onElement( elementDecls[opts.random.nextInt(elementDecls.length)] );
      return;
    }
       
    if( node.getNodeType()!=Node.DOCUMENT_NODE ) {
      // these errors cannot be generated for the document element
      if( opts.random.nextDouble() < opts.probMissingElemError ) {
        // missing element error. skip generating this instance.
        noteError("missing element: "+ExpressionPrinter.printSmallest(exp) );
        return;
      }
   
      if( opts.random.nextDouble() < opts.probSlipInElemError ) {
        // slip-in error. generate random element.
        ElementExp e = elementDecls[opts.random.nextInt(elementDecls.length)];
        noteError("slip-in element: "+ExpressionPrinter.printSmallest(e) );
        onElement( e );
      }
    }
   
    StringPair name = getName(exp.getNameClass());

    if( opts.random.nextDouble() < opts.probElemNameTypo ) {
      noteError("element name typo: "+name.localName);
      name = generateTypo(name);
    }
   
    Element child = domDoc.createElementNS( name.namespaceURI, name.localName );
    node.appendChild( child );
    node = child;
   
    // generate children
    depth++;
    exp.contentModel.visit(this);
    depth--;
   
    node = child.getParentNode();
  }
 
  public void onAnyString() {
    node.appendChild( domDoc.createTextNode(opts.dtGenerator.generate(StringType.theInstance,getContext())) );
  }
 
  public void onOneOrMore( OneOrMoreExp exp ) {
    if( opts.random.nextDouble() < opts.probMissingPlus ) {
      noteError("missing " + ExpressionPrinter.printSmallest(exp) );
      return;
    }
   
    int m = opts.width.next()+1;
    if( cutBack() )  m=1;
    for( int i=0; i<m; i++ )
      exp.exp.visit(this);
  }
 
  public void onZeroOrMore( OneOrMoreExp exp ) {
    int m = opts.width.next();
    if( cutBack() )  m=0;
    for( int i=0; i<m; i++ )
      exp.exp.visit(this);
  }
 
  public void onValue( ValueExp exp ) {
    String text;
    if( exp.dt instanceof XSDatatype ) {
      XSDatatype xsd = (XSDatatype)exp.dt;
      text = xsd.convertToLexicalValue(exp.value,getContext());
    } else {
      text = exp.value.toString();
      if(!exp.dt.sameValue( exp.value, exp.dt.createValue(text,getContext()) ) )
        throw new Error("unable to produce a value for the datatype:"+exp.name);
    }
   
    node.appendChild( domDoc.createTextNode(text) );
    return;
  }
  public void onData( DataExp exp ) {
    String value;
    if( exp.dt==com.sun.msv.datatype.xsd.IDType.theInstance ) {
      do {
        value = opts.dtGenerator.generate(NmtokenType.theInstance,getContext());
      }while( ids.contains(value) );
      ids.add(value);
    }
    else
    if( exp.dt.getIdType()==Datatype.ID_TYPE_IDREF
    ||  exp.dt.getIdType()==Datatype.ID_TYPE_IDREFS ) {
      Node n = domDoc.createTextNode("{TmpIDRef}");
      node.appendChild(n);
      idrefs.add(n); // memorize this node so that we can patch it later.
      return;
    } else {
      value = opts.dtGenerator.generate(exp.dt,getContext());
    }
   
    node.appendChild( domDoc.createTextNode(value) );
  }
 
  public void onConcur( ConcurExp exp ) {
    throw new Error("concur is not supported");
  }
 
  protected ContextProviderImpl getContext() {
    Node n = node;
    while(!(n instanceof Element) && n!=null) {
      if(n instanceof Attr)
        n = ((Attr)n).getOwnerElement();
      else
        n = n.getParentNode();
    }
    if(n==null)    throw new Error()// impossible
    return new ContextProviderImpl((Element)n);
  }
 
  /** generaets a name that satisfies given NameClass */
  private StringPair getName( NameClass nc ) {
    StringPair name = opts.nameGenerator.generate(nc);
   
    if( !nc.accepts( name.namespaceURI, name.localName ) )
      throw new Error()// invalid
   
    return name;
  }
 
 
  /** enumerates children of BinaryExp into a vector. */
  private Vector getChildren( BinaryExp exp ) {
    final Vector vec = new Vector();
    Iterator itr = exp.children();
    while( itr.hasNext() )  vec.add( itr.next() );
    return vec;
  }

  /**
   * generates 'typo'.
   */
  protected StringPair generateTypo( StringPair pair ) {
    // in this implementation, typo is made only to localName.
    StringBuffer buf = new StringBuffer(pair.localName);
   
    int idx = opts.random.nextInt(buf.length());
   
    char ch = buf.charAt(idx);
    if( XmlChars.isNCNameChar((char)(ch-1)) )
      ch = (char)(ch-1);
    else
    if( XmlChars.isNCNameChar((char)(ch+1)) )
      ch = (char)(ch+1);
    else
    do
    {
      ch = (char)opts.random.nextInt();
    }while(!XmlChars.isNCNameChar(ch));
   
    buf.setCharAt( idx, ch );
   
    return new StringPair(pair.namespaceURI,buf.toString());
  }
 
}
TOP

Related Classes of com.sun.msv.generator.Generator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.