/*-------------------------------------------------------------------------
 * rbe.EB.java
 * Timothy Heil
 * 10/5/99
 *
 * ECE902 Fall '99
 *
 * TPC-W emulated browser.
 *------------------------------------------------------------------------
 *
 * This is part of the the Java TPC-W distribution,
 * written by Harold Cain, Tim Heil, Milo Martin, Eric Weglarz, and Todd
 * Bezenek.  University of Wisconsin - Madison, Computer Sciences
 * Dept. and Dept. of Electrical and Computer Engineering, as a part of
 * Prof. Mikko Lipasti's Fall 1999 ECE 902 course.
 *
 * Copyright (C) 1999, 2000 by Harold Cain, Timothy Heil, Milo Martin, 
 *                             Eric Weglarz, Todd Bezenek.
 *
 * This source code is distributed "as is" in the hope that it will be
 * useful.  It comes with no warranty, and no author or distributor
 * accepts any responsibility for the consequences of its use.
 *
 * Everyone is granted permission to copy, modify and redistribute
 * this code under the following conditions:
 *
 * This code is distributed for non-commercial use only.
 * Please contact the maintainer for restrictions applying to 
 * commercial use of these tools.
 *
 * Permission is granted to anyone to make or distribute copies
 * of this code, either as received or modified, in any
 * medium, provided that all copyright notices, permission and
 * nonwarranty notices are preserved, and that the distributor
 * grants the recipient permission for further redistribution as
 * permitted by this document.
 *
 * Permission is granted to distribute this code in compiled
 * or executable form under the same conditions that apply for
 * source code, provided that either:
 *
 * A. it is accompanied by the corresponding machine-readable
 *    source code,
 * B. it is accompanied by a written offer, with no time limit,
 *    to give anyone a machine-readable copy of the corresponding
 *    source code in return for reimbursement of the cost of
 *    distribution.  This written offer must permit verbatim
 *    duplication by anyone, or
 * C. it is distributed by someone who received only the
 *    executable form, and is accompanied by a copy of the
 *    written offer of source code that they received concurrently.
 *
 * In other words, you are welcome to use, share and improve this codes.
 * You are forbidden to forbid anyone else to use, share and improve what
 * you give them.
 *
 ************************************************************************/

package rbe;

import java.net.*;
import java.io.*;

import java.util.Random;
import java.util.Date;
import java.util.Vector;

import rbe.util.Debug;
import rbe.util.StringPattern;
import rbe.util.StrStrPattern;
import rbe.util.CharStrPattern;
import rbe.util.CharSetStrPattern;

// EB stands for 'Emulated Browser'
public class EB extends Thread {
  // Terminate all EBs.
  public static volatile boolean terminate=false;  

  int          [/*from*/][/*to*/] transProb;      // Transition probabilities.
  EBTransition [/*from*/][/*to*/] trans;          // EB transitions.
  EBTransition curTrans;
  int curState;                                   // Current state.
  String nextReq;            // Next HTTP request.
  String html;               // Received HTML
  public String prevHTML;    // HTML from a previous page.
  int maxTrans;
  String name;
  byte [] buffer = new byte[4096];
  long    cid;                // CUSTOMER_ID.  See TPC-W Spec.
  String sessionID;          // SESSION_ID.  See TPC-W Spec. 
  long    shopID;             // Shopping ID.  
  String fname = null;       // Customer first name.
  String lname = null;       // Customer last name.
  public RBE rbe;
  long usmd;
  boolean toHome;
  boolean stagger = true;

  // Wait for key-stroke between transisions.
  //   Does not do think-times.
  public boolean waitKey = true;
  
  public Random rand = new Random();

  // Think time-scaling.
  public double tt_scale = 1.0;
  
  // Set this higher to see more messages. 
  public static int DEBUG =0;

  public static final int NO_TRANS = 0;
  public static final int MIN_PROB = 1;
  public static final int MAX_PROB = 9999;
  public static final int ID_UNKNOWN = -1;

  private final StrStrPattern imgPat    = new StrStrPattern("<IMG");
  private final StrStrPattern inputPat  = 
      new StrStrPattern("<INPUT TYPE=\"IMAGE\"");
  private final StrStrPattern srcPat    = new StrStrPattern("SRC=\"");
  private final CharStrPattern quotePat = new CharStrPattern('\"');

  public EB(RBE rbe,
	    int [][] prob, // Transition probabilities.  
	                   //   See TPC-W Spec. Section 5.2.2.
	    EBTransition [][] trans, // Actual transitions.
	    int max,     // Number of transitions. -1 implies continuous
	    String name  // String name.
	    )
  {
    int i,j;
    int s;
    int prev;

    // Make sure prob and trans are well-formed.
    s = prob.length;
    Debug.assertTPCW(s>0, "No states in prob.");
    Debug.assertTPCW(trans.length == s, "Number of states in prob (" + s + 
		 ") does not equal number of states in trans (" + 
		 trans.length + ")");

    for (j=0;j<s;j++) {
      Debug.assertTPCW(trans[j].length==s, "Transition matrix is not square.");
      Debug.assertTPCW(prob[j].length==s, "Transition matrix is not square.");

      prev = 0;
      for (i=0;i<s;i++) {
	if (prob[j][i]==NO_TRANS) {
	  Debug.assertTPCW(trans[j][i] == null, "Transition method specified " + 
		       "for impossible transition." + i + ", " + j + " " + trans[j][i]);
	}
	else {
	  Debug.assertTPCW(prob[j][i] <= MAX_PROB, 
		       "Transition probability for prob[" + 
		       j + "][" + i + "] (" + prob[j][i] + 
		       ") is larger than " + MAX_PROB);
	  Debug.assertTPCW(prob[j][i] >= MIN_PROB, 
		       "Transition probability for prob[" + 
		       j + "][" + i + "] (" + prob[j][i] + 
		       ") is less than " + MIN_PROB);
	  Debug.assertTPCW(trans[j][i]!=null, 
		       "No transition method for possible transition [" + 
		       j +"][" +i + "]");

	  Debug.assertTPCW(prob[j][i] > prev, 
		       "Transition [" + j + "][" + i + "] has probability (" + 
		       prob[j][i] + " not greater than previous " + 
		       "probability (" + prev + ")");
	  prev = prob[j][i];
	}
      }
      Debug.assertTPCW(prev==MAX_PROB, "Final probability for state [" + j + 
		   "] ( " + prev + ") is not " + MAX_PROB);
    }

    this.rbe       = rbe;
    this.transProb = prob;
    this.trans     = trans;
    this.name      = name;
    
    maxTrans = max;    
    super.setDaemon(true);
    initialize();
  }

  public final int states() 
  {
    return(transProb.length);
  }

  public void initialize() {
    curState = 0;
    nextReq = null;
    html = null;
    prevHTML = null;
    cid    = ID_UNKNOWN;
    sessionID = null;
	shopID = ID_UNKNOWN;
    usmd = System.currentTimeMillis() + usmd(); // Luis Rodero-Merino: user session will last until usmd
    fname = null;
    lname = null;
  }

  public void run() {
	// Luis Rodero-Merino: see page 103 of TPC-W 1.8 spec
    long wirt_t1;  // WIRT.T1 in TPC_W.Spec.
    long wirt_t2;  // Same as TT.T1 in TPC-W Spec.
    long wirt;     // Web Interaction Response Time (WIRT).
    long expectedTransactionTime = 0; // Added by Luis Rodero-Merino
    long thinkingtime=0L;    // Think Time

    wirt_t1 = System.currentTimeMillis();

    if (DEBUG>0)
      System.out.println("usmd " + usmd);

    try {
    	while ((maxTrans == -1) || (maxTrans>0)) {
    		
    		expectedTransactionTime = expectedTransactionTime();
    		
    		if(curState == 3)
    			System.out.println("[" + Thread.currentThread().getName() + "] >> In 'BestSeller' state, wirt_t1=" + wirt_t1);

    		if (terminate) { 
    			System.out.println("[" + Thread.currentThread().getName() + "] EB " + name + "commiting suicide!");
    			return;
    		}
	
    		if (nextReq!=null) {

    			// Check if user session is finished.
    			if ((wirt_t1 >= usmd) && (toHome)) {
    				// User session is complete. Start new user session.
    				System.out.println("[" + Thread.currentThread().getName() + "] " + name + " completed user session.");
    				initialize();
    				continue;
    			}

    			if (nextReq.equals("")) {
    				rbe.stats.error("Restarting new user session due to error.", " <???>");
    				// User session is complete. Start new user session.
    				System.out.println("[" + Thread.currentThread().getName() + "] " + name + " completed user session.");
    				initialize();
    				continue;
    			}

    			// 2) Send HTTP request.
    			// This is purely for testing purposes.
    			if (nextReq.startsWith("file:")) {
    				int q = nextReq.indexOf('?');
    				if (q==-1) {
    					q = nextReq.length();
    				}
    				nextReq = nextReq.substring(0,q);
    				// System.out.println("nextReq trimmed : " + nextReq);
    			}
    			URL httpReq = new URL(nextReq);
	 
    			// 3) Receive HTML response page.
    			if (DEBUG > 0)
    				System.out.println("[" + Thread.currentThread().getName() + "] " + name + "Making request.");
        		if(curState == 3)
        			System.out.println("[" + Thread.currentThread().getName() + "] >> In 'BestSeller' state, sending request");
        		rbe.stats.startedInteraction();
    			getHTML(httpReq);
        		if(curState == 3)
        			System.out.println("[" + Thread.currentThread().getName() + "] >> In 'BestSeller' state, request response received");
    			if (DEBUG > 0)
    				System.out.println("[" + Thread.currentThread().getName() + "] " + name + "Received HTML.");
    			
    			// 4) Measure absolute response time, TT.T1 = WIRT.T2.  This is a
    			// time stamp just following the reception of the last byte of the
    			// HTML response page, which was provided by the SUT.
    			wirt_t2 = System.currentTimeMillis();
        		
    			// 5) Compute and store Web Interaction Response Time (WIRT) 
    			rbe.stats.finishedInteraction(curState, wirt_t1, wirt_t2, thinkingtime);

    			if (DEBUG > 2)
    				System.out.println("[" + Thread.currentThread().getName() + "] Post process: " + curTrans);
    			
    			curTrans.postProcess(this, html);
    		} else {
    			html = null;
    			wirt_t2 = wirt_t1;
    		}

    		if(curState == 3)
    			System.out.println("[" + Thread.currentThread().getName() + "] >> In 'BestSeller' state, wirt_t2=" + wirt_t2 + ", WIRT: " + (wirt_t2-wirt_t1));

    		// 6) Pick the next navigation option.
    		// 7) Compose HTTP request.
    		nextState();

    		if (nextReq != null) {
	
    			if (terminate) {
    				System.out.println("[" + Thread.currentThread().getName() + "] EB " + name + "commiting suicide!");
    				return;
    			}

    			// 8) Pick think time (TT), and compute absolute request time
    			thinkingtime = expectedTransactionTime - (wirt_t2 - wirt_t1);
    			if(thinkingtime < 0)
    				thinkingtime = 0;
    			//System.out.println("ExpecTransTime: " + expectedTransactionTime + ", RealTransTime: " + (wirt_t2 - wirt_t1) + ", ThinkingTime: " + thinkingtime);
    			
    			// 9) Wait for absolute request time.
    			try {
    				if(waitKey) // Luis Rodero-Merino: 'waitKey' is only true for interactive runs, in our case the EB thread will always sleep
    					rbe.getKey();    				
    				else
    					sleep(thinkingtime);    				
    			} catch (InterruptedException inte) {
    				System.out.println("[" + Thread.currentThread().getName() + "] EB " + name + " Caught an interrupted exception!");
    				return;
    			}

    			wirt_t1 = System.currentTimeMillis();
	  
    			if (maxTrans > 0)
    				maxTrans--;
    		} else
    			System.out.println("[" + Thread.currentThread().getName() + "] ERROR: nextReq == null!");
    	}
    } catch (MalformedURLException murl) {
      murl.printStackTrace();
      return;
    } catch (IOException ioe) {
      ioe.printStackTrace();
      return;
    }
    
  }

  void getHTML(URL url) 
  {

    // System.out.println("Begin reading HTML. " + name);
    html = "";
    int r;
    BufferedInputStream in=null;
    URLConnection urlConn = null;
    //BufferedInputStream imgIn=null;
    boolean retry;
    Vector imageRd = new Vector(0);

    do {
      retry = false;
      try {
    	  urlConn = url.openConnection();
    	  urlConn.setConnectTimeout(Integer.MAX_VALUE);
    	  urlConn.setReadTimeout(Integer.MAX_VALUE);
    	  in = new BufferedInputStream(urlConn.getInputStream(), 4096);
	//	 System.out.println("DoneOpening input stream");
      }
      catch (IOException ioe) {
		ioe.printStackTrace(System.out);
		//ioe.printStackTrace();
		rbe.stats.error("Unable to open URL." , url.toExternalForm());
		retry=true;
		continue;
      }
      try {
	  
	  //	  while(in.available() == 0){
	  // System.out.println("Nothing available on input stream!");
	  //}
	  while ((r = in.read(buffer, 0, buffer.length))!=-1) {
	      if (r>0) {
		  html = html + new String(buffer, 0, r);
	      }
	      //	      System.out.print(".");
	  }
      }
      catch (IOException ioe) {
	rbe.stats.error("Unable to read HTML from URL." , 
			url.toExternalForm());
	retry=true;
	continue;
      }

      if (retry) {
	  try {
	      if (waitKey) {
		  rbe.getKey();
	      }
	      else {
		  sleep(1000L);
	      }
	  }
	  catch (InterruptedException inte) {
	      System.out.println("In getHTML, caught interrupted exception!");
	      return;
	  }	  
      }
    } while (retry);
   
    try {
        in.close();
    	urlConn.getInputStream().close();
    }
    catch (IOException ioe) {
	      System.out.println("IOException caught when closing URL connection. Ignoring, but here is the message: '" + ioe.getMessage() + "'");
	      //rbe.stats.error("Unable to close URL." , url.toExternalForm());
    }

    /******
    html = 
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "JIGSAW-SESSION-ID=kjfljelejl_343434223_32" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "I_ID=563 " +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "I_ID=534 " +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "I_ID=564 " +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "I_ID=568 " +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>" +
      "<HTML> AA HTM IM  AA HTM IM  AA HTM IM  AA HTM IM  </HTML>";
      ****/

    if (DEBUG>0) {
	//System.out.println("Received HTML...");
    }

    if (DEBUG>10) {

      System.out.println(html);
    }

    // Scan for image requests, and request those.
    int cur = 0;

    // Suppress image requests.
    if (!RBE.getImage) return;

    findImg(html, url, imgPat, srcPat, quotePat, imageRd);
    findImg(html, url, inputPat, srcPat, quotePat, imageRd);

    if (DEBUG>2) {
	System.out.println("Found " + imageRd.size() + " images.");
    }
    while (imageRd.size()>0) {
      int max = imageRd.size();
      int min = Math.max(max - rbe.maxImageRd,0);
      int i;
      try {
	  for (i=min;i<max;i++) {
	      ImageReader rd = (ImageReader) imageRd.elementAt(i);
	      //	System.out.println("EB #" + sessionID+ "reading image: " + rd.imgURLStr);
	      if (!rd.readImage()) {
		  if (DEBUG>2) {
		      System.out.println("Read " + rd.tot + " bytes from " + 
					 rd.imgURLStr);
		  }
		  imageRd.removeElementAt(i);
		  i--;
		  max--;
	      }
	  }
      }
      catch (InterruptedException inte) {
	  System.out.println("In getHTML, caught interrupted exception!");
	  return;
      }
    }
  }
    
  private void findImg(String html, URL url, StringPattern imgPat, 
		    StringPattern srcPat, StringPattern quotePat,
		    Vector imageRd)
  {
    int cur = 0;
    
    while ((cur = imgPat.find(html, cur)) > -1) {
      cur = srcPat.find(html, imgPat.end()+1);
      quotePat.find(html, srcPat.end()+1);

      String imageURLString = html.substring(srcPat.end()+1, quotePat.start());
      
      if (DEBUG>2) {
	System.out.println("Found image " + imageURLString + " " + name);
      }
      
      imageRd.addElement(new ImageReader(rbe, url, imageURLString, buffer));
      cur = quotePat.start()+1;
    }
  }

  // Luis Rodero-Merino, this function returns the time a transaction+TT should take in ms
  long expectedTransactionTime() {
	  // We compute it as thinking times are usually computed by the spec 
	  return thinkTime();
  }
  long thinkTime() // Luis Rodero-Merino, this function returns the TT in ms
  {
		// By TPCW spec (Clause 5.3.1.1 in both TPCW 1.8 and 1.6):
		// r = uniformRandom() , 0.0 < r < 1.0
		// 7.0 <= mu <= 8.0 
		// TT = -ln(r) * mu
		// if (TT>10*mu) then TT=10*mu
	  	//System.out.println("Using tt_mean: " + rbe.tt_mean);
		double r = rand.nextDouble();
		double tt = rbe.tt_mean * (-Math.log(r));
		tt = (tt > rbe.tt_mean * 10) ? rbe.tt_mean * 10 : tt;		
		return (long)tt;
  }

  long usmd() // Luis Rodero-Merino: stands for 'User Session Minimum duration' (max 1 hour, average 15 minutes)
  {
    //return(rbe.negExp(rand, 0L, 1.0, 3600000L /*60 minutes*/, 0.0183156, 
	//	      900000.0 /* 15 minutes */));
    return(rbe.negExp(rand, 0L, 1.0, 360000L /*6 minutes*/, 0.0183156, 
		      90000.0 /* 1.5 minutes */));
  }

  void nextState()
  {
    long randomValue = nextLong(MAX_PROB-MIN_PROB + 1) + MIN_PROB;
    int j;

    // System.out.println(transProb);

    for (j=0;j<transProb[curState].length;j++) {
    	if (DEBUG>2)
    		System.out.print(" " + j + ", " + transProb[curState][j]);   	
    	if (transProb[curState][j]>=randomValue) {
			rbe.stats.transition(curState,j);
			curTrans = trans[curState][j];
			nextReq = curTrans.request(this, html);
			toHome = trans[curState][j].toHome();
			if (DEBUG>2)
			    System.out.println(name + " from " + curState + " to " + j + " Rand Value " + randomValue + " via:\n     " + nextReq);			
			curState = j;
			return;
    	}
    }

    Debug.fail("Should not be here.");
  }

  // Needed, because Java 1.1 did not have Random.nextInt(int range)
  public long nextLong(long range) {
    long i = Math.abs(rand.nextLong());
    return (i % (range));
  }

  // Adds CUSTOMER_ID and SHOPPING_ID fields to HTTP request,
  //  if they are known.
  String addIDs(String i) {

    if (sessionID != null) {
      i = RBE.addSession(i,RBE.field_sessionID, ""+sessionID);
    }
    
    if (cid != ID_UNKNOWN) {
      i = RBE.addField(i,RBE.field_cid, ""+cid);
    }

    if (shopID != ID_UNKNOWN) {
      i = RBE.addField(i,RBE.field_shopID, ""+shopID);
    }

    return(i);
  }

  public long findID(String html, StrStrPattern tag) {
    long id;

    // NOTE: StringPattern.first/last are not thread-safe. 

    // Find the tag string.
    int i = tag.find(html);
    if (i==-1) {
      return(EB.ID_UNKNOWN);
    }
    i = i + tag.length();

    // Find the digits following the tag string.
    int j = CharSetStrPattern.digit.find(html.substring(i));
    if (j==-1) {
      return(EB.ID_UNKNOWN);
    }

    // Find the end of the digits.
    j = j + i;
    int k = CharSetStrPattern.notDigit.find(html.substring(j));
    if (k==-1) {
      k = html.length();
    }
    else {
      k = k + j;
    }	 
    
    id =  Long.parseLong(html.substring(j, k));
    
    return(id);
  }

  public String findSessionID(String html, StrStrPattern tag, 
			   StrStrPattern etag) {

    // NOTE: StringPattern.first/last are not thread-safe. 

    // Find the tag string.
    int i = tag.find(html);
    if (i==-1) {
      return(null);
    }
    i = i + tag.length();

    // Find end of the digits.
    int j = etag.find(html, i);
    if (j==-1) {
      return(null);
    }
    
    return(html.substring(i,j));
  }
}
