/*-------------------------------------------------------------------------
 * rbe.RBE.java
 * Timothy Heil
 * ECE902 Fall '99
 *
 * TPC-W Remote Browser Emulator.
 *
 * Main program.
 *------------------------------------------------------------------------
 *
 * This file is a modification of the the original Java TPC-W distribution.
 * The modification adds the functionality that stores the processing time
 * of each transaction run during the execution of the experiment. Those times
 * are stored in different files (a file per transaction type), in the folder
 * set by the new configuration parameter 'RESULTSDIR'. In the same folder
 * a file with global statistics is also written, with the total amount of
 * transactions run, the average transaction time, and the resulting throughput.
 * Most of this functionality is included in the new functions
 * computeAndStoreWIRTResults() and storeAllWIRTResults().
 * The modification was performed by Dr. Luis Rodero-Merino, Laboratory of
 * Distributed Systems, Technical University of Madrid, 2012.
 * 
 * The original file was 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.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;

import rbe.args.Arg;
import rbe.args.ArgDB;
import rbe.args.BooleanArg;
import rbe.args.DateArg;
import rbe.args.DoubleArg;
import rbe.args.IntArg;
import rbe.args.PrintStreamArg;
import rbe.args.StringArg;
import rbe.util.Histogram;
import rbe.util.Pad;
import rbe.util.StrStrPattern;

public class RBE {

  // URLs
  public static String www1 = "http://ironsides.cs.wisc.edu:8001/";
  public static String www;
  public static String homeURL;
  public static String shopCartURL;
  public static String orderInqURL;
  public static String orderDispURL;
  public static String searchReqURL;
  public static String searchResultURL;
  public static String newProdURL;
  public static String bestSellURL;
  public static String prodDetURL;
  public static String custRegURL;
  public static String buyReqURL;
  public static String buyConfURL;
  public static String adminReqURL;
  public static String adminConfURL;
  public static Date startTime;

  static {
	 setURLs();
  }

  public static void setURLs()
  {
	 www = www1 + "servlet/";
	 adminReqURL = www+"TPCW_admin_request_servlet";
	 adminConfURL = www+"TPCW_admin_response_servlet";
	 
	 bestSellURL = www+"TPCW_best_sellers_servlet";
	 buyConfURL = www+"TPCW_buy_confirm_servlet";
	 buyReqURL = www+"TPCW_buy_request_servlet";
	 custRegURL = www+"TPCW_customer_registration_servlet";
	 homeURL = www+"TPCW_home_interaction";
	 newProdURL = www+"TPCW_new_products_servlet";
	 orderDispURL = www+"TPCW_order_display_servlet";
	 orderInqURL = www+"TPCW_order_inquiry_servlet";
	 prodDetURL = www+"TPCW_product_detail_servlet";
	 shopCartURL = www+"TPCW_shopping_cart_interaction";
	 searchReqURL = www+"TPCW_search_request_servlet";
	 searchResultURL = www+"TPCW_execute_search";
  }

  public static final StrStrPattern yourCID = new StrStrPattern("C_ID=");
  public static final StrStrPattern yourShopID = new StrStrPattern("SHOPPING_ID=");
  public static final StrStrPattern yourSessionID = new StrStrPattern(";jsessionid=");
  public static final StrStrPattern endSessionID = new StrStrPattern("?");

  // FIELD NAMES
  public static final String field_cid = "C_ID";
  public static final String field_sessionID = ";jsessionid=";
  public static final String field_shopID = "SHOPPING_ID";
  public static final String field_uname = "UNAME";
  public static final String field_passwd = "PASSWD";
  public static final String field_srchType = "search_type";
  public static final String authorType = "author";
  public static final String subjectType = "subject";
  public static final String titleType = "title";
  public static final String field_srchStr = "search_string";
  public static final String field_addflag = "ADD_FLAG";
  public static final String field_iid = "I_ID";
  public static final String field_qty = "qty";
  public static final String field_subject = "subject";
  public static final String field_retflag = "RETURNING_FLAG";
  public static final String field_fname = "FNAME";
  public static final String field_lname = "LNAME";
  public static final String field_street1 = "STREET_1";
  public static final String field_street2 = "STREET_2";
  public static final String field_city = "CITY";
  public static final String field_state = "STATE";
  public static final String field_zip = "ZIP";
  public static final String field_country = "COUNTRY";
  public static final String field_phone = "PHONE";
  public static final String field_email = "EMAIL";
  public static final String field_birthdate = "BIRTHDATE";
  public static final String field_data = "DATA";
  public static final String field_cctype = "CC_TYPE";
  public static final String field_ccnumber = "CC_NUMBER";
  public static final String field_ccname = "CC_NAME";
  public static final String field_ccexp = "CC_EXPIRY";
  public static final String field_shipping = "SHIPPING";
  public static final String field_newimage = "I_NEW_IMAGE";
  public static final String field_newthumb = "I_NEW_THUMBNAIL";
  public static final String field_newcost = "I_NEW_COST";

  public static boolean getImage; // Whether to fetch images.
  public static boolean monitor;  // Whether or not to do monitoring
  public static boolean incremental;  

  public int numCustomer = 1000;  // Number of initial customers
  public int cidA = 1023;         // Used for generating random CIDs (see TPC-W spec Clause 2.3.2)

  // TPC-W spec values for cidA (see TPC-W spec Clause 2.3.2)
  public static final int [][] stdCIDA = 
  // For NUM_CUSTOMERS in this range    Value for A
  {
    {          1,      9999,                     1023 },
    {      10000,     39999,                     4095 },
    {      40000,    159999,                    16383 },
    {     160000,    639999,                    65535 },
    {     640000,   2559999,                   262143 },
    {    2560000,  10239999,                  1048575 },
    {   10240000,  40959999,                  4194303 },
    {   40960000, 163839999,                 16777215 },
    {  163840000, 655359999,                 67108863 }
  };

  public static int numItem = 10000;     // Number of items for sale.
  public static int numItemA = 511;      // Used for search strings (see TPC-W Spec. 2.10.5.1)

  public static final int [][] stdNumItemA =
  // For NUM_ITEMS    Value for A
  {
    {      1000,               63 },
    {     10000,              511 },
    {    100000,             4095 },
    {   1000000,            32767 },
    {  10000000,           524287 }
  };

  public int maxImageRd=10;       // Maximum number of images to read at once.
  public EBStats stats;

  public double slowDown;         // Time slow down factor.
  // 1000 means one thousand real seconds equals one simulated second.
  public double speedUp;          // 1/slowDown.

  // Luis Rodero-Merino: added to allow users set the expected think time mean (see spec TPCW)
  public long tt_mean = 7000;

  public static final BufferedReader bin = 
    new BufferedReader(new InputStreamReader(System.in));

  public static void main(String [] args) {
    RBE rbe = new RBE();
    int i;
    Vector ebs = new Vector(0);

    System.out.println("Remote Browser Emulator for TPC-W.");
    System.out.println("  ECE 902  Fall '99");
    System.out.println("  Version 1.5");

    ArgDB db = new ArgDB();

    startTime = new Date();
    // Luis Rodero-Merino: added this to control load with more than 500 users (we do not allow more than that)
    int maxEBs = 500;
    // Luis Rodero-Merino: the EBs factory also sets for each EB (at instantiation time) the possible browser states and transition probabilities
    EBFactoryArg ebfArg = new EBFactoryArg(	"-EB", "EB Factory", "% Factory class used to create EBs. <class> <#> <factory args...>.",
    										rbe, ebs, db, maxEBs);

    PrintStreamArg oFile = new PrintStreamArg("-OUT", "Output file", "% Name of matlab .m output file for results.", db);

    DateArg st = new DateArg(	"-ST", "Starting time for ramp-up",
    							"% Time (such as Nov 2, 1999 11:30:00 AM CST)  at which to start ramp-up. Useful for synchronizing multiple RBEs.",
    							System.currentTimeMillis()+2000L, db);

    IntArg ru = new IntArg("-RU", "Ramp-up time", "% Seconds used to warm-up the simulator.", 10*60, db);

    IntArg mi = new IntArg("-MI", "Measurement interval", "% Seconds used for measuring SUT performance.", 30*60, db);

    IntArg rd = new IntArg("-RD", "Ramp-down time", "% Seconds of steady-state operation following measurment interval.", 5*60, db);

    // Luis Rodero-Merino: this parameter has certain impact on stats, fortunately it is set to 1.0 in our tests
    DoubleArg slow = new DoubleArg(	"-SLOW", "Slow-down factor",
    								"% 1000 means one thousand real seconds equals one simulated second. Accepts factional values and E notation.",
    								1.0, db);

    DoubleArg tt_scale = new DoubleArg(	"-TT", "Think time multiplication.",
		    							"% Used to increase (>1.0) or decrease (<1.0) think time. In addition to slow-down factor.",
		    							1.0, db);

	 BooleanArg key = new BooleanArg(	"-KEY", "Interactive control.",
			       						"% Require user to hit RETURN before every interaction.  Overrides think time.",
			       						false, db);

	 BooleanArg getImage = new BooleanArg(	"-GETIM", "Request images.",
											"% True will cause RBE to request images.  False suppresses image requests.",
											true, db);

    IntArg img = new IntArg("-CON", "Image connections", "% Maximum number of images downloaded at once.", 4, db);
    
    IntArg custa = new IntArg(	"-CUSTA", "CID NURand A",
    							"% Used to generate random CIDs. See TPC-W Spec. Clause 2.3.2. -1 means use TPC-W spec. value.", 
    							-1, db);

    IntArg cust = new IntArg("-CUST", "Number of customers", "% Number of customers in the database. Used to generated random CIDs.",
    						 1000, db);
    
    // Luis Rodero-Merino
    // NOTE: The number of items, which is set in the call to the program (-ITEM flag) must be the same than
    // the items generated by the TPCW_Populate.java program (which is hardcoded). Default is 10000
    IntArg item = new IntArg(	"-ITEM", "Number of items", "% Number of items in the database. Used to generate random searches.",
    							10000, db);

    IntArg itema = new IntArg(	"-ITEMA", "Item NURand A",
    							"% Used to generate random searches. See TPC-W Spec. Clause 2.10.5.1. -1 means use TPC-W spec. value.",
    							-1, db);

    IntArg debug = new IntArg(	"-DEBUG", "Debug message.", "% Increase this to see more debug messages ~1 to 10.", 0, db);

    IntArg maxErr = new IntArg(	"-MAXERROR", "Maximum errors allowed.",
    							"% RBE will terminate after this many errors.  0 implies no limit.", 1, db);

	StringArg wwwArg =	new StringArg(	"-WWW", "Base URL", "% The root URL for the TPC-W pages.", RBE.www1, db);
	
	BooleanArg monArg = new BooleanArg(	"-MONITOR", "Do utilization monitoring", "% TRUE=do monitoring, FALSE=Don't do monitoring",
										false, db);
	
	BooleanArg incrArg = new BooleanArg("-INCREMENTAL", "Start EBs Incrementally",
										"% TRUE=do them in increments, FALSE=Do them all at once",
										false, db);
	
	StringArg resultsDir = new StringArg("-RESULTSDIR", "Results dir", "Dir to store stats results", db);

    if (args.length==0) {
      Usage(args, db);
      return;
    }

    try {
      db.parse(args);
    }
    catch (Arg.Exception ae) {
      System.out.println("Error:");
      System.out.println(ae);
      Usage(args, db);
      return;
    }

    Usage(args, db);

    long tt_mean = 7000; // Defaults to 7000 ms of thinking time (in fact transactions time)    
    if(ebfArg.demandedEBs > maxEBs) {
    	System.out.println("Resetting tt_mean by multiplying it by " + (maxEBs*1.0/ebfArg.demandedEBs));
    	tt_mean *= (maxEBs*1.0/ebfArg.demandedEBs);
    }
   	System.out.println("tt_mean set to: " + tt_mean);        
   	
    // Copy in parameters.
    rbe.maxImageRd  = img.num;
    rbe.numCustomer = cust.num;
    rbe.cidA        = custa.num;
    RBE.numItem     = item.num;
    RBE.numItemA    = itema.num;
    rbe.tt_mean		= tt_mean;
    RBE.www1        = wwwArg.s;
    RBE.getImage    = getImage.flag;
    RBE.monitor     = monArg.flag;
    RBE.incremental = incrArg.flag;
    RBE.setURLs();
    EB.DEBUG = debug.num;
    EBStats.maxError = maxErr.num;

    // Check for numCustomer valid. 
    if (rbe.numCustomer < 1) {
      System.out.println("Number of customers (" + rbe.numCustomer + ")  must be >= 1.");
      return;
    }

    if (rbe.numCustomer > stdCIDA[stdCIDA.length-1][1]) {
      System.out.println("Number of customers (" + rbe.numCustomer + ")  must be <= " + stdCIDA[stdCIDA.length-1][1] + ".");
      return;
    }

    // Compute default CID A value, if needed.
    if (rbe.cidA == -1) {
    	for (i=0;i<stdCIDA.length;i++) {
    		if ((rbe.numCustomer >= stdCIDA[i][0]) && (rbe.numCustomer <= stdCIDA[i][1])) {
    			rbe.cidA = stdCIDA[i][2];
    			System.out.println("Choose " + rbe.cidA + " for -CUSTA.");
    			break;
    		}
    	}
    }

    // Check num item is valid.
    for (i=0;i<stdNumItemA.length;i++) {
    	if (rbe.numItem == stdNumItemA[i][0])
    		break;
    }

    if (i==stdNumItemA.length) {
    	System.out.println("Number of items (" + rbe.numItem + ") must be one of ");
    	for (i=0;i<stdNumItemA.length;i++) {
    		System.out.println("    " + stdNumItemA[i][0]);
    	}
    	return;
    }

    // Compute standard item A value, if needed.
    if (rbe.numItemA == -1) {
    	rbe.numItemA = stdNumItemA[i][1];
    	System.out.println("Choose " + rbe.numItemA + " for -ITEMA.");
    }

    // Check max image read parameter.
    if (rbe.maxImageRd < 1) {
    	System.out.println("-CON must be >= 1.");
    	return;
    } 
    
    System.out.println("TTMEAN set to " + rbe.tt_mean);

    // Correct ramp-up time by start time.
    long start = st.d.getTime();
    long addRU = start - System.currentTimeMillis();

    if (addRU < 0L) {
      System.out.println("Warning: start time " + (((double) addRU)/1000.0) + 
			 " seconds before current time.\n" +
			 "Resetting to current time.");
      start = System.currentTimeMillis();
    }

    // Luis Rodero-Merino: in our tests, both slowDown and speedUp are set to 1
    rbe.slowDown = slow.num;
    rbe.speedUp  = 1/rbe.slowDown;
    
    // Luis Rodero-Merino: folder where results will be stored
    File wirtStatsDir = new File(resultsDir.s);
    remove(wirtStatsDir);
    wirtStatsDir.mkdirs();

    // Create statistics object.    
//    public EBStats(RBE rbe,
//			  int wirt_max, int wirt_binSize,
//			  int tt_max, int tt_binSize,
//			  int states,
//			  long start,    // When to start test.
//			  long miWait,   // Length of ramp-up
//			  long miDur,    // Duration of measurement interval
//			  long rampDown) // Length of ramp-down
    
    // Luis Rodero-Merino: comments added by me 
    rbe.stats = new EBStats(rbe,// This object
			    60000, 50,		// Max amount of WIRTs stored in EBStats' histograms, and the size of each histogram bin (there will be 15 histograms, hardcoded in EBStats)
			    75000, 100,		// Max amount of 'Think Times' stored in EBStats' histogram, and the size of each histogram bin (only one histogram, hardcoded in EBStats)
			    ebfArg.maxState,// Possible states of an Emulated Browser (EB), read from the EB factory
			    start,			// When to start test.
			    1000L*ru.num,	// Length of ramp-up
			    1000L*mi.num,	// Duration of measurement interval
			    1000L*rd.num,	// Length of ramp-down,
			    5,				// Luis Rodero-Merino: seconds between live measurements to show in file (see next parameter)
			    new File(wirtStatsDir, "ThAndAvTxTime.csv"), // Luis Rodero-Merino: File to store live measurements (CSV format)
			    new File(wirtStatsDir, "ThAndAvTxTime.txt")  // Luis Rodero-Merino: File to store live measurements (txt format)
			    );
    
    String pidStr = null;
    if(monitor){
    	try {
		    char c[] = new char[10];
		    Runtime r = Runtime.getRuntime();
		    Process proc = r.exec("/usr/local/bin/monitor -log /bigvol2/monitor_traces/java.mon -interval 10 -sample 10 -L -Toplog -F");
		    //	    InputStream i= proc.getInputStream();
		    InputStreamReader reader= new InputStreamReader(proc.getInputStream());
		    reader.skip(40);
		    
		    reader.read(c, 0, 10);
		    for(int j = 0; c[j]!= '\n'; j++)
		    	pidStr = new String(c,0,j);
		    System.out.println("Pid = " + pidStr);
	    
    	} catch(java.lang.Exception ex){
    		System.out.println("Unable to monitor process");
    	}
    }

    // Re-initialize EBs.  This is necessary to correctly scale usmd for
    //  the slow-down factor which was not computed when EBs were created.
    for (i=0;i<ebs.size();i++) {
      EB e = (EB) ebs.elementAt(i);
      e.initialize();
      e.tt_scale = tt_scale.num; 	// Luis Rodero-Merino: a factor to modify Thinking Time, by default is 1.0 (no modification)
      e.waitKey = key.flag;			// Luis Rodero-Merino: this flag sets if the EB is interactive, by default is 'false'
      e.setName("TPC-W Emulated Browser " + (i+1));
      e.setDaemon(true);
    }

    // Start EBs...
    System.out.println("Starting " + ebs.size() + " EBs.");

    for (i=0;i<ebs.size();i++) {
      EB e = (EB) ebs.elementAt(i);
      // Luis Rodero-Merino: this 'try' statement is only to start EBs in sets of 10 (not all at once), but it is not used by us
      try {
		  if(RBE.incremental && ((i+1)%10 == 0)) {
		      System.out.println(i + " EBs Alive!");
		      //Thread.currentThread().sleep((ru.num*1000L)/ebs.size());
		      Thread.currentThread().sleep(10000L);
		  }
      } catch(java.lang.Exception ex) {
    	  System.out.println("Unable to sleep");
      }
      e.start();
    }
    System.out.println("All of the EBs are alive!");

    // Wait for ramp down.
    rbe.stats.waitForRampDown();

    if (monitor){
		try {
		    Runtime r = Runtime.getRuntime();
		    Process proc = r.exec("/bin/kill "+pidStr);
		} catch(java.lang.Exception ex){
		    System.out.println("Unable to destroy monitor process");
		}
    }
 
    // Terminate all EBs
    System.out.println("Terminating EBs...");
    EB.terminate = true;
    try {
    	Thread.currentThread().sleep(10000L);
    } catch (InterruptedException ie) {
    	System.out.println("Unable to sleep!");
    }

    /*for(i=0; i < ebs.size(); i++) {
    	EB e = (EB) ebs.elementAt(i);
    	System.out.println(e + ": alive: " + e.isAlive());
    }*/

    for (i=0;i<ebs.size();i++) {
    	EB e = (EB) ebs.elementAt(i);
    	if(!e.isAlive()) {
        	System.out.println("main thread: " + e + " is not alive");
    		continue;
    	}
    	System.out.println("main thread: " + e + " is alive, about to interrupt");
    	//e.interrupt();
    	e.stop();
    	/*try {
    		e.join();
    	} catch (InterruptedException inte) {
    		inte.printStackTrace();
    		return;
    	}*/
    }
    System.out.println("EBs finished.");

    if (!oFile.set()) {
    	oFile.s = System.out;
    }

    oFile.s.println("% Start time: " + startTime);
    oFile.s.println("% System under test: " + www);
    Date endTime = new Date();
    oFile.s.println("% End time: " + endTime);
    oFile.s.println("% Transaction Mix: " + ebfArg.className);
    db.print(oFile.s);
    //ofile.s.println("% Database parameters: num items=" + rbe.numItem + 
    //	    " num customers= " + rbe.numCustomer);
    //oFile.s.println("% Images On?: " + RBE.getImage + 
    //	    " Think Time Scale Factor: " + e.tt_scale); 

    rbe.stats.print(oFile.s);
    oFile.s.close();
    
    
    // Luis Rodero-Merino: storing transaction times histograms and
    // computing if they fulfilled TPC-W conditions (see TPC-W spec 1.8 page 104)    
    List<double[]> allTransAndAverTimesPerType = new ArrayList<double[]>();
    
    // STATE 0: INIT (not defined in the spec)
    // STATE 1: ADMIN CONFIRM (90% WIRT constraint 20)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "01-AdminConfirmDurTimes.txt"), rbe.stats.transactionTimesArray[1],
    															new File(wirtStatsDir, "01-AdminConfirmInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[1],
    															20000));
    // STATE 2: ADMIN REQUEST (90% WIRT constraint 3)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "02-AdminRequestDurTimes.txt"), rbe.stats.transactionTimesArray[2],
			new File(wirtStatsDir, "02-AdminRequestInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[2],
    		3000));
    // STATE 3: BEST SELLERS (90% WIRT constraint 5)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "03-BestSellersDurTimes.txt"), rbe.stats.transactionTimesArray[3],
			new File(wirtStatsDir, "03-BestSellersInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[3],
    		5000));
    // STATE 4: BUY CONFIRM (90% WIRT constraint 5)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "04-BuyConfirmDurTimes.txt"), rbe.stats.transactionTimesArray[4],
			new File(wirtStatsDir, "04-BuyConfirmInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[4],
    		5000));
    // STATE 5: BUY REQUEST (90% WIRT constraint 3)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "05-BuyRequestDurTimes.txt"), rbe.stats.transactionTimesArray[5],
			new File(wirtStatsDir, "05-BuyRequestInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[5],
    		3000));
    // STATE 6: CUSTOMER REGISTER (90% WIRT constraint 3)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "06-CustomerRegisterDurTimes.txt"), rbe.stats.transactionTimesArray[6],
			new File(wirtStatsDir, "06-CustomerRegisterInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[6],
    		3000));
    // STATE 7: HOME (90% WIRT constraint 3)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "07-HomeDurTimes.txt"), rbe.stats.transactionTimesArray[7],
			new File(wirtStatsDir, "07-HomeInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[7],
			3000));
    // STATE 8: NEW PRODUCTS (90% WIRT constraint 5)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "08-NewProductsDurTimes.txt"), rbe.stats.transactionTimesArray[8],
			new File(wirtStatsDir, "08-NewProductsInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[8],
    		5000));
    // STATE 9: ORDER DISPLAY (90% WIRT constraint 3)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "09-OrderDisplayDurTimes.txt"),rbe.stats.transactionTimesArray[9],
			new File(wirtStatsDir, "09-OrderDisplayInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[9],
    		3000));
    // STATE 10: ORDER INQUIRY (90% WIRT constraint 3)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "10-OrderInquiryDurTimes.txt"), rbe.stats.transactionTimesArray[10],
			new File(wirtStatsDir, "10-OrderInquiryInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[10],
    		3000));    
    // STATE 11: PRODUCT DETAIL (90% WIRT constraint 3)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "11-ProductDetailDurTimes.txt"), rbe.stats.transactionTimesArray[11],
			new File(wirtStatsDir, "11-ProductDetailInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[11],
    		3000));
    // STATE 12: SEARCH REQUEST (90% WIRT constraint 3)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "12-SearchRequestDurTimes.txt"), rbe.stats.transactionTimesArray[12],
			new File(wirtStatsDir, "12-SearchRequestInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[12],
    		3000));    
    // STATE 13: SEARCH RESULTS (90% WIRT constraint 10)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "13-SearchResultsDurTimes.txt"), rbe.stats.transactionTimesArray[13],
			new File(wirtStatsDir, "13-SearchResultsInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[13],
    		3000));
    // STATE 14: SHOPPING CART (90% WIRT constraint 3)
    allTransAndAverTimesPerType.add(computeAndStoreWIRTResults(new File(wirtStatsDir, "14-ShoppingCartDurTimes.txt"), rbe.stats.transactionTimesArray[14],
			new File(wirtStatsDir, "14-ShoppingCartInitAndDurTimes.txt"), rbe.stats.transactionInitTimesArray[14],
    		3000));
    
    // Writing final statistics
    storeAllWIRTResults(wirtStatsDir, rbe.stats.allTimesArray, rbe.stats.allInitTimesArray, allTransAndAverTimesPerType, ebfArg.demandedEBs, mi.num);
    
    System.out.println("Really finishing RBE!.");    
  }
  
  // Luis Rodero-Merino: Method to store WIRT times into given file, along with some information (see TPC-W spec 1.8)
  private static double[] computeAndStoreWIRTResults(File wirtTimesFile, List wirtTimes, File wirtInitTimesFile, List wirtInitTimes, long wirtConstraint) {
	  
	if(wirtTimes.size() != wirtInitTimes.size()) {
		throw new Error("ERROR: Different sizes of arrays with tx times and tx init times!! , file " + wirtInitTimesFile.getName() + " won't be created");
	}
	  
	System.out.println("LRM: Writing WIRT times in file " + wirtTimesFile.getPath());  
	PrintStream wirtTimesStream = null;
	PrintStream wirtInitTimesStream = null;
	try {
		wirtTimesStream = new PrintStream(new FileOutputStream(wirtTimesFile));
		wirtInitTimesStream = new PrintStream(new FileOutputStream(wirtInitTimesFile));
	} catch (FileNotFoundException e) {
		System.out.println("WTF??");
	}
	
	if(wirtTimes.isEmpty()) {
		wirtTimesStream.println("# No transactions of this type");
		wirtTimesStream.close();
		wirtInitTimesStream.println("# No transactions of this type");
		wirtInitTimesStream.close();
		return new double[]{0,0};
	}
	
	// Writing wirt times file, that contains the duration of all transactions; also we keep the total duration of all tx for statistics
	double totalTimes = 0;
	int countOfTimesBeyondWIRTConstraint = 0;
	Iterator wirtTimesIter = wirtTimes.iterator();
	while(wirtTimesIter.hasNext()) {
		long wirtTime = (Long)wirtTimesIter.next();
		totalTimes += wirtTime;
		if(wirtTime > wirtConstraint)
			countOfTimesBeyondWIRTConstraint++;
		wirtTimesStream.print(wirtTime+"\t");
	}
	wirtTimesStream.println();
	
	// Writing wirt init times file 
	Iterator wirtInitTimesIter = wirtInitTimes.iterator();
	wirtTimesIter = wirtTimes.iterator();
	while(wirtTimesIter.hasNext()) {
		long wirtTime = (Long)wirtTimesIter.next();
		long wirtInitTime = (Long)wirtInitTimesIter.next();
		wirtInitTimesStream.println(wirtInitTime+"\t"+wirtTime);			
	}
	wirtInitTimesStream.close();
	
	// Cannot use the 'modern' loop as we are using JDK 1.3
	/*for(long time: wirtTimes) {
		totalTimes += time;
		if(time > wirtConstraint)
			countOfTimesBeyondWIRTConstraint++;
		stream.println(time);
	}*/
	double averWIRTTime = (totalTimes/wirtTimes.size());
	double percWIRTTimesBeyondConstraint = (countOfTimesBeyondWIRTConstraint*100.0/wirtTimes.size());
	// This is a brute force way to compute the 90th percentile... but anyway
	Collections.sort(wirtTimes);
	long percentile90th = (Long)wirtTimes.get(wirtTimes.size()*90/100);
	wirtTimesStream.println(	"# Total transactions: " + wirtTimes.size() +
					"\tAver WIRT time: " + averWIRTTime +
					"\tTimes beyond WIRT constraint (" + wirtConstraint + "): " + countOfTimesBeyondWIRTConstraint +
					"\tPercentage times beyond WIRT constraint: " + percWIRTTimesBeyondConstraint +
					"\tPercentile 90th: " + percentile90th +
					"\tPassed test: " + ((percWIRTTimesBeyondConstraint < 10.0 && (averWIRTTime <= percentile90th + 100)) ? "TRUE" : "FALSE"));
	wirtTimesStream.close();
	return new double[]{wirtTimes.size(), averWIRTTime};
  }
  
  private static void storeAllWIRTResults(File wirtResultsDir, List<Long> allTransTimes, List<Long> allTransInitTimes, List<double[]> allTransAndAverTimesPerType, int customers, int measurementInterval) {
	  // Printing count of all transactions of all types
	  PrintStream allTransCountAndAvTimeStream = null;
	  try {
		  allTransCountAndAvTimeStream = new PrintStream(new FileOutputStream(new File(wirtResultsDir, "AllTransCountAndAvTime.txt")));
	  } catch (FileNotFoundException e) {
		  System.out.println("WTF??");
	  }
	  long totalTimes = 0;
	  double averTrTime = 0;
	  double throughput = 0;
	  if(allTransTimes.size() > 0) {
		  for(long time: allTransTimes)
			  totalTimes += time;
		  averTrTime = (totalTimes*1.0)/allTransTimes.size();
		  throughput = (allTransTimes.size()*1.0)/measurementInterval;
	  }
	  allTransCountAndAvTimeStream.println("#Costumers\tInterval(sec)\tTransactions\tAvTrTime(ms)\tThroughput(tr/sec)");
	  allTransCountAndAvTimeStream.println(customers + "\t" + measurementInterval + "\t" + allTransTimes.size()+"\t"+averTrTime+"\t"+throughput);
	  allTransCountAndAvTimeStream.close();
	  
	  // Printing transactions run and average duration per second, ordered by transaction init time
	  // First ordering transactions durations by init times
	  SortedMap<Long, List<Long>> txInitAndDur = new TreeMap<Long, List<Long>>();
	  for(int txIndex = 0; txIndex < allTransTimes.size(); txIndex++) {
		  long txInit = allTransInitTimes.get(txIndex);
		  long txDuration = allTransTimes.get(txIndex);
		  List<Long> durationTimes = txInitAndDur.get(txInit);
		  if(durationTimes == null) {
			  durationTimes = new ArrayList<Long>();
			  txInitAndDur.put(txInit, durationTimes);
		  }
		  durationTimes.add(txDuration);
	  }
	  // And now computing, per each second, how many tx were run and their av execution time
	  Set<Long> orderedInitTimes = txInitAndDur.keySet();
	  long nextSecondInitTime = txInitAndDur.firstKey() + 1000;
	  List<Long> presentSecondAllTxDurationTimes = new ArrayList<Long>();
	  List<Integer> txRunPerSecond = new ArrayList<Integer>();
	  List<Double> averTxTimePerSecond = new ArrayList<Double>();
	  for(long initTime: orderedInitTimes) {
		  if(initTime > nextSecondInitTime) {
			  long thisSecondAllTxTimesSum = 0;
			  for(long txDuration: presentSecondAllTxDurationTimes)
				  thisSecondAllTxTimesSum += txDuration;
			  averTxTimePerSecond.add((thisSecondAllTxTimesSum*1.0)/presentSecondAllTxDurationTimes.size());
			  txRunPerSecond.add(presentSecondAllTxDurationTimes.size());
			  presentSecondAllTxDurationTimes.clear();
		      nextSecondInitTime += 1000;	  
		  }
		  presentSecondAllTxDurationTimes.addAll(txInitAndDur.get(initTime));
	  }
	  // Finally printing to file
	  PrintStream transAvDurPerSecondStream = null;
	  try {
		  transAvDurPerSecondStream = new PrintStream(new FileOutputStream(new File(wirtResultsDir, "TransCountAndAvTransTimePerSecond.txt")));
	  } catch (FileNotFoundException e) {
		  System.out.println("WTF??");		  
	  }
	  transAvDurPerSecondStream.println("#Second\tTxRun\tAvTxTime");
	  for(int second = 0; second < averTxTimePerSecond.size(); second++) 
		  transAvDurPerSecondStream.println(second + "\t" + txRunPerSecond.get(second) + "\t" + averTxTimePerSecond.get(second));
	  transAvDurPerSecondStream.close();
	  
	  // Count of transactions per transaction type
	  PrintStream transCountPerTypeStream = null;
	  try {
		  transCountPerTypeStream = new PrintStream(new FileOutputStream(new File(wirtResultsDir, "TransCountPerType.txt")));
	  } catch (FileNotFoundException e) {
		  System.out.println("WTF??");		  
	  }
	  transCountPerTypeStream.print(customers+"");
	  for(double[] transData: allTransAndAverTimesPerType)
		  transCountPerTypeStream.print("\t" + transData[0]);
	  transCountPerTypeStream.println();
	  transCountPerTypeStream.close();

	  // Average transaction time per type of transaction
	  PrintStream transAvTimesPerTypeStream = null;
	  try {
		  transAvTimesPerTypeStream = new PrintStream(new FileOutputStream(new File(wirtResultsDir, "TransAvTimesPerType.txt")));
	  } catch (FileNotFoundException e) {
		  System.out.println("WTF??");
	  }
	  transAvTimesPerTypeStream.print(customers+"");
	  // Writing transactions stats per type
	  for(double[] transData: allTransAndAverTimesPerType)
		  transAvTimesPerTypeStream.print("\t" + transData[1]);
	  transAvTimesPerTypeStream.println();
	  transAvTimesPerTypeStream.close();
  }
  
  
  public static void getKey() {
	 System.out.println("Type RETURN to continue...");
	 try {
		bin.readLine();
	 }
	 catch (java.io.IOException e) {
		e.printStackTrace();		
	 }
  }

  public static void Arguments(String [] args) {
    int a;

    for (a=0;a<args.length;a++) {
      System.out.println("#" + Pad.l(3,""+(a+1)) + "  " + args[a]);
    }
  }

  public static void Usage(String [] args, ArgDB db) {
    System.out.println("Input command-line arguments");
    Arguments(args);

    System.out.println("\nOptions");
    db.print(System.out);
  }

  // Static random functions.

  // Defined in TPC-W Spec Clause 2.1.13
  public static final int NURand(Random rand, int A, int x, int y)
  {
    return((( (nextInt(rand, A+1)) | (nextInt(rand, y-x+1)+x)) % (y-x+1)) +x);
  }

  public final long slow(long t) { return((long) (slowDown*t+0.5)); } 	// Luis Rodero-Merino: as the benchmark is callsed with slowDown 1.0,
  																		// and the 0.5 value is lost due to truncation, this call has no effect
  public final long speed(long t) { return((long) (speedUp*t+0.5)); } // Luis Rodero-Merino: this method is called by EBStats, but it does not have any impact in our experiments

  // Negative exponential distribution used by 
  //  TPC-W spec for Think Time (Clause 5.3.2.1) and USMD (Clause 6.1.9.2)
  public final long negExp(	Random rand,
		  					long min,	// ? IT IS NOT USED AT ALL!
		  					double lMin,// ? IT IS NOT USED AT ALL!
		  					long max,	// To truncate the result
		  					double lMax,// To truncate the result
		  					double mu)	// Expected average of the distribution 
  {	  
	double r = rand.nextDouble();

    if (r < lMax) {
      return(slow(max)); // Really weird way to truncate things...
    }

	 return(slow((long) (-mu*Math.log(r))));
  }

  // Defined in TPC-W Spec Clause 4.6.2.8
  private static final String [] digS = {
    "BA","OG","AL","RI","RE","SE","AT","UL","IN","NG"
  };
  
  public static String digSyl(long d, int n) {
    String s = "";

    if (n==0)
    	return(digSyl(d));

    for (;n>0;n--) {
      int c = (int)(d % 10);
      s = digS[c]+s;
      d = d /10;
    }

    return(s);
  }

  public static String digSyl(long d) {
    String s = "";

    for (;d!=0;d=d/10) {
      int c = (int)(d % 10);
      s = digS[c]+s;      
    }

    return(s);
  }

  // Gets the username and password fields according to TPC-W Spec. 4.6.2.9 ff.
  public static String unameAndPass(long cid) {
    String un = digSyl(cid, 0);
    return(field_uname + "=" + un +
	   "&" + field_passwd + "=" + un.toLowerCase());
  }

  // Subject list.  See TPC-W Spec. 4.6.2.11
  public static final String [] subjects = {
    "ARTS",
    "BIOGRAPHIES",
    "BUSINESS",
    "CHILDREN",
    "COMPUTERS",
    "COOKING",
    "HEALTH",
    "HISTORY",
    "HOME",
    "HUMOR",
    "LITERATURE",
    "MYSTERY",
    "NON-FICTION",
    "PARENTING",
    "POLITICS",
    "REFERENCE",
    "RELIGION",
    "ROMANCE",
    "SELF-HELP",
    "SCIENCE-NATURE",
    "SCIENCE_FICTION",
    "SPORTS",
    "YOUTH",
    "TRAVEL"
  };

  // Select a subject string randomly and uniformly from above list.
  //   See TPC-W Spec. 2.10.5.1
  public static String unifSubject(Random rand) {
    return(subjects[nextInt(rand, subjects.length)]);
  }

  // Select a subject string randomly and uniformly from above list.
  //   See TPC-W Spec. 2.10.5.1
  // NOTE:  The "YOUTH" and "TRAVEL" subjects are missing from the home page.
  //   I believe this to be an error, but cannot be sure.
  // Change this function if this is determined to not be an error.
  public static String unifHomeSubject(Random rand) 
    { return(unifSubject(rand)); }

  // Adds a field to a HTTP request.
  //  field name = f, value = v.
  public static String addField(String i, String f, String v)
  {
    if (i.indexOf((int) '?')==-1) {
      // First field
      i = i + '?';
    }
    else {
      // Another additional field.
      i = i + '&';
    }
    i = i + f + "=" + v;

    return(i);
  }

  public static String addSession(String i, String f, String v){
    StringTokenizer tok = new StringTokenizer(i, "?");
    String return_val = null;
    try {
      return_val = tok.nextToken();
      return_val = return_val + f + v;
      return_val = return_val + "?" + tok.nextToken();
    }
    catch (NoSuchElementException e) { 
    }

    return(return_val);
  }

  public static final String [] nchars = { 
	 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
  };

  public static final String [] achars = { 
	 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
	 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 
	 "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", 
	 "u", "v", "w", "x", "y", "z", 
	 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", 
	 "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", 
	 "U", "V", "W", "X", "Y", "Z", 

	 "%21",  // "!"
	 "%40",  // "@"
	 "%23",  // "#"
	 "%24",  // "$"
	 "%25",  // "%"
	 "%5E",  // "^"
	 "%26",  // "&"
	 "*",    // "*"
	 "%28",  // "("
	 "%29",  // ")"
	 //	 "_",    // "_"
	 "%5F", 
	 "-",    // "-"
	 "%3D",  // "="
	 "%2B",  // "+"
	 "%7B",  // "{"
	 "%7D",  // "}"
	 "%5B",  // "["
	 "%5D",  // "]"
	 "%7C",  // "|"
	 "%3A",  // ":"
	 "%3B",  // ";"
	 "%2C",  // ","
	 ".",    // "."
	 "%3F",  // "?"
	 "%2F",  // "/"
	 "%7E",  // "~"
	 "+"     // " "
  };

  public static String astring(Random rand, int min, int max)
  {
	 return(rstring(rand, min,max,achars));
  }

  public static String nstring(Random rand, int min, int max)
  {
	 return(rstring(rand, min,max,nchars));
  }

  private static String rstring(Random rand, int min, int max, String [] cset)
  {
    int l = cset.length;
    int r = nextInt(rand, max-min+1)+min;
    String s;

    for (s="";s.length()<r;s = s + cset[nextInt(rand, l)]);

    return(s);
  }

  public static final String [] countries = {
	 "United+States",                    "United+Kingdom",
	 "Canada",                           "Germany",
	 "France",                           "Japan",
	 "Netherlands",                      "Italy",
	 "Switzerland",                      "Australia",
	 "Algeria",                          "Argentina",
	 "Armenia",                          "Austria",
	 "Azerbaijan",                       "Bahamas",
	 "Bahrain",                          "Bangla+Desh",
	 "Barbados",                         "Belarus",
	 "Belgium",                          "Bermuda",
	 "Bolivia",                          "Botswana",
	 "Brazil",                           "Bulgaria",
	 "Cayman+Islands",                   "Chad",
	 "Chile",                            "China",
	 "Christmas+Island",                 "Colombia",
	 "Croatia",                          "Cuba",
	 "Cyprus",                           "Czech+Republic",
	 "Denmark",                          "Dominican+Republic",
	 "Eastern+Caribbean",                "Ecuador",
	 "Egypt",                            "El+Salvador",
	 "Estonia",                          "Ethiopia",
	 "Falkland+Island",                  "Faroe+Island",
	 "Fiji",                             "Finland",
	 "Gabon",                            "Gibraltar",
	 "Greece",                           "Guam",
	 "Hong+Kong",                        "Hungary",
	 "Iceland",                          "India",
	 "Indonesia",                        "Iran",
	 "Iraq",                             "Ireland",
	 "Israel",                           "Jamaica",
	 "Jordan",                           "Kazakhstan",
	 "Kuwait",                           "Lebanon",
	 "Luxembourg",                       "Malaysia",
	 "Mexico",                           "Mauritius",
	 "New+Zealand",                      "Norway",
	 "Pakistan",                         "Philippines",
	 "Poland",                           "Portugal",
	 "Romania",                          "Russia",
	 "Saudi+Arabia",                     "Singapore",
	 "Slovakia",                         "South+Africa",
	 "South+Korea",                      "Spain",
	 "Sudan",                            "Sweden",
	 "Taiwan",                           "Thailand",
	 "Trinidad",                         "Turkey",
	 "Venezuela",                        "Zambia",
  };

  public static String unifCountry(Random rand) {
	 return(countries[nextInt(rand, countries.length)]);
  }

  public static final Calendar c = new GregorianCalendar(1880,1,1);
  public static final long dobStart = c.getTime().getTime();;
  public static final long dobEnd =  System.currentTimeMillis();;

    /*static {
	 Calendar c = new GregorianCalendar(1880,1,1);
	 dobStart = c.getTime().getTime();

	 // FIXME:  This has some drawbacks.  Assums the day does not change
	 //  during benchmark execution.  Also, does not give the current
	 //  date the same chance as all the other dates.
	 //  In the end, this simplification will not affect anything.
	 dobEnd   = System.currentTimeMillis();
	 
	 }*/

  public static String unifDOB(Random rand) {
	 long t = ((long) (rand.nextDouble()*(dobEnd-dobStart)))+dobStart;
	 Date d = new Date(t);
	 Calendar c = new GregorianCalendar();
	 c.setTime(d);

	 return("" + c.get(Calendar.DAY_OF_MONTH) + "%2f" + 
			  c.get(Calendar.DAY_OF_WEEK) + "%2f" + c.get(Calendar.YEAR));
  }

  public static final String [] ccTypes = {
	 "VISA", "MASTERCARD", "DISCOVER", "DINERS", "AMEX"
  };

  public static String unifCCType(Random rand) {
	 return(ccTypes[nextInt(rand, ccTypes.length)]);
  }

  public static String unifExpDate(Random rand) {
	 Date d = new Date(System.currentTimeMillis() + 
							 ((long) (nextInt(rand, 730))+1)*24L*60L*60L*1000L);
	 Calendar c = new GregorianCalendar();
	 c.setTime(d);

	 return("" + c.get(Calendar.DAY_OF_MONTH) + "%2f" + 
			  c.get(Calendar.DAY_OF_WEEK) + "%2f" + c.get(Calendar.YEAR));

  }

  public static int unifDollars(Random rand) {
	 return (nextInt(rand, 9999)+1);
  }
  public static int unifCents(Random rand) {
	 return (nextInt(rand, 100));
  }

  public static final String [] shipTypes = {
	 "AIR",
	 "UPS",
	 "FEDEX",
	 "SHIP",
	 "COURIER",
	 "MAIL"
  };

  public static String unifShipping(Random rand) {
	 int i = nextInt(rand, shipTypes.length);
	 return(shipTypes[i]);
  }

  // FIXME:  These need to be implemented with specifics from
  //  the data-base/ web-server people.
  public static String unifImage(Random rand) {
	 int i = nextInt(rand, numItem)+1;
	 int grp = i % 100;
	 return(mungeURL("img" + grp + "/image_" + i + ".gif"));
  }

  public static String unifThumbnail(Random rand) {
	 int i = nextInt(rand, numItem)+1;
	 int grp = i % 100;
	 return(mungeURL("img" + grp + "/thumb_" + i + ".gif"));
  }

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

  // Finds any non-URLable characters and converts them to URL form.
  //  All alpha numerics are left alone.
  //  <space> goes to '+'
  //  All others go to %xx (hexadecimal representation.)
  public static String mungeURL(String url) {
	 int i;
	 String mURL = "";

	 // System.out.println("Munging: " + url);
	 for (i=0;i<url.length();i++) {
		char ch = url.charAt(i);
		if ( ((ch >= '0') && (ch <='9')) ||
			  ((ch >= 'a') && (ch <='z')) ||
			  ((ch >= 'A') && (ch <='Z')) || 
		     ((ch == '.') || (ch =='/')) ) {
		  mURL = mURL + ch;
		}
		else if ( ch == ' ') {
		  mURL = mURL + '+';
		}
		else {
		  int d = ch;
		  int d1 = d>>4;
		  int d2 = d & 0xf;
		  char c1 = (char) ( (d1 > 9) ? ( 'A'+d1-10 ) : '0'+d1);
		  char c2 = (char) ( (d2 > 9) ? ( 'A'+d2-10 ) : '0'+d2);
		  mURL = mURL + "%" + c1 + c2;
		}
		// System.out.println("munging: " + mURL);
	 }
	 
	 return(mURL);
  }
  
  // Used to remove files or dirs (when it is a dir, first all its contents must be removed recursively before deleting the dir itself)
  private static void remove(File file) {
	  if(file.isDirectory())
		  for(String itemName: file.list()) 
			  remove(new File(itemName));
	  file.delete();
  }
  
}
