Package com.sun.tahiti.compiler.sm

Source Code of com.sun.tahiti.compiler.sm.MarshallerGenerator

/*
* @(#)$Id: MarshallerGenerator.java 1256 2001-10-23 18:27:03Z Bear $
*
* 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.tahiti.compiler.sm;

import com.sun.tahiti.compiler.Symbolizer;
import com.sun.tahiti.compiler.XMLWriter;
import com.sun.tahiti.grammar.*;
import com.sun.msv.grammar.*;
import com.sun.msv.grammar.util.ExpressionWalker;
import com.sun.msv.reader.GrammarReaderController;
import org.xml.sax.SAXException;
import org.apache.xml.serialize.XMLSerializer;
import org.apache.xml.serialize.OutputFormat;
import java.io.ByteArrayOutputStream;
import java.util.Set;
import java.util.Iterator;

/**
* generates a simple marhsllaer for a ClassItem.
*
* <p>
* This algorithm cannot generate a marshaller for complex object models.
*
* @author
*  <a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
*/
public class MarshallerGenerator implements ExpressionVisitorVoid {

  /**
   * generates a marshaller for the given ClassItem.
   *
   * <p>
   * Using this method directly is discouraged due to the following reasons.
   *
   * <ul>
   <li>
   *    This method does NOT call the startDocument/endDocument events
   *    of the handler. The caller has to invoke them if necessary.
   *  </li><li>
   *    This method could fail even after several fragments are sent
   *    to a XMLWriter object, by throwing an Abort exception.
   *    This happens when the object model is
   *    too compilcated for this algorithm. The caller has to catch it
   *    and cancel any sideeffects caused by already written fragments.
   *  </li>
   * </ul>
   *
   * <p>
   * This method throws an Abort exception if it finds impossible to generate
   * a marshaller.
   */
  public static void write( Symbolizer symbolizer, ClassItem cls, XMLWriter out,
        final GrammarReaderController controller ) {
   
    cls.exp.visit(new MarshallerGenerator(symbolizer,cls,out,controller));
  }

  /**
   * generates a marshaller for the given ClassItem.
   *
   * @return
   *    a byte array that contains XML representation of the marshaller.
   *    null if this method fails to produce a marhsaller.
   */
  public static byte[] write( Symbolizer symbolizer, ClassItem cls,
          GrammarReaderController controller ) throws SAXException {
   
    try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
   
      final XMLWriter out = new XMLWriter(
        new XMLSerializer(baos,new OutputFormat("xml",null,true) ));
     
      out.handler.startDocument();
      write( symbolizer, cls, out, controller );
      out.handler.endDocument();
     
      return baos.toByteArray();
    } catch ( XMLWriter.SAXWrapper w ) {
      throw w.e;
    } catch ( Abort a ) {
      return null;
    }
  }
 
 
 
  private MarshallerGenerator( Symbolizer symbolizer,
    ClassItem cls, XMLWriter out, GrammarReaderController controller) {
   
    this.symbolizer = symbolizer;
    this.owner = cls;
    this.out = out;
    this.controller = controller;
  }
 
  private final Symbolizer symbolizer;
  private final ClassItem owner;
  private final XMLWriter out;
  private final GrammarReaderController controller;
 
  /**
   * Exception that indicates the object model is too complicated
   * to generate a marshaller by this algorithm.
   */
  public static class Abort extends RuntimeException {};
 

 
  /**
   * &lt;oneOrMore/> will produce the following marshaller.
   *
   * <PRE>
   * do {
   *   marshal body;
   * } while( item available );
   * </PRE>
   */
  public void onOneOrMore( OneOrMoreExp exp ) {
       
    Set uniqueFields = calcUniqueField( owner.exp, exp );
    if( uniqueFields==null ) {
      // this class cannot be marshalled.
      controller.warning( null,
        localize(
          UNABLE_TO_PRODUCE_MARSHALLER_ONEORMORE, owner.getTypeName() ));
      throw new Abort();
    }
    if( uniqueFields.size()==0 ) {
      // there is no field in this.
      // so ignore this <oneOrMore>.
      exp.exp.visit(this);
      return;
    }
       
   
    out.start("oneOrMore",new String[]{"while",setToString(uniqueFields)});
    exp.exp.visit(this);
    out.end("oneOrMore");
  }
       
  public void onChoice( ChoiceExp exp ) {
    Expression[] children = exp.getChildren();
    Expression otherwise = null;
    Expression undecidable = null;
         
    if( currentField!=null ) {
      /*
      if
        "this choice is contained in a FieldItem"
      and
      (
        (
          "the multiplicity of this field in this ClassItem is at most one"
            and
          "all the branches are either JavaItem or epsilon"
        )
          or
        "all the branches are JavaItem"
      )
     
      then we can produce a simple marshaller.
     
      this works for choices of <value>s/<data>s.
      */
      boolean hasEpsilonEdge = false;
      boolean fail = false;
      boolean hasMarshallableItem = false;
      boolean hasPrimitiveItem = false;
      // common type of primitives
      PrimitiveItem primitiveType = null;
     
      for( int i=0; i<children.length; i++ ) {
        if( children[i] instanceof InterfaceItem
        ||  children[i] instanceof ClassItem) {
          hasMarshallableItem = true;
          continue;
        }
        if( children[i] instanceof PrimitiveItem) {
          hasPrimitiveItem = true;
          PrimitiveItem thisType = (PrimitiveItem)children[i];
          if(primitiveType==null)    primitiveType = thisType;
          else
          // this short cut doesn't work if the underlying datatype is
          // different.
          if(primitiveType.dt!=thisType.dtfail = true;
          continue;
        }
       
        if( (children[i] instanceof IgnoreItem && children[i].isEpsilonReducible() )
        ||  children[i]==Expression.epsilon ) {
          hasEpsilonEdge = true;
          continue;
        }
       
        // otherwise, this simple short cut doesn't work
        fail = true;
        break;
      }
     
      if( hasMarshallableItem && hasPrimitiveItem )
        // this short cut doesn't work if these two types are mixed.
        fail = true;
     
      if( !fail && hasEpsilonEdge
      &&  !owner.getFieldUse(currentField.name).multiplicity.isAtMostOnce() )
        // if there is an epsilon-edge, then the entire field must be (0,1)/(1,1)
        // multiplicity.
        fail = true;
     
      if(!fail) {
        // all check done. the short-cut can be used.
        if( hasEpsilonEdge ) {
          out.start("choice");
          out.start("option",new String[]{"if",currentField.name});
        }
       
        if( hasMarshallableItem ) {
          out.element("marshall", new String[]{
              "type", "object",
              "fieldName", currentField.name });
        } else {
          out.element("marshall", new String[]{
              "type", "primitive",
              "fieldName", currentField.name,
              "dataSymbol", symbolizer.getId(primitiveType.exp) });
        }
       
        if( hasEpsilonEdge ) {
          out.end("option");
          out.start("otherwise");
          out.element("epsilon");
          out.end("otherwise");
          out.end("choice");
        }
       
        return;
      }
    }
   
     
     
    out.start("choice");
   
    for( int i=0; i<children.length; i++ ) {
      Set uniqueFields = calcUniqueField( owner.exp, children[i] );
           
      if( uniqueFields==null ) {
        // we can possibly have one branch which doesn't have
        // the unique field. ("possibly" means if there is
        // no "otherwise" branch.)
        if(undecidable!=null) {
          // unable to produce marshaller.
//          System.out.println(
//            com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(exp));
//          System.out.println(
//            com.sun.msv.grammar.util.ExpressionPrinter.printFragment(owner.exp));
          controller.warning( null,
            localize(
              UNABLE_TO_PRODUCE_MARSHALLER_UNDECIDABLE_CHOICE, owner.getTypeName() ));
          throw new Abort();
        }
        undecidable = children[i];
        continue;
      }
      if( uniqueFields.size()==0 ) {
        // if this branch doesn't have a FieldItem, use it
        // as the "otherwise" case.
        otherwise = children[i];
        continue;
      }
           
      out.start("option",new String[]{"if",setToString(uniqueFields)});
      children[i].visit(this);
      out.end("option");
    }
    if( otherwise!=null && undecidable!=null ) {
      // we have both the "otherwise" branch and the "undecidable" branch.
      // we can't tell which branch to use, so we cannot produce a marshaller
      controller.warning( null,
        localize(
          UNABLE_TO_PRODUCE_MARSHALLER_MULTIPLE_DEFAULTS, owner.getTypeName() ));
      throw new Abort();
    }
         
    // we can have one undecidable branch or one "otherwise" branch.
    // this branch should be expanded if any other choices fail.
    out.start("otherwise");
    if(otherwise!=null)    otherwise.visit(this);
    else
    if(undecidable!=nullundecidable.visit(this);
    else
      out.element("notPossible");
    out.end("otherwise");
           
    out.end("choice");
  }
 
  /**
   * converts a set of Strings into whitespace-delimited string.
   */     
  private String setToString( Set s ) {
    StringBuffer buffer = new StringBuffer();
    Iterator itr = s.iterator();
    while( itr.hasNext() ) {
      buffer.append(' ');
      buffer.append(itr.next());
    }
    return buffer.toString();
  }
       
  public void onEpsilon() {
    out.element("epsilon");
  }
       
  /**
   * If there is a FieldItem that contains the currently visited expression,
   * then this field holds a reference to that object. Otherwise set to null.
   */
  private FieldItem currentField = null;
       
  public void onOther( OtherExp exp ) {
    if( exp instanceof FieldItem ) {
      // in properly normalized AGM, it can never be possible
      // for two FieldItems to nest.
      assert( currentField==null );
      currentField = (FieldItem)exp;
      exp.exp.visit(this);
      assert( currentField==exp );
      currentField = null;
      return;
    }
    if( exp instanceof ClassItem || exp instanceof InterfaceItem ) {
      out.element("marshall",
        new String[]{
          "type","object",
          "fieldName", currentField.name });
      return;
    }
    if( exp instanceof PrimitiveItem ) {
      out.element("marshall",
        new String[]{
          "type","primitive",
          "fieldName", currentField.name,
          "dataSymbol", symbolizer.getId(((PrimitiveItem)exp).exp) });
      return;
    }
    if( exp instanceof IgnoreItem ) {
      if( !exp.exp.isEpsilonReducible() ) {
        // if an IgnoreItem is not epsilon-reducible,
        // then apparently we cannot marshall this class.
        controller.warning( null,
          localize(
            UNABLE_TO_PRODUCE_MARSHALLER_IGNOREITEM, owner.getTypeName() ));
        throw new Abort();
      } else
        out.element("epsilon");
           
      return;
    }
         
    throw new Error(exp.toString());
  }
       
  public void onSequence( SequenceExp exp )    { onGroup(exp); }
  public void onInterleave( InterleaveExp exp )  { onGroup(exp); }
  public void onGroup( BinaryExp exp ) {
    out.start("group");
    Expression[] children = exp.getChildren();
    for( int i=0; i<children.length; i++ )
      children[i].visit(this);
    out.end("group");
  }
       
  public void onAttribute( AttributeExp exp ) {
    // TODO: support non-simple name class.
    if(!(exp.nameClass instanceof SimpleNameClass))
      throw new Error();
   
    onItem( "attribute", (SimpleNameClass)exp.nameClass, exp.exp );
  }
       
  public void onElement( ElementExp exp ) {
    // TODO: support non-simple name class.
    if(!(exp.getNameClass() instanceof SimpleNameClass))
      throw new Error();
    onItem( "element", (SimpleNameClass)exp.getNameClass(), exp.contentModel );
  }
       
  public void onItem( String tag, SimpleNameClass nc, Expression body ) {
    out.start(tag,
      new String[]{"uri", nc.namespaceURI, "name", nc.localName});
    body.visit(this);
    out.end(tag);
  }
       
  public void onList( ListExp exp ) {
    out.start("list");
    exp.exp.visit(this);
    out.end("list");
  }
       
       
  // expressions that do not affect the marshaller.
  public void onRef( ReferenceExp exp ) {
    exp.exp.visit(this);
  }
       
  // these methods shall never be called
  public void onMixed( MixedExp exp ) {
    throw new Error();
  }
  public void onConcur( ConcurExp exp ) {
    throw new Error();
  }
  public void onAnyString() {
    throw new Error();
  }
  public void onNullSet() {
    throw new Error();
  }
    // DataExp/ValuExp should have been wrapped by a PrimitiveItem.
  public void onData( DataExp exp ) {
    throw new Error();
  }
  public void onValue( ValueExp exp ) {
    throw new Error();
  }
 
 
 
  /**
   * computes the field name which can only appear in the specified branch
   * among the entire tree starting from the 'root' node.
   *
   * <p>
   * use caution. Since Expressions are shared, it is possible that the root
   * exp contains the branch more than once. For example,
   *
   * <pre>
   * root -> SequenceExp(branch,branch)
   * </pre>
   *
   * @return
   *    this method returns null to indicate that there is no unique name.
   *    Otherwise, this method returns a set
   *    of the fields which appear in this branch but not elsewhere.
   */
  private Set calcUniqueField( Expression root, final Expression branch ) {

    final Set branchFields = new java.util.HashSet();
   
    branch.visit( new FieldWalker(currentField){
      // this marshaller cannot support AGM that contains a loop
      protected void findField( FieldItem field, Type child ) {
        branchFields.add( field.name );
      }
    });

    root.visit( new ExpressionVisitorVoid(){
      private boolean visitBranch = false;
      private FieldItem currentField = null;

      public void onOther( OtherExp exp ) {
        if(!test(exp))  return;
       
        if( exp instanceof FieldItem ) {
          assert( currentField==null );
          currentField = (FieldItem)exp;
          exp.exp.visit(this);
          assert( currentField==exp );
          currentField = null;
          return;
        }
        if( exp instanceof ClassItem || exp instanceof InterfaceItem
        ||  exp instanceof PrimitiveItem ) {
          assert( currentField!=null );
          if(branchFields.remove( currentField.name )) {
/*            System.out.println(currentField.name+" is removed");
            System.out.println("owner is "+
              com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(owner));
            System.out.println("branch is "+branch.toString());
            System.out.println(
              com.sun.msv.grammar.util.ExpressionPrinter.printContentModel(branch));
            Exception e = new Exception();
            e.fillInStackTrace();
            e.printStackTrace(System.out);
*/          }
          return;
        }
        if( exp instanceof IgnoreItem return;
        assert(!(exp instanceof JavaItem));
        exp.exp.visit(this);
      }
      public void onChoice( ChoiceExp exp )    { onBinExp(exp); }
      public void onSequence( SequenceExp exp )  { onBinExp(exp); }
      public void onInterleave( InterleaveExp exp){ onBinExp(exp); }
      public void onBinExp( BinaryExp exp ) {
        if(test(exp)) {
          exp.exp1.visit(this);
          exp.exp2.visit(this);
        }
      }
     
      public void onElement( ElementExp exp ) {
        if(test(exp))  exp.contentModel.visit(this);
      }
      public void onAttribute( AttributeExp exp ) {
        if(test(exp))  exp.exp.visit(this);
      }
      public void onRef( ReferenceExp exp ) {
        if(test(exp))  exp.exp.visit(this);
      }
      public void onOneOrMore( OneOrMoreExp exp ) {
        if(test(exp))  exp.exp.visit(this);
      }
      public void onList( ListExp exp ) {
        if(test(exp))  exp.exp.visit(this);
      }
      public void onEpsilon() {}
     
      private boolean test( Expression exp ) {
        if( exp!=branch ) {
/*          if( exp instanceof OneOrMoreExp && branch instanceof OneOrMoreExp ) {
            try {
            System.out.println( exp.toString()+" vs "+branch.toString() );
            System.out.println(
              ((OneOrMoreExp)exp).exp.toString() +" , "+
              ((OneOrMoreExp)branch).exp.toString() );
            FieldItem lhs = (FieldItem)((OneOrMoreExp)exp).exp;
            FieldItem rhs = (FieldItem)((OneOrMoreExp)branch).exp;
            System.out.println( lhs.name + " : " + rhs.name );
            System.out.println( lhs.exp.toString() + " / " + rhs.exp.toString() );
            System.out.println(
              ((InterfaceItem)lhs.exp).name
              + " / " +
              ((ClassItem)rhs.exp).name );
            } catch( Exception e ) {;}
          }
*/          return true;
        }
       
        if(visitBranch)
          // this is the second time to visit the branch.
          // that means this branch is contained more than once.
          // so there is no unique field.
          branchFields.clear();
          // by setting branchFields empty, the calcUniqueField method
          // will return null.
        else
          visitBranch = true;
        return false;
      }
     
       
     
      // these primitives should not be used.
      public void onMixed( MixedExp exp ) { throw new Error(); }
      public void onConcur( ConcurExp exp ) { throw new Error(); }
      public void onData( DataExp exp ) { throw new Error(); }
      public void onValue( ValueExp exp ) { throw new Error(); }
      public void onNullSet() { throw new Error(); }
      public void onAnyString() { throw new Error(); }
    });
   
   
    // there is no unique field.
    if( branchFields.size()==0 return null;
   
    return branchFields;
  }
 
 
  private static void assert( boolean b ) {
    if(!bthrow new Error();
  }


  private static String localize( String prop, Object arg ) {
    return localize( prop, new Object[]{arg} );
  }

  private static String localize( String prop, Object[] args ) {
    String format = java.util.ResourceBundle.getBundle(
      "com.sun.tahiti.compiler.sm.Messages").getString(prop);
   
      return java.text.MessageFormat.format(format, args );
  }

  static final String UNABLE_TO_PRODUCE_MARSHALLER_ONEORMORE = // arg:1
    "MarshallerGenerator.OneOrMore";
  static final String UNABLE_TO_PRODUCE_MARSHALLER_UNDECIDABLE_CHOICE = // arg:1
    "MarshallerGenerator.UndecidableChoice";
  static final String UNABLE_TO_PRODUCE_MARSHALLER_MULTIPLE_DEFAULTS = // arg:1
    "MarshallerGenerator.MultipleDefaults";
  static final String UNABLE_TO_PRODUCE_MARSHALLER_IGNOREITEM = // arg:1
    "Marshaller.IgnoreItem";
}
TOP

Related Classes of com.sun.tahiti.compiler.sm.MarshallerGenerator

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.