/*
* @(#)$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 {};
/**
* <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.dt) fail = 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!=null) undecidable.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(!b) throw 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";
}