//
// FlowStepVarType.java - Convert variable type
//
// Note that when converting from String to Document,
// we don't support validation against a DTD.
// Instead, use of a DTD -> XSD conversion tool is recommended.
//
// Note that when converting from Document to String, you very often want
// to specify <outputproperty name="indent" value="yes"> in the <vartype>.
// Its not enough to use <xsl:output indent="yes"> in a preceeding XSLT
// file, as the XML transformations are handled using DOM Documents,
// rather than stream data.
//
// When converting to byte[], Strings are converted using character encoding
// information, and all other object types are Java serialized.
// Conversion of byte[] to Object is attempted using Java deserialization.
//

package nyangau.jmsadapter;

//...simports:0:
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StringReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Serializable;
import javax.jms.JMSException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ErrorHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
//...e

public class FlowStepVarType extends FlowStep
  {
  protected String var;
  protected String destvar;
//...senum VT:2:
enum VT
  {
  VT_String,
  VT_Document,
  VT_Boolean,
  VT_Byte,
  VT_Character,
  VT_Short,
  VT_Integer,
  VT_Long,
  VT_Float,
  VT_Double,
  VT_Bytes,
  VT_Object
  }
//...e
  protected VT vartype;
  // used when byte[]<->String
  protected String enc;
  // used when String->Document
  protected boolean coalescing;
  protected boolean expandentityreferences;
  protected boolean ignoringcomments;
  // used when Document->String
  protected Map<String,String> outputproperties;
//...sFlowStepVarType:2:
public FlowStepVarType(Node eStep)
  throws FlowProcessorException
  {
  super(eStep);
  NamedNodeMap nnmStep = eStep.getAttributes();
  Node aVar = nnmStep.getNamedItem("var");
  var = ( aVar != null ) ? aVar.getNodeValue() : "v0";
  Node aDestVar = nnmStep.getNamedItem("destvar");
  destvar = ( aDestVar != null ) ? aDestVar.getNodeValue() : var;
  Node aType = nnmStep.getNamedItem("type");
  if ( aType == null )
    throw new FlowProcessorException("unexpected missing type in step "+name+", bad jmsadapter.xsd?");
  String type = aType.getNodeValue();

  if ( type.equals("String") )
    vartype = VT.VT_String;
  else if ( type.equals("Document") )
    vartype = VT.VT_Document;
  else if ( type.equals("Boolean") )
    vartype = VT.VT_Boolean;
  else if ( type.equals("Byte") )
    vartype = VT.VT_Byte;
  else if ( type.equals("Character") )
    vartype = VT.VT_Character;
  else if ( type.equals("Short") )
    vartype = VT.VT_Short;
  else if ( type.equals("Integer") )
    vartype = VT.VT_Integer;
  else if ( type.equals("Long") )
    vartype = VT.VT_Long;
  else if ( type.equals("Float") )
    vartype = VT.VT_Float;
  else if ( type.equals("Double") )
    vartype = VT.VT_Double;
  else if ( type.equals("byte[]") )
    vartype = VT.VT_Bytes;
  else if ( type.equals("Object") )
    vartype = VT.VT_Object;
  else
    throw new FlowProcessorException("unexpected type "+type+" in step "+name+", bad jmsadapter.xsd?");

  // Used when converting byte[]<->String
  Node aEnc = nnmStep.getNamedItem("encoding");
  enc = ( aEnc != null ) ? aEnc.getNodeValue() : null;

  // Then are used when converting String->Document
  Node aCoalescing = nnmStep.getNamedItem("coalescing");
  coalescing = ( aCoalescing != null )
    ? aCoalescing.getNodeValue().equals("true")
    : true;
  Node aExpandEntityReferences = nnmStep.getNamedItem("expandentityreferences");
  expandentityreferences = ( aExpandEntityReferences != null )
    ? aExpandEntityReferences.getNodeValue().equals("true")
    : true;
  Node aIgnoringComments = nnmStep.getNamedItem("ignoringcomments");
  ignoringcomments = ( aIgnoringComments != null )
    ? aIgnoringComments.getNodeValue().equals("true")
    : false;

  // These are used when converting Document->String
  outputproperties = new HashMap<String,String>();
  NodeList nlOutputProps = eStep.getChildNodes();
  for ( int i = 0; i < nlOutputProps.getLength(); i++ )
    {
    Node eOutputProp = nlOutputProps.item(i);
    if ( eOutputProp.getNodeType() == Node.ELEMENT_NODE &&
         eOutputProp.getNodeName().equals("outputproperty") )
      {
      NamedNodeMap nnmOutputProp = eOutputProp.getAttributes();
      Node aPropName = nnmOutputProp.getNamedItem("name");
      if ( aPropName == null )
        throw new FlowProcessorException("unexpected missing outputproperty name in step "+name+", bad jmsadapter.xsd?");
      String propname = aPropName.getNodeValue();
      Node aPropValue = nnmOutputProp.getNamedItem("value");
      if ( aPropValue == null )
        throw new FlowProcessorException("unexpected missing outputproperty value in step "+name+", bad jmsadapter.xsd?");
      String propvalue = aPropValue.getNodeValue();
      outputproperties.put(propname, propvalue);
      }
    }
  }
//...e
//...sprocess:2:
//...sstrToDoc:2:
protected Document strToDoc(String s)
  throws FlowProcessorException
  {
  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  // not validating
  // Note: we don't support validation against DTDs during parsing
  // and so we can't support ignoringelementcontentwhitespace either.
  dbf.setNamespaceAware(true); // must do this, else can't validate ok
  dbf.setCoalescing(coalescing);
  dbf.setExpandEntityReferences(expandentityreferences);
  dbf.setIgnoringComments(ignoringcomments);
  try
    {
    DocumentBuilder db = dbf.newDocumentBuilder();
    db.setErrorHandler(
      new ErrorHandler()
        {
        public void warning(SAXParseException e)
          throws SAXException
          {
          }
        public void error(SAXParseException e)
          throws SAXException
          {
          }
        public void fatalError(SAXParseException e)
          throws SAXException
          {
          }
        }
      );
    Document d = db.parse(new InputSource(new StringReader(s)));
      // Note: we avoid using new InputStream(new StringInputStream()),
      // as we want to avoid working at the byte level,
      // after all, we already have a String, which is a sequence of chars.
      // Note that StringInputStream is deprecated for this reason.
    return d;
    }
  catch ( ParserConfigurationException e )
    {
    // Serious problems
    throw new FlowProcessorException("can't parse XML", e);
    }
  catch ( SAXParseException e )
    {
    throw new FlowProcessorException("can't parse XML, line "+e.getLineNumber()+", uri "+e.getSystemId(), e);
    }
  catch ( SAXException e )
    {
    throw new FlowProcessorException("can't parse XML", e);
    }
  catch ( IOException e )
    {
    // What are the odds of this, our InputStream is in memory!
    throw new FlowProcessorException("unexpected IOException, input is in memory", e);
    }
  }
//...e
//...sdocToStr:2:
protected String docToStr(Document d)
  throws FlowProcessorException
  {
  try
    {
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer t = tf.newTransformer(); // the "identity" transformation
    Set<String> keys = outputproperties.keySet();
    for ( String key : keys )
      {
      String value = outputproperties.get(key);
      t.setOutputProperty(key, value);
      }
    CharArrayWriter caw = new CharArrayWriter();
    t.transform(
      new DOMSource((Node) d),
      new StreamResult(caw)
      );
    caw.flush();
    return caw.toString();
    }
  catch ( TransformerException e )
    {
    throw new FlowProcessorException("can't convert Document to String in step "+name, e);
    }
  }
//...e

public void process(FlowContext c)
  throws FlowProcessorException, JMSException
  {
  Object o = c.getVar(var);
  if ( o == null )
    throw new FlowProcessorException(var+" undefined in step "+name);
  if ( o instanceof FlowNullValue )
    throw new FlowProcessorException(var+" is null in step "+name);
  switch ( vartype )
    {
//...sVT_String \45\ XML transform\44\ bytesToString or toString:6:
case VT_String:
  {
  if ( o instanceof String )
    ;
  else if ( o instanceof Document )
    o = docToStr((Document) o);
  else if ( o instanceof byte[] )
    o = FlowHelper.bytesToString((byte[])o, enc);
  else if ( o instanceof Exception )
    o = FlowHelper.diagForm((Exception)o);
  else
    o = o.toString();
  }
  break;
//...e
//...sVT_Document \45\ XML parse:6:
case VT_Document:
  {
  if ( o instanceof String )
    o = strToDoc((String) o);
  else if ( o instanceof byte[] )
    {
    o = FlowHelper.bytesToString((byte[])o, enc);
    o = strToDoc((String) o);
    }
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Document");
  }
  break;
//...e
//...sVT_Boolean \45\ valueOf String\44\ or numeric value \33\\61\ 0:6:
case VT_Boolean:
  {
  if ( o instanceof String )
    o = Boolean.valueOf((String)o);
  else if ( o instanceof Boolean )
    ;
  else if ( o instanceof Byte )
    o = new Boolean( (boolean) ( ((Byte)o).byteValue() != 0 ) );
  else if ( o instanceof Character )
    o = new Boolean( (boolean) ( ((Character)o).charValue() != 0 ) );
  else if ( o instanceof Short )
    o = new Boolean( (boolean) ( ((Short)o).shortValue() != 0 ) );
  else if ( o instanceof Integer )
    o = new Boolean( (boolean) ( ((Integer)o).intValue() != 0 ) );
  else if ( o instanceof Long )
    o = new Boolean( (boolean) ( ((Long)o).longValue() != 0 ) );
  else if ( o instanceof Float )
    o = new Boolean( (boolean) ( ((Float)o).floatValue() != 0.0 ) );
  else if ( o instanceof Double )
    o = new Boolean( (boolean) ( ((Double)o).doubleValue() != 0.0 ) );
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Boolean");
  }
  break;
//...e
//...sVT_Byte \45\ valueOf String\44\ or numeric value:6:
case VT_Byte:
  {
  if ( o instanceof String )
    o = Byte.valueOf((String)o);
  else if ( o instanceof Boolean )
    o = new Byte( (byte) ( ((Boolean)o).booleanValue() ? 1 : 0 ) );
  else if ( o instanceof Byte )
    ;
  else if ( o instanceof Character )
    o = new Byte( (byte) ( ((Character)o).charValue() ) );
  else if ( o instanceof Short )
    o = new Byte( (byte) ( ((Short)o).shortValue() ) );
  else if ( o instanceof Integer )
    o = new Byte( (byte) ( ((Integer)o).intValue() ) );
  else if ( o instanceof Long )
    o = new Byte( (byte) ( ((Long)o).longValue() ) );
  else if ( o instanceof Float )
    o = new Byte( (byte) ( ((Float)o).floatValue() ) );
  else if ( o instanceof Double )
    o = new Byte( (byte) ( ((Double)o).doubleValue() ) );
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Byte");
  }
  break;
//...e
//...sVT_Character \45\ charAt\40\0\41\\44\ or numeric value:6:
case VT_Character:
  {
  if ( o instanceof String )
    o = new Character( ((String)o).charAt(0) );
  else if ( o instanceof Boolean )
    o = new Character( (char) ( ((Boolean)o).booleanValue() ? 1 : 0 ) );
  else if ( o instanceof Byte )
    o = new Character( (char) ( ((Byte)o).byteValue() ) );
  else if ( o instanceof Character )
    ;
  else if ( o instanceof Short )
    o = new Character( (char) ( ((Short)o).shortValue() ) );
  else if ( o instanceof Integer )
    o = new Character( (char) ( ((Integer)o).intValue() ) );
  else if ( o instanceof Long )
    o = new Character( (char) ( ((Long)o).longValue() ) );
  else if ( o instanceof Float )
    o = new Character( (char) ( ((Float)o).floatValue() ) );
  else if ( o instanceof Double )
    o = new Character( (char) ( ((Double)o).doubleValue() ) );
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Character");
  }
  break;
//...e
//...sVT_Short \45\ valueOf String\44\ or numeric value:6:
case VT_Short:
  {
  if ( o instanceof String )
    o = Short.valueOf((String)o);
  else if ( o instanceof Boolean )
    o = new Short( (short) ( ((Boolean)o).booleanValue() ? 1 : 0 ) );
  else if ( o instanceof Byte )
    o = new Short( (short) ( ((Byte)o).byteValue() ) );
  else if ( o instanceof Character )
    o = new Short( (short) ( ((Character)o).charValue() ) );
  else if ( o instanceof Short )
    ;
  else if ( o instanceof Integer )
    o = new Short( (short) ( ((Integer)o).intValue() ) );
  else if ( o instanceof Long )
    o = new Short( (short) ( ((Long)o).longValue() ) );
  else if ( o instanceof Float )
    o = new Short( (short) ( ((Float)o).floatValue() ) );
  else if ( o instanceof Double )
    o = new Short( (short) ( ((Double)o).doubleValue() ) );
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Short");
  }
  break;
//...e
//...sVT_Integer \45\ valueOf String\44\ or numeric value:6:
case VT_Integer:
  {
  if ( o instanceof String )
    o = Integer.valueOf((String)o);
  else if ( o instanceof Boolean )
    o = new Integer( (int) ( ((Boolean)o).booleanValue() ? 1 : 0 ) );
  else if ( o instanceof Byte )
    o = new Integer( (int) ( ((Byte)o).byteValue() ) );
  else if ( o instanceof Character )
    o = new Integer( (int) ( ((Character)o).charValue() ) );
  else if ( o instanceof Short )
    o = new Integer( (int) ( ((Short)o).shortValue() ) );
  else if ( o instanceof Integer )
    ;
  else if ( o instanceof Long )
    o = new Integer( (int) ( ((Long)o).longValue() ) );
  else if ( o instanceof Float )
    o = new Integer( (int) ( ((Float)o).floatValue() ) );
  else if ( o instanceof Double )
    o = new Integer( (int) ( ((Double)o).doubleValue() ) );
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Integer");
  }
  break;
//...e
//...sVT_Long \45\ valueOf String\44\ or numeric value:6:
case VT_Long:
  {
  if ( o instanceof String )
    o = Long.valueOf((String)o);
  else if ( o instanceof Boolean )
    o = new Long( (long) ( ((Boolean)o).booleanValue() ? 1 : 0 ) );
  else if ( o instanceof Byte )
    o = new Long( (long) ( ((Byte)o).byteValue() ) );
  else if ( o instanceof Character )
    o = new Long( (long) ( ((Character)o).charValue() ) );
  else if ( o instanceof Short )
    o = new Long( (long) ( ((Short)o).shortValue() ) );
  else if ( o instanceof Integer )
    o = new Long( (long) ( ((Integer)o).intValue() ) );
  else if ( o instanceof Long )
    ;
  else if ( o instanceof Float )
    o = new Long( (long) ( ((Float)o).floatValue() ) );
  else if ( o instanceof Double )
    o = new Long( (long) ( ((Double)o).doubleValue() ) );
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Long");
  }
  break;
//...e
//...sVT_Float \45\ valueOf String\44\ or numeric value:6:
case VT_Float:
  {
  if ( o instanceof String )
    o = Float.valueOf((String)o);
  else if ( o instanceof Boolean )
    o = new Float( (float) ( ((Boolean)o).booleanValue() ? 1 : 0 ) );
  else if ( o instanceof Byte )
    o = new Float( (float) ( ((Byte)o).byteValue() ) );
  else if ( o instanceof Character )
    o = new Float( (float) ( ((Character)o).charValue() ) );
  else if ( o instanceof Short )
    o = new Float( (float) ( ((Short)o).shortValue() ) );
  else if ( o instanceof Integer )
    o = new Float( (float) ( ((Integer)o).intValue() ) );
  else if ( o instanceof Long )
    o = new Float( (float) ( ((Long)o).floatValue() ) );
  else if ( o instanceof Float )
    ;
  else if ( o instanceof Double )
    o = new Float( (float) ( ((Double)o).doubleValue() ) );
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Float");
  }
  break;
//...e
//...sVT_Double \45\ valueOf String\44\ or numeric value:6:
case VT_Double:
  {
  if ( o instanceof String )
    o = Double.valueOf((String)o);
  else if ( o instanceof Boolean )
    o = new Double( (double) ( ((Boolean)o).booleanValue() ? 1 : 0 ) );
  else if ( o instanceof Byte )
    o = new Double( (double) ( ((Byte)o).byteValue() ) );
  else if ( o instanceof Character )
    o = new Double( (double) ( ((Character)o).charValue() ) );
  else if ( o instanceof Short )
    o = new Double( (double) ( ((Short)o).shortValue() ) );
  else if ( o instanceof Integer )
    o = new Double( (double) ( ((Integer)o).intValue() ) );
  else if ( o instanceof Long )
    o = new Double( (double) ( ((Long)o).floatValue() ) );
  else if ( o instanceof Float )
    o = new Double( (double) ( ((Float)o).floatValue() ) );
  else if ( o instanceof Double )
    ;
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Double");
  }
  break;
//...e
//...sVT_Bytes \45\ stringToBytes or serialize:6:
case VT_Bytes:
  {
  if ( o instanceof String )
    o = FlowHelper.stringToBytes((String)o, enc);
  else if ( o instanceof Document )
    o = docToStr((Document) o);
  else if ( o instanceof Serializable )
    {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try
      {
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(o);
      oos.flush();
      }
    catch ( IOException e )
      {
      throw new FlowProcessorException("cannot serialize "+o.getClass().getName(), e);
      }
    o = baos.toByteArray();
    }
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to byte[]");
  }
  break;
//...e
//...sVT_Object \45\ deserialize:6:
case VT_Object:
  {
  if ( o instanceof byte[] )
    {
    byte[]b = (byte[]) o;
    ByteArrayInputStream bais = new ByteArrayInputStream(b);
    try
      {
      ObjectInputStream ois = new ObjectInputStream(bais);
      o = (Serializable) ois.readObject();
      }
    catch ( IOException e )
      {
      throw new FlowProcessorException("IOException deserializing object", e);
      }
    catch ( ClassNotFoundException e )
      {
      throw new FlowProcessorException("ClassNotFoundException deserializing object", e);
      }
    }
  else
    throw new FlowProcessorException("don't know how to convert "+o.getClass().getName()+" to Object");
  }
  break;
//...e
    }
  c.setVar(destvar, o);
  }
//...e
  }
