// 20001020 mkr; created
// 20051103 mkr; add GPL, comment properly for javadoc

/*
maintained by martl@sulis.de

purpose: run regular expression substitutions on column data.
         - edit column inline or
	   - create new column from altered content

This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation;
see http://www.gnu.org/licenses/gpl.html

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
*/

package ext.tools.webjects;

import ext.tools.*;
import com.infoengine.log.LogWriter;
import com.infoengine.object.*;
import com.infoengine.procunit.webject.*;
import com.infoengine.object.factory.*;
import com.infoengine.util.IEResource;
import com.infoengine.xml.ProcessXml;
import java.text.Format;
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.*;


/**
  * implements a webject to run regular expression substitution on group columns.
  * <br><b>NOTE: this alters the input group! to preserve state copy-group before!</b>
  * <br>pattern matching runs in DOTALL mode. (see http://java.sun.com/docs/books/tutorial/extra/regex/pattern.html)
  * 
  * <br>all params can be repeated, each set is processed separately e.g.,: COL_IN[0], COL_OUT[0], RE[0] etc.
  * 
  * <p>Parameters:<ul>
  *     <li> COL_IN
  * 	    <br> MANDATORY.  name of input column
  *     <li> COL_OUT
  * 	    <br> new name for altered contents. if missing COL_IN is altered.
  *     <li> EMPTY_IF_NO_MATCH
  * 	    <br> defaults to false, only relevant when creating new colums (COL_IN <> COL_OUT)<br>
  * 		tells whether an unaltered value should result in an empty att (else COL_OUT value = COL_IN value)<br>
  * 		(NOTE: - in case e.g., you subst "2" by "2" COL_OUT will be emptied if EMPTY_IF_NO_MATCH=true)
  *     <li> RE
  * 	    <br> default = .* (whole contents)<br>
  * 		the re to match column contents against. use () to remember into $1 etc.
  *     <li> VERBOSE
  * 	    <br> default false<br>
  * 		will write some info about processing to wtadapter log
  *     <li> IGNORECASE
  * 	    <br> default=false<br>
  * 		do matching case insensitive.
  *     <li> GLOBAL
  * 	    <br> default=false<br>
  * 		substitute all occurrences (replaceFirst vs. replaceAll)
  *     <li> SUBST
  * 	    <br> default=empty<br>
  * 		String to substitute for match. may contain !Col-name! to bring in values from other attributes within the same row
  * </ul>
  * <pre>
  * Example: create a column "swap" from a column "one" swapping the 2nd and 3rc char: (abcde -> acbde):
  * 
  * &lt;ie:webject name="re-subst" type="EXT" use="ext.tools.webjects.Subst"&gt;
  *    &lt;ie:param name="GROUP_IN" data="g"/&gt;
  *    &lt;ie:param name="COL_IN" data="one"/&gt;
  *    &lt;ie:param name="COL_OUT" data="swap"/&gt;
  *    &lt;ie:param name="RE" data="(?&lt;=.)(.)(.)"/&gt;
  *    &lt;ie:param name="SUBST" data="$2$1"/&gt;
  * &lt;/ie:webject&gt;
  *
  * Example: create a column "new" from a column "old1" and "old2" so if old1=X and old2=Y new will be "X Y"
  * 
  * &lt;ie:webject name="re-subst" type="EXT" use="ext.tools.webjects.Subst"&gt;
  *    &lt;ie:param name="GROUP_IN" data="g"/&gt;
  *    &lt;ie:param name="COL_IN" data="old1"/&gt;
  *    &lt;ie:param name="COL_OUT" data="new"/>
  *    &lt;ie:param name="SUBST" data="$1 !old2!"/>
  * &lt;/ie:webject>
  * </pre>
  * @author mkraegeloh
  * @version 1.0
  */

public class Subst{
   public static final String CVSVersion = "$Header: /usr/local/cvsroot/SULIS/src/ext/tools/webjects/Subst.java,v 1.2 2007/01/22 10:41:43 mkr Exp $";
   static final String VERSION="1.1";
   static final int COL_OUT=0;
   static final int PATTERN=1;
   static final int REPLACEMENT=2;
   static final int GLOBAL=3;
   static Pattern paramRef;
   boolean verbose=false;
   static{ try{
	paramRef = Pattern.compile("!(.*?)!");
	}catch(Exception kommtNedWeilDesPatternOkIs){throw new RuntimeException(kommtNedWeilDesPatternOkIs);}
   }

   // this gets invoked with Re-Subst ...
   /**
     * entry point for webject invocation.
     *
     * @param task	used to get to group_in
     * @exception    	
     * @return Task	passes back group_in
   */
   public Task reSubst(Task task) throws GroupWebjectException{
	// setup
	LogWriter.log.println("reSubst: starting Version="+VERSION);
	Webject w = task.getWebject();
	String groupOut=w.paramValue("GROUP_OUT","SUBST");
	Task t = new Task();
	Group g = new Group();
	g.setName(groupOut);
	g.setType(1);
	g.setSuccess("ERROR");
	g.setStatus(1); // remains till we have good return
	t.addOutput(g);

	// deal with parameters
	String gInName=w.paramValue("GROUP_IN", null);
	LogWriter.log.println("reSubst: got GROUP_IN " + gInName);
	if(gInName == null){
	   g.setMessage("need input group name");
	   LogWriter.log.println("reSubst: returning error \"need input group name\"");
	   return t;
	}
	/* remember possibly repeated params in a hash like:
	{ colname => ( 
	 ( coloutname pattern replacement globalflag )
	 ( coloutname pattern replacement globalflag )
	 )
	 col2 => ( ( ....) ( ... ) )
	}
	*/
	Enumeration colsIn=w.paramValues("COL_IN");
	Enumeration colsOut=w.paramValues("COL_OUT");
	Enumeration re=w.paramValues("RE");
	Enumeration subst=w.paramValues("SUBST");
	Enumeration global=w.paramValues("GLOBAL");
	Enumeration ic=w.paramValues("IGNORECASE");
	String en=w.paramValue("EMPTY_IF_NO_MATCH");
	boolean makeEmpty=en != null && en.equalsIgnoreCase("true");
	String verboseS=w.paramValue("VERBOSE");
	verbose=verboseS != null && verboseS.equalsIgnoreCase("true");
	Hashtable cols=new Hashtable();
	Vector colss=new Vector();
	String colName="",reText="";
	LogWriter.log.println("reSubst: prepare makeEmpty="+makeEmpty);
	Vector v,v1;
	while(colsIn.hasMoreElements()){
	   v=new Vector(4);
	   colName=(String)colsIn.nextElement();
	   if((v1=(Vector)cols.get(colName))==null){
		cols.put(colName,v1=new Vector());
		colss.add(colName);
	   }
	   v1.add(v);
	   v.add(COL_OUT,nextVal(colsOut,colName));
	   int iflag=nextVal(ic,"false").equalsIgnoreCase("true") ? Pattern.CASE_INSENSITIVE : 0;
	   try{
		v.add(PATTERN,Pattern.compile(reText=nextVal(re,".*"),iflag|Pattern.DOTALL));
		LogWriter.log.println("reSubst: re \"" + reText + "\" compiled ok");
	   }catch(Exception ex){
		g.setMessage("bad re: "+reText);
		LogWriter.log.println("reSubst: returning error \"bad re: " + reText + "\"");
		return t;
	   }
	   v.add(REPLACEMENT,nextVal(subst,""));
	   v.add(GLOBAL,nextVal(global,"false"));
	}

	// setup done, do the work.
	LogWriter.log.println("reSubst: processing " + gInName);
	Group gIn=task.getGroupIn(gInName);
	if(gIn == null){
	   g.setMessage("input group null!");
	   LogWriter.log.println("reSubst: returning error \"need input group name\"");
	   return t;
	}
	String vIn=null, vOut=null, newCol=null;
	Att att=null;
	Element e=null;
	Vector colv,col;
	LogWriter.log.println("reSubst: start loop");
	for(Enumeration e1 = gIn.getElements(); e1.hasMoreElements(); ){
	   if(verbose)LogWriter.log.println("reSubst: element");
	   e=(Element)e1.nextElement(); // no .deepClone();
	   // loop subst cols
	   //for(Enumeration e2=cols.keys(); e2.hasMoreElements(); )
	   for(int coli=0; coli<colss.size(); coli++ ){
		colName=(String)colss.get(coli);
		colv=(Vector)cols.get(colName);
		for(int ci=0;ci<colv.size();ci++){
		   if(verbose)LogWriter.log.println("reSubst: colName="+colName);
		   col=(Vector)colv.get(ci);
		   att=e.getAtt(colName);
		   if(att == null){
			if(verbose)LogWriter.log.println("reSubst: col not found, skipped");
			continue;
		   }
		   Object o=att.getValue();
		   if(o == null){
			if(verbose)LogWriter.log.println("reSubst: attValue not found, skipped");
			continue;
		   }
		   vIn=o.toString();
		   if(col.get(GLOBAL).toString().equalsIgnoreCase("true"))
			vOut=((Pattern)col.get(PATTERN)).matcher(vIn).replaceAll((String)col.get(2));
		   else
			vOut=((Pattern)col.get(PATTERN)).matcher(vIn).replaceFirst((String)col.get(2));
		   vOut=substCols(vOut,e); // allow to insert other columns data with xxx!COLA!yyy
		   if(! (newCol=(String)col.get(COL_OUT)).equals(colName)){
			e.removeAtt(newCol);
			if(makeEmpty && vOut.equals(vIn) )
			   e.addAtt(new Att(newCol,""));
			else
			   e.addAtt(new Att(newCol,vOut));
		   }
		   else{
			att.setValue(vOut);
		   }
		   if(verbose)LogWriter.log.println("reSubst: "+vIn+"->"+vOut);
		}
	   }
	   g.addElement(e);
	 }
	 g.setStatus(0);
	 g.setSuccess("ERROR");
	 return t;
   }

   // convenience ...
   /**
     * return the next value of the enumeration passed in or supplied default value if enumeration finished.
     *
     * @param e	input enum
     * @param d	default value
     * @exception    	
   */
   String nextVal(Enumeration e, String d){
    if(e.hasMoreElements())
	 return (String)e.nextElement();
    return d;
   }
   /**
     * replace patterns in the form !name! with the value from an att called "name"
     *
     * @param val	attribute value to work on
     * @param e	element to take replacement values from
     * @exception    	
   */
   String substCols(String val, Element e){
	Matcher m;
	String pval,name;

	if(verbose)
	   LogWriter.log.println("substCols: in="+val);
	while(true){
	   m = paramRef.matcher(val);
	   if (m.find()) {
		name=m.group(1);
		pval=ToolUtils.nn(e.getValue(name));
		val=m.replaceFirst(pval);
	   } else {
		break;
	   }
	}
	if(verbose)
	   LogWriter.log.println("substCols: out="+val);
	return val;
   }
}
