package org.kohsuke.bali.writer; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import org.kohsuke.bali.automaton.Alphabet; import org.kohsuke.bali.automaton.AttributeAlphabet; import org.kohsuke.bali.automaton.DataAlphabet; import org.kohsuke.bali.automaton.ElementAlphabet; import org.kohsuke.bali.automaton.InterleaveAlphabet; import org.kohsuke.bali.automaton.ListAlphabet; import org.kohsuke.bali.automaton.NameSignature; import org.kohsuke.bali.automaton.NonExistentAttributeAlphabet; import org.kohsuke.bali.automaton.State; import org.kohsuke.bali.automaton.TextAlphabet; import org.kohsuke.bali.automaton.Transition; import org.kohsuke.bali.automaton.TreeAutomaton; import org.kohsuke.bali.automaton.ValueAlphabet; import org.kohsuke.bali.datatype.DatatypeImpl; import org.kohsuke.bali.datatype.Parameter; import org.kohsuke.bali.datatype.Value; import org.relaxng.datatype.Datatype; import com.sun.msv.util.StringPair; /** * Produces C++ code for Windows/Visual C++ environment * that works with MSXML. * * @author Kohsuke Kawaguchi (kk@kohsuke.org) */ public class WinCppWriter implements AutomatonWriter { public WinCppWriter( String namespaceName, String className, File outDir ) throws IOException { this.namespaceName = namespaceName; this.className = className; targetDir = outDir; cppFile = new PrintWriter( new FileWriter( new File( outDir, className+".cpp" ) ) ); headerFile = new PrintWriter( new FileWriter( new File( outDir, className+".h" ) ) ); } /** Namespace to put the generated code, or null if no namespace is necessary. */ private final String namespaceName; private final String className; /** Writer connected to the generated source file. */ private final PrintWriter cppFile; /** Writer connected to the generated header file. */ private final PrintWriter headerFile; /** The directory to which all the generated files will be placed. */ private final File targetDir; /** * Stores objects and assign them continuous index number that * starts from 0. * <p> * Used to store transitions into one array. */ private class IndexMap { /** back-end storage. */ private final HashMap core = new HashMap(); /** storage for reverse look up */ private final ArrayList list = new ArrayList(); /** Gets the index assigned to the given object. */ public int get( Object o ) { // let it cause an error if the object is not in the table return ((Integer)core.get(o)).intValue(); } /** Gets the object with the specified index. */ public Object get( int index ) { return list.get(index); } public int size() { return list.size(); } /** * Stores a new object into the map. * * If the specified object is new, this method assigns a new * index and returns it. If it is already contained in this set, * just return the existing index number. */ public int put( Object o ) { Object v = core.get(o); if(v!=null) return ((Integer)v).intValue(); // new object. assign a new number core.put( o, new Integer(index) ); list.add( o ); return index++; } public boolean contains( Object o ) { return core.containsKey(o); } private int index = 0; } public void write(TreeAutomaton automaton) throws IOException { // initialize buffers datatypesCode = new StringWriter(); datatypesWriter = new PrintWriter(datatypesCode); datatypes = new IndexMap(); nameTestsCode = new StringWriter(); nameTestsWriter = new PrintWriter(nameTestsCode); cppFile.println("#include \"stdafx.h\""); cppFile.println("#include \"validateletImpl.h\""); cppFile.println("#include \""+className+".h\""); cppFile.println(); if( namespaceName!=null ) cppFile.println("namespace "+namespaceName+" {"); cppFile.println("using namespace MSXML2;"); cppFile.println("using namespace bali;"); cppFile.println("using namespace bali::transition;"); cppFile.println(); cppFile.println("SingleState states[];"); cppFile.println("Datatype* datatypes[];"); cppFile.println("NameSignature nameTests[];"); cppFile.println(); // do the real work State[] states = automaton.getStates(); // print attributes IndexMap amap = printAlphabets( automaton, AttributeAlphabet.class, "Att", "aTr", new Printer() { public void print(Transition tr, int previousIndex) { AttributeAlphabet a = (AttributeAlphabet)tr.alphabet; cppFile.println(MessageFormat.format( " '{' '{' {0},{1} '}',{2},states+{3},states+{4},{5} '}',", new Object[] { new Integer(a.name.mask), new Integer(a.name.test), a.repeated?"true":"false", new Integer(tr.left.id), new Integer(tr.right.id), (previousIndex==-1)?"NULL":"aTr+"+previousIndex })); } public void printEmptyLine() { cppFile.println(" { {-1,-1}, false,NULL,NULL,NULL },"); } }); // print datatype alphabets IndexMap dmap = printAlphabets( automaton, TextAlphabet.class, "Data", "dTr", new Printer() { public void print(Transition tr, int previousIndex) { if( tr.alphabet instanceof DataAlphabet ) { DataAlphabet d = (DataAlphabet)tr.alphabet; cppFile.println(MessageFormat.format( " '{' states+{0},states+{1},datatypes+{2},{3} '}',", new Object[] { new Integer(tr.left.id), new Integer(tr.right.id), new Integer(printDatatype(d.datatype)), (previousIndex==-1)?"NULL":"dTr+"+previousIndex })); } if( tr.alphabet instanceof ValueAlphabet ) { ValueAlphabet v = (ValueAlphabet)tr.alphabet; cppFile.println(MessageFormat.format( " '{' NULL,states+{1},datatypes+{2},{3} '}',", new Object[] { null, new Integer(tr.right.id), new Integer(printDatatype(v)), (previousIndex==-1)?"NULL":"dTr+"+previousIndex })); } } public void printEmptyLine() { cppFile.println(" { NULL,NULL,NULL,NULL },"); } }); // print elements IndexMap emap = printAlphabets( automaton, ElementAlphabet.class, "Element", "eTr", new Printer() { public void print(Transition tr, int previousIndex) { ElementAlphabet e = (ElementAlphabet)tr.alphabet; cppFile.println(MessageFormat.format( " '{' '{' {0},{1} '}',states+{2},states+{3},{4} '}',", new Object[] { new Integer(e.name.mask), new Integer(e.name.test), new Integer(tr.left.id), new Integer(tr.right.id), (previousIndex==-1)?"NULL":"eTr+"+previousIndex })); } public void printEmptyLine() { cppFile.println(" { {-1,-1}, NULL,NULL,NULL },"); } }); // print interleaves IndexMap imap = printAlphabets( automaton, InterleaveAlphabet.class, "Interleave", "iTr", new Printer() { public void print(Transition tr, int previousIndex) { InterleaveAlphabet i = (InterleaveAlphabet)tr.alphabet; cppFile.println(MessageFormat.format( " '{' states+{0},states+{1},states+{2},{3},{4} '}',", new Object[] { new Integer(tr.left.id), new Integer(tr.right.id), new Integer(i.join.id), i.textToLeft?"true":"false", (previousIndex==-1)?"NULL":"iTr+"+previousIndex })); } public void printEmptyLine() { cppFile.println(" { NULL,NULL,NULL,false,NULL },"); } }); // print lists IndexMap lmap = printAlphabets( automaton, ListAlphabet.class, "List", "lTr", new Printer() { public void print(Transition tr, int previousIndex) { ListAlphabet l = (ListAlphabet)tr.alphabet; cppFile.println(MessageFormat.format( " '{' states+{0},states+{1},{2} '}',", new Object[] { new Integer(tr.left.id), new Integer(tr.right.id), (previousIndex==-1)?"NULL":"lTr+"+previousIndex })); } public void printEmptyLine() { cppFile.println(" { NULL,NULL,NULL },"); } }); // print nonAtts IndexMap nmap = printAlphabets( automaton, NonExistentAttributeAlphabet.class, "NoAtt", "nTr", new Printer() { public void print(Transition tr, int previousIndex) { NonExistentAttributeAlphabet n = (NonExistentAttributeAlphabet)tr.alphabet; cppFile.println(MessageFormat.format( " '{' states+{0},nameTests+{1},{2},nameTests+{3},{4},{5} '}',", new Object[] { new Integer(tr.right.id), printNameTests(n.negativeNameTests), new Integer(n.negativeNameTests.length), printNameTests(n.positiveNameTests), new Integer(n.positiveNameTests.length), (previousIndex==-1)?"NULL":"nTr+"+previousIndex })); } public void printEmptyLine() { cppFile.println(" { NULL,NULL,0,NULL,0,NULL },"); } }); {// print datatype objects String str = datatypesCode.getBuffer().toString(); if(str.length()!=0) { cppFile.println("static Datatype* datatypes[] = {"); cppFile.write(str); cppFile.println("};"); cppFile.println(); } } {// print name signatures String str = nameTestsCode.getBuffer().toString(); if(str.length()!=0) { cppFile.println("static NameSignature nameTests[] = {"); cppFile.write(str); cppFile.println("};"); cppFile.println(); } } // print states cppFile.println("static StateInfo stateInfos[] = {"); for( int i=0; i<states.length; i++ ) { cppFile.println(MessageFormat.format( "'{' {0},{1},{2},{3},{4},{5},{6},{7},{8} '}',", new Object[] { Util.padl(Integer.toString(i),4), states[i].isFinal?"true ":"false", states[i].isPersistent()?"true ":"false", findFirstTransition(states[i],"aTr",amap,AttributeAlphabet.class), findFirstTransition(states[i],"dTr",dmap,TextAlphabet.class), findFirstTransition(states[i],"eTr",emap,ElementAlphabet.class), findFirstTransition(states[i],"iTr",imap,InterleaveAlphabet.class), findFirstTransition(states[i],"lTr",lmap,ListAlphabet.class), findFirstTransition(states[i],"nTr",nmap,NonExistentAttributeAlphabet.class) })); } cppFile.println("};"); cppFile.println(); cppFile.println(MessageFormat.format("static SingleState states[{0}];", new Object[]{ new Integer(states.length) })); cppFile.println(); // print name literals StringPair[] literals = automaton.listNameCodes(); if( literals.length!=0 ) { cppFile.println("static NameLiteral nameLiterals[] = {"); for( int i=0; i<literals.length; i++ ) { String uri = literals[i].namespaceURI; String local = literals[i].localName; if( uri==TreeAutomaton.IMPOSSIBLE ) uri = "*"; if( local==TreeAutomaton.IMPOSSIBLE ) local = "*"; cppFile.println(MessageFormat.format(" '{' L{0},L{1},{2} '}',", new Object[]{ Util.toCppString(uri), Util.toCppString(local), new Integer( automaton.getNameCode(literals[i]) ) })); } cppFile.println("};"); cppFile.println(); } cppFile.println("// schema object"); cppFile.println(MessageFormat.format("Schema {0}(states,stateInfos,{1},states+0,{2},{3},{4});", new Object[]{ className, new Integer(states.length), (literals.length!=0)?"nameLiterals":"NULL", new Integer(literals.length), new Integer(automaton.getNameCode(TreeAutomaton.WILDCARD)) })); cppFile.println(); cppFile.println( MessageFormat.format( "ISAXContentHandler* create{0}SAXValidatelet() '{'\n"+ " ComObject<Validatelet>* p = new ComObject<Validatelet>();\n"+ " p->AddRef();\n"+ " p->setSchema(&{1});\n"+ " return p;\n"+ "'}'", new Object[]{ Util.capitalizeFirst(className), className } )); cppFile.println( MessageFormat.format( "bali::IValidatelet* create{0}DOMValidatelet() '{'\n"+ " ComObject<Validatelet>* p = new ComObject<Validatelet>();\n"+ " p->AddRef();\n"+ " p->setSchema(&{1});\n"+ " return p;\n"+ "'}'", new Object[]{ Util.capitalizeFirst(className), className } )); cppFile.println("}"); // close namespace headerFile.println(MessageFormat.format( "#pragma once\n"+ "\n"+ "\n"+ "// for this validatelet to compile,\n"+ "// stdafx.h needs to have the following lines.\n"+ "/*\n"+ "#import <msxml4.dll>\n"+ "#include <crtdbg.h>\n"+ "#include \"validatelet.h\"\n"+ "*/\n"+ "\n"+ "\n"+ "// create a new instance of validatelet.\n"+ "// A validatelet will be returned with its reference count\n"+ "// incremented to 1. Thus it's the caller's responsibility to\n"+ "// release the object.\n"+ "{0}\n"+ "MSXML2::ISAXContentHandler* create{1}SAXValidatelet();\n"+ "bali::IValidatelet* create{1}DOMValidatelet();\n"+ "{2}\n", new Object[]{ namespaceName!=null?"namespace "+namespaceName+" {":"", Util.capitalizeFirst(className), namespaceName!=null?"}":"" })); cppFile.close(); headerFile.close(); copy("validatelet.h"); copy("validatelet.cpp"); copy("validateletImpl.h"); } /** Datatypes are generated into this string. */ private StringWriter datatypesCode; /** Writer object connected to the datatypesCode. */ private PrintWriter datatypesWriter; /** Maintains index for datatypes. */ private IndexMap datatypes; /** * Generates a datatype definition and returns * its index number. */ private int printDatatype( Datatype _dt ) { DatatypeImpl dt = (DatatypeImpl)_dt; if( datatypes.contains(dt) ) // this datatype has already been generated return datatypes.get(dt); int idx = datatypes.put(dt); PrintWriter w = datatypesWriter; w.print(" createDatatype(L"); w.print(Util.toCppString(dt.nsURI)); w.print(",L"); w.print(Util.toCppString(dt.name)); // TODO: context Parameter[] params = dt.parameters; for( int i=0; i<params.length; i++ ) { w.print(",L"); w.print(Util.toCppString(params[i].name)); w.print(",L"); w.print(Util.toCppString(params[i].value)); } w.println("),"); return idx; } /** * Generates a value alphabet as a datatype definition * and returns its index. */ private int printDatatype( ValueAlphabet va ) { if( datatypes.contains(va) ) // this datatype has already been generated return datatypes.get(va); int dtidx = printDatatype(va.datatype); // generate the underlying datatype first int idx = datatypes.put(va); datatypesWriter.println(MessageFormat.format( " createValueDatatype(datatypes+{0},L{1}),", new Object[] { new Integer(dtidx), // TODO: context Util.toCppString(((Value)va.value).value) })); return idx; } private StringWriter nameTestsCode; private PrintWriter nameTestsWriter; private int nameTestsSize = 0; /** * Prints all the name tests in one row and returns the index * to the first one. */ private Integer printNameTests( NameSignature[] tests ) { for( int i=0; i<tests.length; i++ ) nameTestsWriter.println(MessageFormat.format( " '{' {0},{1} '}',", new Object[] { new Integer(tests[i].mask), new Integer(tests[i].test) })); int r = nameTestsSize; nameTestsSize += tests.length; return new Integer(r); } private interface Printer { /** * Prints an alphabet. * * @param previousIndex * -1 if no previous alphabet, or its index. */ public void print( Transition tr, int previousIndex ); /** * Prints a dummy empty alphabet. */ public void printEmptyLine(); } private IndexMap printAlphabets( TreeAutomaton automaton, Class alphabetType, String typeName, String varName, Printer printer ) { IndexMap map = new IndexMap(); cppFile.println(MessageFormat.format("static {0} {1}[] = '{'", new Object[]{typeName,varName})); boolean hasAlphabet = false; State[] states = automaton.getStates(); for( int i=0; i<states.length; i++ ) { Object o = printAlphabets(alphabetType,map,states[i],printer); hasAlphabet |= o!=null; } if(!hasAlphabet) // if no alphabet was there, print an empty line (otherwise // the compiler will barf.) printer.printEmptyLine(); cppFile.println("};"); cppFile.println(""); return map; } /** * Prints all alphabets of the given type * and returns the lastly printed alphabet, or null if none. */ private Transition printAlphabets( Class alphabetType, IndexMap map, State st, Printer printer ) { Transition last; if( st.nextState!=null ) last = printAlphabets(alphabetType,map,st.nextState,printer); else last = null; Transition[] trs = st.getTransitions(); for( int i=0; i<trs.length; i++ ) { Transition tr = trs[i]; if( alphabetType.isInstance(tr.alphabet) ) { if( map.contains(tr) ) return tr; // this state has already been processed map.put(tr); // register this new alphabet printer.print( tr, last==null?-1:map.get(last) ); last = tr; } } return last; } /** * Finds the first transition of the given type of alphabet and * returns "[varName]+[index]". If none is found, return "NULL". */ private String findFirstTransition( State st, String varName, IndexMap map, Class alphabetType ) { // note that we have to search it in the reverse order Transition[] trs = st.getTransitions(); for( int i=trs.length-1; i>=0; i-- ) { Transition tr = trs[i]; if( alphabetType.isInstance(tr.alphabet) ) return varName+"+"+ Util.padl( Integer.toString(map.get(tr)), 3 ); } if( st.nextState!=null ) return findFirstTransition(st.nextState,varName,map,alphabetType); return Util.padr("NULL",7); } /** * Copies the resource file with the given file name to the target directory. * Used to produce runtime code the target directory. */ public void copy( String fileName ) throws IOException { InputStream is = this.getClass().getResourceAsStream("/wincpp/"+fileName); OutputStream os = new FileOutputStream( new File( targetDir, fileName ) ); // copy the data byte[] buf = new byte[256]; int len; while( (len=is.read(buf)) != -1 ) os.write(buf,0,len); is.close(); os.close(); } }