package org.kohsuke.bali.writer;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import org.kohsuke.bali.automaton.AlphabetVisitor;
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.Transition;
import org.kohsuke.bali.automaton.TreeAutomaton;
import org.kohsuke.bali.automaton.ValueAlphabet;
import org.kohsuke.bali.automaton.builder.TooComplicatedException;
import org.kohsuke.bali.datatype.DatatypeImpl;
import org.kohsuke.bali.datatype.ValidationContextImpl;
import org.kohsuke.bali.datatype.Value;
import org.relaxng.datatype.Datatype;
import com.sun.msv.util.StringPair;
/**
* Encodes an automaton into a set of strings so that
* the Java/C# validatelet can process it.
*
* @author Kohsuke Kawaguchi (kk@kohsuke.org)
*/
public abstract class StringBasedEncoder implements AutomatonWriter {
/**
* Processes the encoded strings.
*/
protected abstract void write(
String encNameCodes,
int defaultNameCode,
String encStates,
String encATr,
String encDTr,
String encETr,
String encITr,
String encLTr,
String encNTr,
String encVTr,
DatatypeImpl[] datatypes,
Value[] values
) throws IOException;
public void write(TreeAutomaton automaton) throws IOException {
StringBuffer encNameCodes = new StringBuffer();
int defaultNameCode;
{// encode name code map
StringPair[] literals = automaton.listNameCodes();
for( int i=0; i<literals.length; i++ ) {
StringPair literal = literals[i];
if( literal==TreeAutomaton.WILDCARD ) continue; // don't need to encode this.
encNameCodes.append( encodeInt(automaton.getNameCode(literal)) );
encNameCodes.append(literal.namespaceURI.equals("\u0000")?"*":literal.namespaceURI);
encNameCodes.append('\u0000');
encNameCodes.append(literal.localName.equals("\u0000")?"*":literal.localName);
encNameCodes.append('\u0000');
}
defaultNameCode = automaton.getNameCode(TreeAutomaton.WILDCARD);
}
// encode all the states into one string.
State[] states = automaton.getStates();
limit( states.length < 65535 ); // 0xFFFF is reserved for "not exist".
StringBuffer encStates = new StringBuffer(states.length*8); // encoded states
final StringBuffer encATr = new StringBuffer(); // encoded attribute transitions
final StringBuffer encDTr = new StringBuffer(); // encoded data transitions
final StringBuffer encETr = new StringBuffer(); // encoded element transitions
final StringBuffer encITr = new StringBuffer(); // encoded interleave transitions
final StringBuffer encLTr = new StringBuffer(); // encoded list transitions
final StringBuffer encNTr = new StringBuffer(); // encoded non-existent attribute transitions
final StringBuffer encVTr = new StringBuffer(); // encoded value transitions
// datatypes that are already encoded
final List datatypes = new ArrayList();
final List values = new ArrayList();
for( int i=0; i<states.length; i++ ) {
State s = states[i];
encStates.append((char)('0'
+(s.getTextSensitivity()*4) // 2 bits
+(s.isFinal?2:0)
+(s.isPersistent()?1:0))); // bit-encoded flags: isFinal/isPersistent/isTextIgnorable
encStates.append(encode(s.nextState));
encStates.append((char)encATr.length()); // index to the transition table
encStates.append((char)encDTr.length());
encStates.append((char)encETr.length());
encStates.append((char)encITr.length());
encStates.append((char)encLTr.length());
encStates.append((char)encNTr.length());
encStates.append((char)encVTr.length());
Transition[] trs = s.getDeclaredTransitions();
for( int j=0; j<trs.length; j++ ) {
final Transition tr = trs[j];
tr.alphabet.accept(new AlphabetVisitor() {
public Object attribute(AttributeAlphabet alpha) {
encATr.append(encode(tr.left));
encATr.append(encode(tr.right));
encATr.append(encode(alpha.name));
encATr.append(alpha.repeated?'R':'-');
return null;
}
public Object element(ElementAlphabet alpha) {
encETr.append(encode(tr.left));
encETr.append(encode(tr.right));
encETr.append(encode(alpha.name));
return null;
}
public Object nonExistentAttribute(NonExistentAttributeAlphabet alpha) {
limit( alpha.negativeNameTests.length<256 );
limit( alpha.positiveNameTests.length<256 );
// left state is unused with this type of alphabets
encNTr.append(encode(tr.right));
// encode the size of name signatures
encNTr.append( (char)(
(alpha.negativeNameTests.length<<8)+alpha.positiveNameTests.length) );
for( int i=0; i<alpha.negativeNameTests.length; i++ )
encNTr.append( encode(alpha.negativeNameTests[i]) );
for( int i=0; i<alpha.positiveNameTests.length; i++ )
encNTr.append( encode(alpha.positiveNameTests[i]) );
return null;
}
public Object interleave(InterleaveAlphabet alpha) {
encITr.append(encode(tr.left));
encITr.append(encode(tr.right));
encITr.append(encode(alpha.join));
encITr.append(alpha.textToLeft?'L':'R');
return null;
}
public Object list(ListAlphabet alpha) {
encLTr.append(encode(tr.left));
encLTr.append(encode(tr.right));
return null;
}
public Object data(DataAlphabet alpha) {
encDTr.append(encode(tr.left));
encDTr.append(encode(tr.right));
encDTr.append(encode(alpha.datatype,datatypes));
return null;
}
public Object value(ValueAlphabet alpha) {
// encode the value object and obtain its index
int idx = values.size();
values.add(alpha.value);
encVTr.append(encode(tr.right));
encVTr.append(encode(alpha.datatype,datatypes));
encVTr.append((char)idx);
return null;
}
});
}
}
limit( encATr.length()<Character.MAX_VALUE);
limit( encDTr.length()<Character.MAX_VALUE);
limit( encETr.length()<Character.MAX_VALUE);
limit( encITr.length()<Character.MAX_VALUE);
limit( encLTr.length()<Character.MAX_VALUE);
limit( encNTr.length()<Character.MAX_VALUE);
limit( encVTr.length()<Character.MAX_VALUE);
write( encNameCodes.toString(), defaultNameCode, encStates.toString(),
encATr.toString(),
encDTr.toString(),
encETr.toString(),
encITr.toString(),
encLTr.toString(),
encNTr.toString(),
encVTr.toString(),
(DatatypeImpl[]) datatypes.toArray(new DatatypeImpl[datatypes.size()]),
(Value[]) values.toArray(new Value[values.size()]) );
}
/** Encodes an integer (32 bits) into two charactres (2*16bits) */
private String encodeInt( int i ) {
return new String(new char[]{ (char)(i&0xFFFF), (char)(i>>16) });
}
/** Encodes a state into a character. */
private char encode( State s ) {
if(s==null) return '\uFFFF';
else return (char)s.id;
}
/** Encodes a name signature into 4 characters. */
private String encode( NameSignature name ) {
return encodeInt(name.mask) + encodeInt(name.test);
}
/** Encodes a datatype object and returns its index. */
private char encode( Datatype _dt, List datatypes ) {
DatatypeImpl dt = (DatatypeImpl)_dt;
// see if this datatype has already been encoded
int idx = datatypes.indexOf(dt);
if(idx==-1) {
idx = datatypes.size();
datatypes.add(dt);
}
return (char)idx;
}
/** Encodes a datatype object and returns its index. */
protected String encode( DatatypeImpl dt ) {
StringBuffer buf = new StringBuffer();
for( int i=0; i<dt.parameters.length; i++ ) {
if( buf.length()!=0 )
buf.append(',');
buf.append(escape(dt.parameters[i].name));
buf.append(',');
buf.append(escape(dt.parameters[i].value));
buf.append(',');
buf.append(encodeContext(dt.parameters[i].context));
}
return escape(dt.nsURI)+","
+ escape(dt.name)+","
+ "new Object[]{"+buf+"}";
}
protected String encodeContext( ValidationContextImpl context ) {
String[] contexts = context.getQueriedNamespaces();
if( contexts.length==0 )
return "null";
else
return "new String[]{"+flatten(contexts,',')+"}";
}
/**
* Combines all strings in the collection by inserting a separator
* between tokens.
*/
private String flatten( String[] values, char sep ) {
StringBuffer result = new StringBuffer();
for( int i=0; i<values.length; i++ ) {
if(result.length()!=0) result.append(sep);
result.append(escape(values[i]));
}
return result.toString();
}
/**
* Escape unsafe characters in the target platform and
* enquote.
*/
protected abstract String escape( String str );
/** Tests the limitation of this implementation. */
private void limit( boolean test ) {
if(!test)
throw new TooComplicatedException();
}
//
//
// print with format
//
//
protected String format( String propName, Object arg1 ) {
return format( propName, new Object[]{arg1} );
}
protected String format( String propName, Object arg1, Object arg2 ) {
return format( propName, new Object[]{arg1,arg2} );
}
protected String format( String propName, Object arg1, Object arg2, Object arg3 ) {
return format( propName, new Object[]{arg1,arg2,arg3} );
}
protected String format( String property, Object[] args ) {
String text = ResourceBundle.getBundle(
this.getClass().getName()).getString(property);
return MessageFormat.format(text,args);
}
}