//
// FlowStepVarMap.java - Map
//

package nyangau.jmsadapter;

//...simports:0:
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.Vector;
import java.util.regex.*;
import java.io.IOException;
import javax.jms.JMSException;
import javax.xml.namespace.NamespaceContext;
import javax.xml.transform.dom.DOMSource;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
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 FlowStepVarMap extends FlowStep
  {
  protected String var;
  protected String destvar;
  protected XPathExpression xpe;
  protected Pattern pattern;
  protected int cg;
  protected String map;
  protected DataMap datamap;
//...sFlowStepVarMap:2:
public FlowStepVarMap(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 aXpath = nnmStep.getNamedItem("xpath");
  if ( aXpath != null )
    {
    String xpath = aXpath.getNodeValue();
    try
      {
      XPath xp = XPathFactory.newInstance().newXPath();
      Node aNsctx = nnmStep.getNamedItem("nsctx");
      if ( aNsctx != null )
        xp.setNamespaceContext( FlowHelper.getNamespaceContext(aNsctx.getNodeValue()) );
      xpe = xp.compile(xpath);
      }
    catch ( XPathExpressionException e )
      {
      throw new FlowProcessorException("bad XPath "+xpath+" in step "+name, e);
      }
    }
  else
    xpe = null;
  Node aRegexp = nnmStep.getNamedItem("regexp");
  if ( aRegexp != null )
    {
    String regexp = aRegexp.getNodeValue();
    try
      {
      pattern = Pattern.compile(regexp);
      }
    catch ( PatternSyntaxException e )
      {
      throw new FlowProcessorException("bad Java Regular Expression "+regexp+" in step "+name+", bad jmsadapter.xsd?"); 
      }
    }
  else
    pattern = null;
  Node aCg = nnmStep.getNamedItem("group");
  cg = ( aCg != null ) ? Integer.parseInt(aCg.getNodeValue()) : 0;

  Node aMap = nnmStep.getNamedItem("map");
  if ( aMap == null )
    throw new FlowProcessorException("unexpected missing map in step "+name+", bad jmsadapter.xsd?");
  map = aMap.getNodeValue();

  // Note that you might think we can lookup the DataMap right now,
  // but note that not all of them may have been read from the configuration
  // XML at the time we are called here, so we do this lookup lazily.
  datamap = null;
  }
//...e
//...sprocess:2:
//...smap:2:
protected void map(Set<String> unmapped, Map<String,String> mapped)
  throws FlowProcessorException
  {
  // Do the mapping
  datamap.map(unmapped, mapped);

  // Did we fail?
  // If so, try to give some useful information
  if ( ! unmapped.isEmpty() )
    {
    StringBuffer sb = new StringBuffer();
    int i = 0;
    for ( String s : unmapped )
      {
      if ( i++ == 10 )
        {
        sb.append(" ...");
        break;
        }
      sb.append(" ");
      sb.append(s);
      }
    throw new FlowProcessorException("unable to map all values in step "+name+":"+sb.toString());
    }
  }
//...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);

  // Lazily find DataMap
  if ( datamap == null )
    {
    datamap = c.getFlowProcessor().getDataMap(map);
    if ( datamap == null )
      // This can happen as jmsadapter.xsd only ensures we reference a
      // map at any level, and yet we can only find top level ones.
      // I think I should be able to get this to be more restrictive,
      // but alas my powers of xsd:key select= aren't up to the job...
      throw new FlowProcessorException("must not reference nested data map in step "+name);
    }

  // Values to be mapped
  HashSet<String> unmapped = new HashSet<String>();
  HashMap<String,String> mapped = new HashMap<String,String>();

  // Identify parts of document to be mapped
  if ( o instanceof Document && xpe != null )
    {
    Document d = (Document) o;
    NodeList nl;
    try
      {
      nl = (NodeList) xpe.evaluate(d, XPathConstants.NODESET);
      }
    catch ( XPathExpressionException e )
      {
      throw new FlowProcessorException("problem evaluating XPath Expression in step "+name, e);
      }
    catch ( NullPointerException e )
      {
      throw new FlowProcessorException("unexpected NullPointerException", e);
      }
    for ( int i = 0; i < nl.getLength(); i++ )
      {
      Node n = nl.item(i);
      unmapped.add(n.getNodeValue());
      }
    map(unmapped, mapped);
    for ( int i = 0; i < nl.getLength(); i++ )
      {
      Node n = nl.item(i);
      n.setNodeValue(mapped.get(n.getNodeValue()));
      }
    c.setVar(destvar, d);
    }
  else if ( o instanceof String && pattern != null )
    {
    String s = (String) o;
    Matcher matcher = pattern.matcher(new StringBuffer(s));
    class Range
      {
      public int start;
      public int end;
      public Range(int s, int e) { start = s; end = e; }
      }
    Vector<Range> ranges = new Vector<Range>();
    while ( matcher.find() )
      {
      int start = matcher.start(cg);
      int end   = matcher.end  (cg);
      ranges.add(new Range(start, end));
      unmapped.add(s.substring(start, end));
      }
    map(unmapped, mapped);
    StringBuffer sb = new StringBuffer();
    int j = 0;
    for ( int i = 0; i < ranges.size(); i++ )
      {
      Range range = ranges.elementAt(i);
      sb.append(s.substring(j, range.start));
      sb.append(mapped.get(s.substring(range.start, range.end)));
      j = range.end;
      }
    sb.append(s.substring(j));
    c.setVar(destvar, sb.toString());
    }
  else
    throw new FlowProcessorException(var+" is not mappable in step "+name);
  }
//...e
  }
