package testclient;

import iaik.ixsil.algorithms.Transform;
import iaik.ixsil.algorithms.TransformImplExclusiveCanonicalXML;
import iaik.ixsil.init.IXSILInit;
import iaik.ixsil.util.URI;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.io.UTFDataFormatException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.Principal;
import java.security.Security;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.TreeMap;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXParseException;

import at.gv.egovernment.moa.spss.client.call.SignatureCreationCaller;
import at.gv.egovernment.moa.spss.client.call.SignatureVerificationCaller;
import at.gv.egovernment.moa.util.Base64Utils;
import at.gv.egovernment.moa.util.Constants;
import at.gv.egovernment.moa.util.DOMUtils;
import at.gv.egovernment.moa.util.XPathUtils;

import com.sun.net.ssl.internal.ssl.Provider;

/**
 * @author Sven
 *
 * To change this generated comment edit the template variable "typecomment":
 * Window>Preferences>Java>Templates.
 * To enable and disable the creation of type comments go to
 * Window>Preferences>Java>Code Generation.
 */
public class TestClient
{

  // stats
  private int pos_ok = 0;
  private int pos_nok = 0;
  private int pos_exc = 0;
  private int neg_ok = 0;
  private int neg_nok = 0;
  private int neg_exc = 0;
  private long max_request = 0;
  private long min_request = 99999;
  private long all_request = 0;
  private int count_all_request = 0;
  private int count_tests = 0;

  private TreeMap suits = new TreeMap();
  private PrintStream Log = null;
  private static File directory = null;
  private static String directorystring = null;
  private static Provider ssl_provider = null;
  private static boolean ssl_connection = false;
  private static String defaultserver = "http://localhost:8080/";
  private static String defaultdirectory = "data/feature41/";
  private static String server;

  private static boolean ignoreSignatureValue = false;

  // end points
  private static String VERIFICATION_ENDPOINT = "moa-spss/services/SignatureVerification";
  private static String CREATION_ENDPOINT = "moa-spss/services/SignatureCreation";
  private static String SSL_VERIFICATION_ENDPOINT = null;
  private static String SSL_CREATION_ENDPOINT = null;

  private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd hh:mm:ss.SSS");

  // super cool stuff
  private static ArrayList testtimes = new ArrayList();
  private static ArrayList testnames = new ArrayList();
  private static ArrayList testvalues = new ArrayList();
  private static ArrayList testerrors = new ArrayList();
  private static int ELEMS_MAX = 40;
  private static String ELEMS_ICON = "O";
  private static String ELEMS_ICON_BAD = "X";

  private static final boolean debug = true;

  public static void main(String[] args)
  {
    long start = System.currentTimeMillis();
    long end = 0;
    System.out.println("Lade IXSIL ...");
    try
    {
      IXSILInit.init(new URI("init/properties/init.properties"));
    }
    catch (Exception e)
    {
      e.printStackTrace();
      System.exit(1);
    }

    System.out.println("Starte TestClient @ " + sdf.format(new Date(start)) + "...");

    if (args.length == 0)
    {
      directory = new File(defaultdirectory);
      server = defaultserver;
      System.out.println("DefaultPfad wird benutzt (" + directory.getPath() + ")");
      System.out.println("DefaultServer wird benutzt (" + server + ")");
    }
    else if (args.length == 1)
    {
      System.out.println("Pfad " + args[0] + " wird benutzt ");
      directory = new File(args[0]);
      server = defaultserver;
      System.out.println("DefaultServer wird benutzt (" + server + ")");
    }
    else if (args.length == 2)
    {
      System.out.println("Pfad " + args[0] + " wird benutzt ");
      directory = new File(args[0]);
      System.out.println("Server " + args[1] + " wird benutzt ");
      server = args[1];
    }

    VERIFICATION_ENDPOINT = server + VERIFICATION_ENDPOINT;
    CREATION_ENDPOINT = server + CREATION_ENDPOINT;
    TestClient tc = new TestClient();
    tc.run();
    end = System.currentTimeMillis();
    System.out.println("Ende TestClient @ " + sdf.format(new Date(end)) + "...");
    System.out.println("Durchlaufzeit: " + ((end - start) / 1000) + " sekunden");

  }

  public void run()
  {
    ssl_provider = new Provider();
    Security.addProvider(ssl_provider);
    Log = System.out;
    try
    {
      prepareFiles();
      runSuits();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }

    Log.println("----- Auswertung:");
    Log.println("----- Positiv Tests:" + (pos_ok + pos_nok + pos_exc));
    Log.println("-----    OK:" + (pos_ok));
    Log.println("-----    nicht OK:" + (pos_nok));
    Log.println("-----    Exception aufgetreten:" + (pos_exc));
    Log.println("----- Negativ Test:" + (neg_ok + neg_nok + neg_exc));
    Log.println("-----    OK:" + (neg_ok));
    Log.println("-----    nicht OK:" + (neg_nok));
    Log.println("-----    Exception aufgetreten:" + (neg_exc));
    Log.println("----- schnellste Anfrage:" + min_request + " ms");
    Log.println("----- langsamste Anfrage:" + max_request + " ms");
    if (count_all_request > 2)
      Log.println(
        "----- durchschnittliche Anfrage:"
          + ((all_request - max_request - min_request) / (count_all_request - 2))
          + " ms");
    else if (count_all_request == 0)
      Log.println("----- keine g�ltigen Messungen f�r Durchschnittsermittlung");
    else
      Log.println("----- durchschnittliche Anfrage:" + ((all_request) / (count_all_request)) + " ms");
  }

  private void prepareFiles() throws Exception
  {

    if (!directory.isDirectory())
    {
      throw new Exception("Das angegebene Verzeichnis ist kein Verzeichnis. Bitte Pfad �berpr�fen.");
    }

    directorystring = directory.getAbsolutePath();

    String[] list = directory.list();
    for (int counter = 0; counter < list.length; counter++)
    {

      if (list[counter].endsWith("Req.xml")
        || list[counter].endsWith("Res.xml")
        || list[counter].endsWith("Config.xml"))
      {
        String suitename = list[counter].substring(0, list[counter].indexOf("."));

        if (!suits.containsKey(suitename))
          suits.put(suitename, null);

        ArrayList al = (ArrayList) suits.get(suitename);
        if (al == null)
        {
          al = new ArrayList();
          suits.put(suitename, al);
        }
        al.add(list[counter]);
      }
    }

  }

  private void runSuits()
  {
    Iterator i = suits.keySet().iterator();
    while (i.hasNext())
    {
      String suitename = (String) i.next();
      Log.println("Suite:" + suitename);
      ArrayList al = (ArrayList) suits.get(suitename);
      testtimes.clear();
      testnames.clear();
      testvalues.clear();
      testerrors.clear();
      runTests(al, suitename);
      printGraph(testnames, testtimes, testvalues, testerrors, suitename);
    }
  }

  private void runTests(ArrayList testlist, String suitename)
  {
    TreeMap tests = new TreeMap();
    String config = null;

    int size = testlist.size();
    for (int counter = 0; counter < size; counter++)
    {
      String filename = (String) testlist.get(counter);
      StringTokenizer st = new StringTokenizer(filename, ".");
      String prefix = st.nextToken();
      String number = st.nextToken();

      if (number.equals("Config"))
      {
        config = filename;
        continue;
      }

      ArrayList testfiles = null;
      if (!tests.containsKey(number))
      {
        testfiles = new ArrayList();
        tests.put(number, testfiles);
      }
      else
      {
        testfiles = (ArrayList) tests.get(number);
      }
      testfiles.add(filename);
    }

    Iterator i = tests.keySet().iterator();
    while (i.hasNext())
    {
      String number = (String) i.next();
      runTest((ArrayList) tests.get(number), null, number, suitename);
    }
  }

  private void runTest(ArrayList files, String config, String number, String suitename)
  {
    String request = null;
    String response = null;
    String errorresponse = null;

    //Log.println("Test:"+number+" Mit Config:"+config);
    int size = files.size();
    for (int counter = 0; counter < size; counter++)
    {
      String filename = (String) files.get(counter);
      //Log.println("File:"+filename);
      if (filename.endsWith("ErrRes.xml"))
        errorresponse = filename;
      else if (filename.endsWith("Res.xml"))
        response = filename;
      else if (filename.endsWith("Req.xml"))
        request = filename;
      else
        Log.println("Nicht relevant:" + filename);
    }

    if (request != null)
    {
      for (int counter = 0; counter < 1; counter++)
      {
        if (response != null && errorresponse != null)
          Log.println(
            "Test " + number + " nicht g�ltig ! Sowohl Response als auch ErrorResponse vorhanden !");
        else if (response != null)
          runPosTest(request, response, config, number, suitename);
        else if (errorresponse != null)
          runNegTest(request, errorresponse, config, number, suitename);
      }
    }
    else
    {
      Log.println("Test " + number + " nicht g�ltig ! Kein Request vorhanden !");
    }
  }

  private void runPosTest(String request, String response, String config, String number, String suitename)
  {
    long start = System.currentTimeMillis();
    long end = 0;
    Log.println("\n----- Starte Test <" + number + "> (positiv) -----");
    Log.println("----- Request:  " + request);
    Log.println("----- Response: " + response);
    try
    {
      long start_req = 0;
      long end_req = 0;

      Log.println("----- Lade Request:" + directorystring + "/" + request);
      FileInputStream fis = new FileInputStream(directorystring + "/" + request);
      Document root_doc = DOMUtils.parseDocument(fis, false, Constants.ALL_SCHEMA_LOCATIONS, null);
      Element root = root_doc.getDocumentElement();
      if (debug)
        Log.println(DOMUtils.serializeNode(root));

      Log.println("----- Lade Response:" + directorystring + "/" + response);
      FileInputStream fis2 = new FileInputStream(directorystring + "/" + response);
      Document root_response_doc = DOMUtils.parseDocument(fis2, true, Constants.ALL_SCHEMA_LOCATIONS, null);
      Element root_response = root_response_doc.getDocumentElement();

      //can_root_response.normalize();

      if (request.endsWith("CX3.001.Req.xml"))
      {
        printKeyStoreInformation("./resources/client.keystore", "changeit");
        System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
        System.setProperty("javax.net.ssl.keyStore", "./resources/client.keystore");
        System.setProperty("javax.net.ssl.keyStorePassword", "changeit");
        System.setProperty("javax.net.ssl.trustStore", "./resources/client.keystore");
        System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

        ssl_connection = true;
      }
      else
        ssl_connection = false;

      if (request.endsWith("CX0.005.Req.xml")) // ECDSA
        ignoreSignatureValue = true;
      else
        ignoreSignatureValue = false;

      boolean compare = false;

      Transform can = new TransformImplExclusiveCanonicalXML();
      can.setInput(XPathUtils.selectNodeList(root_response, XPathUtils.ALL_NODES_XPATH), null);

      InputStream is = (InputStream) can.transform();
      Document can_root_response_doc =
        DOMUtils.parseDocument(is, true, Constants.ALL_SCHEMA_LOCATIONS, null);
      Element can_root_response = root_response_doc.getDocumentElement();

      if (checkNode(root, "VerifyCMSSignatureRequest"))
      {
        Log.println("----- Anfrage wird gesendet ...");
        SignatureVerificationCaller svc = new SignatureVerificationCaller();
        start_req = System.currentTimeMillis();
        Element root_serverresponse = svc.verifyCMSSignature(root, VERIFICATION_ENDPOINT);
        end_req = System.currentTimeMillis();

        Transform can2 = new TransformImplExclusiveCanonicalXML();
        can2.setInput(XPathUtils.selectNodeList(root_serverresponse, XPathUtils.ALL_NODES_XPATH), null);
        InputStream is2 = (InputStream) can2.transform();

        Document can_root_serverresponse_doc =
          DOMUtils.parseDocument(is2, true, Constants.ALL_SCHEMA_LOCATIONS, null);
        Element can_root_serverresponse = can_root_serverresponse_doc.getDocumentElement();
        if (debug)
          Log.println("----- Antwort sollte so aussehen ...\n" + DOMUtils.serializeNode(can_root_response));
        if (debug)
          Log.println("----- Antwort vom Server ...\n" + DOMUtils.serializeNode(can_root_serverresponse));

        Log.println("----- Antwort validieren ...\n");
        DOMUtils.validateElement(can_root_serverresponse, Constants.ALL_SCHEMA_LOCATIONS, null);
        Log.println("----- Antwort vergleichen ...\n");

        String error = findErrorNode(can_root_serverresponse);
        if (error != null)
        {
          compare = false;
          Log.println("----- ServerError: " + error);
          testerrors.add(error);
        }
        else
        {
          compare = compareElements(can_root_response, can_root_serverresponse);
          testerrors.add(null);
        }

      }
      else if (checkNode(root, "VerifyXMLSignatureRequest"))
      {
        Log.println("----- Anfrage wird gesendet ...");
        SignatureVerificationCaller svc = new SignatureVerificationCaller();
        start_req = System.currentTimeMillis();
        Element root_serverresponse = svc.verifyXMLSignature(root, VERIFICATION_ENDPOINT);
        end_req = System.currentTimeMillis();

        Transform can2 = new TransformImplExclusiveCanonicalXML();
        can2.setInput(XPathUtils.selectNodeList(root_serverresponse, XPathUtils.ALL_NODES_XPATH), null);
        InputStream is2 = (InputStream) can2.transform();

        Document can_root_serverresponse_doc =
          DOMUtils.parseDocument(is2, false, Constants.ALL_SCHEMA_LOCATIONS, null);
        Element can_root_serverresponse = can_root_serverresponse_doc.getDocumentElement();

        if (debug)
          Log.println("----- Antwort sollte so aussehen ...\n" + DOMUtils.serializeNode(can_root_response));
        if (debug)
          Log.println("----- Antwort vom Server ...\n" + DOMUtils.serializeNode(can_root_serverresponse));

        Log.println("----- Antwort validieren ...\n");
        DOMUtils.validateElement(can_root_serverresponse, Constants.ALL_SCHEMA_LOCATIONS, null);
        Log.println("----- Antwort vergleichen ...\n");
        String error = findErrorNode(can_root_serverresponse);
        if (error != null)
        {
          compare = false;
          Log.println("----- ServerError: " + error);
          testerrors.add(error);
        }
        else
        {
          compare = compareElements(can_root_response, can_root_serverresponse);
          testerrors.add(null);
        }
      }
      else if (checkNode(root, "CreateXMLSignatureRequest"))
      {
        Log.println("----- Anfrage wird gesendet ...");
        SignatureCreationCaller scc = new SignatureCreationCaller();
        start_req = System.currentTimeMillis();
        Element root_serverresponse = scc.createXMLSignature(root, CREATION_ENDPOINT);
        end_req = System.currentTimeMillis();

        Transform can2 = new TransformImplExclusiveCanonicalXML();
        can2.setInput(XPathUtils.selectNodeList(root_serverresponse, XPathUtils.ALL_NODES_XPATH), null);
        InputStream is2 = (InputStream) can2.transform();

        Document can_root_serverresponse_doc =
          DOMUtils.parseDocument(is2, false, Constants.ALL_SCHEMA_LOCATIONS, null);
        Element can_root_serverresponse = can_root_serverresponse_doc.getDocumentElement();

        if (debug)
          Log.println("----- Antwort sollte so aussehen ...\n" + DOMUtils.serializeNode(can_root_response));
        //Log.println("----- Antwort vom Server (von Can)...\n"+DOMUtils.serializeNode(root_serverresponse));
        if (debug)
          Log.println("----- Antwort vom Server ...\n" + DOMUtils.serializeNode(can_root_serverresponse));

        Log.println("----- Antwort validieren ...\n");
        DOMUtils.validateElement(can_root_serverresponse, Constants.ALL_SCHEMA_LOCATIONS, null);
        Log.println("----- Antwort vergleichen ...\n");

        String error = findErrorNode(can_root_serverresponse);
        if (error != null)
        {
          compare = false;
          Log.println("----- ServerError: " + error);
          testerrors.add(error);
        }
        else
        {
          compare = compareElements(can_root_response, can_root_serverresponse);
          testerrors.add(null);
        }

      }
      else
      {
        throw new Exception("Responsetyp nicht bekannt");
      }

      if (compare)
      {
        pos_ok++;
        testvalues.add(" OK");
        Log.println("----- Keine Fehler aufgetreten");
      }
      else
      {
        pos_nok++;
        testvalues.add("NOK");
        Log.println("----- Response war nicht ok !");
      }

      Date start_date = new Date(start_req);
      Date end_date = new Date(end_req);
      long diff = end_req - start_req;
      Log.println("----- Requeststart: " + sdf.format(start_date));
      Log.println("----- Requestende:  " + sdf.format(end_date));
      Log.println("----- Requestdauer: " + diff + " ms");
      if (diff > max_request)
        max_request = diff;
      if (diff < min_request)
        min_request = diff;
      all_request += diff;
      count_all_request++;

      // :)
      testtimes.add(new Long(diff));
      testnames.add(number);
    }
    catch (Exception e)
    {
      testtimes.add(new Long(-1L));
      testnames.add(number);
      testvalues.add("EXC");
      testerrors.add(e.getMessage());
      pos_exc++;
      Log.println("----- Exception:\n");
      e.printStackTrace(Log);
    }
    end = System.currentTimeMillis();
    Log.println("----- Durchlaufzeit: " + ((end - start) / 1000) + " sekunden");
    Log.println("----- Ende Test   <" + number + "> -----\n");
  }

  private void runNegTest(
    String request,
    String errorresponse,
    String config,
    String number,
    String suitename)
  {
    long start = System.currentTimeMillis();
    long end = 0;
    Log.println("\n----- Starte Test <" + number + "> (negativ) -----");
    Log.println("----- Config:        " + config);
    Log.println("----- Request:       " + request);
    Log.println("----- ErrorResponse: " + errorresponse);
    int error_no = 0;
    try
    {

      FileInputStream fis = null;
      Document root_doc = null;
      Element root = null;
      long start_req = 0;
      long end_req = 0;

      try
      {
        error_no = Integer.parseInt(readFile(directorystring + "/" + errorresponse));
      }
      catch (NumberFormatException nfe)
      {
        throw new Exception(
          "Fehler beim Lesen der Datei "
            + directorystring
            + "/"
            + errorresponse
            + ". Die Fehlernummer konnte nicht ermittelt werden");
      }

      try
      {
        Log.println("----- Lade Request: " + directorystring + "/" + request);
        fis = new FileInputStream(directorystring + "/" + request);
        root_doc = DOMUtils.parseDocument(fis, false, Constants.ALL_SCHEMA_LOCATIONS, null);
        root = root_doc.getDocumentElement();
      }
      catch (SAXParseException saxpe)
      {
        Log.println("Fehler beim Lesen der Requestdatei !");
        throw saxpe;
      }

      try
      {
        if (request.endsWith("CX4.051.Req.xml"))
        {
          printKeyStoreInformation("./resources/sven.keystore", "example");
          System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
          System.setProperty("javax.net.ssl.keyStore", "./resources/sven.keystore");
          System.setProperty("javax.net.ssl.keyStorePassword", "example");
          System.setProperty("javax.net.ssl.trustStore", "./resources/sven.keystore");
          System.setProperty("javax.net.ssl.trustStorePassword", "example");

          ssl_connection = true;
        }
        else if (request.endsWith("CX3.052.Req.xml"))
        {
          printKeyStoreInformation("./resources/client.keystore", "changeit");
          System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
          System.setProperty("javax.net.ssl.keyStore", "./resources/client.keystore");
          System.setProperty("javax.net.ssl.keyStorePassword", "changeit");
          System.setProperty("javax.net.ssl.trustStore", "./resources/client.keystore");
          System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

          ssl_connection = true;
        }
        else
          ssl_connection = false;

        Element root_serverresponse = null;

        if (checkNode(root, "VerifyCMSSignatureRequest"))
        {
          SignatureVerificationCaller svc = new SignatureVerificationCaller();
          start_req = System.currentTimeMillis();
          root_serverresponse = svc.verifyCMSSignature(root, VERIFICATION_ENDPOINT);
          end_req = System.currentTimeMillis();
          Log.println(DOMUtils.serializeNode(root_serverresponse));
        }
        else if (checkNode(root, "VerifyXMLSignatureRequest"))
        {
          SignatureVerificationCaller svc = new SignatureVerificationCaller();
          start_req = System.currentTimeMillis();
          root_serverresponse = svc.verifyXMLSignature(root, VERIFICATION_ENDPOINT);
          end_req = System.currentTimeMillis();
          Log.println(DOMUtils.serializeNode(root_serverresponse));
        }
        else if (checkNode(root, "CreateXMLSignatureRequest"))
        {
          SignatureCreationCaller svc = new SignatureCreationCaller();
          start_req = System.currentTimeMillis();
          root_serverresponse = svc.createXMLSignature(root, CREATION_ENDPOINT);
          end_req = System.currentTimeMillis();
          Log.println(DOMUtils.serializeNode(root_serverresponse));
        }

        Transform can2 = new TransformImplExclusiveCanonicalXML();
        can2.setInput(XPathUtils.selectNodeList(root_serverresponse, XPathUtils.ALL_NODES_XPATH), null);
        InputStream is2 = (InputStream) can2.transform();

        Document can_root_serverresponse_doc =
          DOMUtils.parseDocument(is2, false, Constants.ALL_SCHEMA_LOCATIONS, null);
        Element can_root_serverresponse = can_root_serverresponse_doc.getDocumentElement();

        int errno = getErrorNumber(can_root_serverresponse);

        if (errno == -1)
        {
          Log.println("Kein Fehler aufgetreten oder Fehlernummer konnte nicht ermittelt werden.");
          neg_nok++;
          testvalues.add("NOK");
          testerrors.add("Kein Fehler aufgetreten oder Fehlernummer konnte nicht ermittelt werden.");
        }
        else
        {
          Log.println("----- Fehlercode vom Server:" + errno);
          Log.println("----- Fehlercode vom Test:" + error_no);
          if (errno == error_no)
          {
            Log.println("Test erfolgreich !");
            testvalues.add(" OK");
            testerrors.add(null);
            neg_ok++;
          }
          else
          {
            Log.println("Test nicht erfolgreich !");
            neg_nok++;
            testvalues.add("NOK");
            testerrors.add("Fehlercodes nicht gleich: Server " + errno + " Client " + error_no);
          }
        }

      }
      catch (org.apache.axis.AxisFault af)
      {
        end_req = System.currentTimeMillis();
        /*
         * Sample Fault:
        		AxisFault
        		 faultCode: {http://xml.apache.org/axis/}Server.userException
        		 faultString: at.gv.egovernment.moa.spss.server.MOAApplicationException: Fehler beim Validieren der Anfrage
        		 faultActor: null
        		 faultDetail: 
        			ErrorResponse: 
        		     <ns2:ErrorCode>1100</ns2:ErrorCode>
        		     <ns2:Info>Fehler beim Validieren der Anfrage</ns2:Info>
        */
        Element base = af.getFaultDetails()[0];
        System.out.println(DOMUtils.serializeNode(base));

        int error_no_server = getErrorNumber(base);
        if (error_no_server == -1)
        {
          Log.println("Kein Fehler aufgetreten oder Fehlernummer konnte nicht ermittelt werden.");
          neg_nok++;
          testvalues.add("NOK");
          testerrors.add("Kein Fehler aufgetreten oder Fehlernummer konnte nicht ermittelt werden.");
        }
        else
        {
          Log.println("----- Fehlercode vom Server:" + error_no_server);
          Log.println("----- Fehlercode vom Test:" + error_no);
          if (error_no_server == error_no)
          {
            Log.println("Test erfolgreich !");
            testvalues.add(" OK");
            testerrors.add(null);
            neg_ok++;
          }
          else
          {
            Log.println("Test nicht erfolgreich !");
            neg_nok++;
            testvalues.add("NOK");
            testerrors.add("Fehlercodes nicht gleich: Server " + error_no_server + " Client " + error_no);
          }
        }
      }
      long diff = end_req - start_req;
      Log.println("----- Requestdauer: " + diff + " ms");
      if (diff > max_request)
        max_request = diff;
      if (diff < min_request)
        min_request = diff;
      all_request += diff;
      count_all_request++;
      testtimes.add(new Long(diff));
      testnames.add(number);

    }
    catch (UTFDataFormatException e)
    {
      method2(request, error_no, number);
    }
    catch (SAXParseException e)
    {
      method2(request, error_no, number);
    }
    catch (Exception e)
    {
      neg_exc++;
      testtimes.add(new Long(-1L));
      testnames.add(number);
      testvalues.add("EXC");
      testerrors.add(e.getMessage());
      Log.println("----- Exception:\n");
      e.printStackTrace(Log);
    }
    end = System.currentTimeMillis();
    Log.println("----- Durchlaufzeit: " + ((end - start) / 1000) + " sekunden");
    Log.println("----- Ende Test   <" + number + "> -----\n");
  }

  private void method2(String request, int error_no, String number)
  {
    try
    {
      Log.println("----- Methode 2\n");
      String data = readFile(directorystring + "/" + request);
      //Log.println("Data:\n"+data);
      int index = data.indexOf(">");
      if (index != -1)
      {
        String xml_head = data.substring(0, index);
        data = data.substring(index + 1);
        //Log.println("Data2:\n"+data);

        data =
          xml_head
            + "<soap:Envelope "
            + "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\" "
            + "soap:encodingStyle=\"http://www.w3.org/2001/12/soap-encoding\"> "
            + "<soap:Body> "
            + data
            + "</soap:Body>"
            + "</soap:Envelope>";
        //Log.println("Data3:\n"+data);

        String ep = null;
        if (data.indexOf("<Create") != -1)
          ep = server + "moa-spss/services/SignatureCreation";
        else
          ep = server + "moa-spss/services/SignatureVerification";

        HttpURLConnection urlc = (HttpURLConnection) (new URL(ep)).openConnection();
        urlc.setDoInput(true);
        urlc.setDoOutput(true);
        urlc.setRequestProperty("SOAPAction", "");
        BufferedWriter ow = new BufferedWriter(new OutputStreamWriter(urlc.getOutputStream()));
        ow.write(data);
        ow.flush();
        int code = 0;
        try
        {
          code = urlc.getResponseCode();
        }
        catch (IOException ioe)
        {
          code = 500;
        }

        if (code == 500)
        {
          BufferedReader err_br = new BufferedReader(new InputStreamReader(urlc.getErrorStream()));
          StringBuffer err_response = new StringBuffer();
          String err_line = null;
          while ((err_line = err_br.readLine()) != null)
          {
            err_response.append(err_line);
          }

          Log.println("ErrResponse:\n" + err_response);

          if (error_no == 0)
          {
            Log.println("Test erfolgreich !");
            testvalues.add(" OK");
            testtimes.add(new Long(-1));
            testnames.add(number);
            testerrors.add(null);
            neg_ok++;
          }
          else
          {
            Log.println("Test nicht erfolgreich !");
            neg_nok++;
            testvalues.add("NOK");
            testtimes.add(new Long(-1));
            testnames.add(number);
            testerrors.add("Test nicht erfolgreich !");
          }

        }
        else
        {
          BufferedReader br = new BufferedReader(new InputStreamReader(urlc.getInputStream()));
          StringBuffer response = new StringBuffer();
          String line = null;
          while ((line = br.readLine()) != null)
          {
            response.append(line);
          }
          String resp = response.toString();
          Log.println("Response:\n" + response);

          int pos = resp.indexOf("ErrorCode>");
          if (pos == -1)
          {
            Log.println("Test nicht erfolgreich !");
            neg_nok++;
            testvalues.add("NOK");
            testtimes.add(new Long(-1));
            testnames.add(number);
            testerrors.add("Test nicht erfolgreich !");
          }
          else
          {
            resp = resp.substring(pos + "ErrorCode>".length());
            pos = resp.indexOf("<");
            if (pos == -1)
            {
              Log.println("Test nicht erfolgreich !");
              neg_nok++;
              testvalues.add("NOK");
              testtimes.add(new Long(-1));
              testnames.add(number);
              testerrors.add("Test nicht erfolgreich !");
            }
            else
            {
              resp = resp.substring(0, pos);

              int err_resp = -1;
              try
              {
                err_resp = Integer.parseInt(resp);
              }
              catch (NumberFormatException nfe)
              {
              }
              //Log.println("ResponseCode:\n"+resp);

              Log.println("----- Fehlercode vom Server:" + err_resp);
              Log.println("----- Fehlercode vom Test:" + error_no);

              if (err_resp == error_no)
              {
                Log.println("Test erfolgreich !");
                neg_ok++;
                testvalues.add(" OK");
                testtimes.add(new Long(-1));
                testnames.add(number);
                testerrors.add(null);
              }
              else
              {
                Log.println("Test nicht erfolgreich !");
                neg_ok++;
                testvalues.add("NOK");
                testtimes.add(new Long(-1));
                testnames.add(number);
                testerrors.add("Fehlercodes nicht gleich: Server " + err_resp + " Client " + error_no);
              }
            }
          }
        }
      }
    }
    catch (Exception ee)
    {
      ee.printStackTrace();
    }
  }

  private boolean compareElements(Element root1, Element root2)
  {
    //Log.println("----- Compare Elements:"+root1.getNodeName()+" "+root2.getNodeName());
    filterTree(root1);
    filterTree(root2);
    return compareNodes(root1, root2, 0, "root/", false);
  }

  private boolean compareNodes(Node n1, Node n2, int level, String path, boolean attribute)
  {
    /*try {
    	Log.println(DOMUtils.serializeNode(n1));
    }
    catch(Exception e)
    {
    	e.printStackTrace();
    }*/
    boolean equal = false;
    //Log.println("----- Compare Node "+level+":"+n1+" "+n2);
    //Log.println("----- Compare Node "+level+":"+n1.getNodeName()+" "+n2.getNodeName());
    //Log.println("----- Checking:"+path+getPathString(n1));
    NodeList nl1 = n1.getChildNodes();
    NodeList nl2 = n2.getChildNodes();

    int size1 = nl1.getLength();
    int size2 = nl2.getLength();

    if (debug)
      display_one(n1);
    if (debug)
      display_one(n2);

    if (debug)
      if (n1.getNodeName().equals("Base64Content") && n2.getNodeName().equals("Base64Content"))
      {
        try
        {
          Log.println(
            "CONT:"
              + new String(Base64Utils.decode(strip(n1.getChildNodes().item(0).getNodeValue()), false)));
          Log.println(
            "CONT:"
              + new String(Base64Utils.decode(strip(n2.getChildNodes().item(0).getNodeValue()), false)));
        }
        catch (Exception e)
        {
          e.printStackTrace();
        }
      }

    if (size1 != size2)
    {
      Log.println(
        "----- Anzahl der Kinder nicht gleich:"
          + path
          + getPathString(n1)
          + "("
          + size1
          + ") / "
          + getPathString(n2)
          + "("
          + size2
          + ")");
      return false;
    }

    equal = compareNodeExact(n1, n2, level, path + getPathString(n1) + "/");
    if (!equal)
    {
      Log.println("----- Knoten sind nicht identisch:" + path + getPathString(n1));
      return false;
    }

    if (n1.hasAttributes() || n2.hasAttributes())
    {
      equal = compareNodeAttriubtes(n1, n2, level + 1, path + getPathString(n1) + "/(a)");
      if (!equal)
      {
        Log.println("----- Attribute stimmen nicht �berein:" + path + getPathString(n1));
        return false;
      }
    }
    if (size1 == 0)
    {
      return true;
    }

    for (int counter = 0; counter < size1; counter++)
    {
      boolean found = false;
      Node comp_n1 = nl1.item(counter);

      //if(comp_n1==null) return false;

      Node comp_n2 = null;
      size2 = nl2.getLength();
      for (int counter2 = 0; counter2 < size2; counter2++)
      {
        comp_n2 = nl2.item(counter2);

        /*equal = compareNodeExact(comp_n1,comp_n2,level+1);
        if(equal) return false;*/
        //Log.println("COMP_N1:"+comp_n1);
        //Log.println("COMP_N2:"+comp_n2);
        equal = compareNodes(comp_n1, comp_n2, level + 1, path + getPathString(comp_n1) + "/", false);
        if (equal)
        {
          n2.removeChild(comp_n2);
          counter2 = size2;
          nl2 = n2.getChildNodes();
          size2 = nl2.getLength();
        }

      }

      if (!equal)
      {
        Log.println("----- Keine �bereinstimmung gefunden:" + path + getPathString(comp_n1));
        return false;
      }
    }
    return true;
  }

  private boolean compareNodeExact(Node n1, Node n2, int level, String path)
  {
    if (n1.getNodeType() == Node.TEXT_NODE)
    {
      Text textnode = (Text) n1;
      /*Log.println("----- *****"+textnode.getNodeName());
      Log.println("----- *****"+textnode.getParentNode().getNodeName());
      Log.println("----- *****"+textnode.getNodeValue());*/
    }

    //Log.println("----- Checking:"+path);
    String n1_name = n1.getNodeName();
    String n2_name = n2.getNodeName();
    /*Log.println("----- !!!!!"+n1.getNodeName());
    Log.println("----- !!!!!"+n1.getNodeValue());
    Log.println("----- !!!!!"+n1.getLocalName());
    Log.println("----- !!!!!"+n1.getPrefix());
    Log.println("----- !!!!!"+n1.getNextSibling());
    Log.println("----- !!!!!"+n1.getPreviousSibling());*/

    //Log.println("----- Compare Node "+level+":"+n1_name+" "+n2_name);
    if (!((n1_name == null && n2_name == null)
      || (n1_name != null && n2_name != null && n1_name.equals(n2_name))))
    {
      Log.println("----- Name stimmt nicht �berein:" + path);
      return false;
    }

    //Log.println("----- Compare Node "+level+":"+n1.getNodeType()+" "+n2.getNodeType());
    if (n1.getNodeType() != n2.getNodeType())
    {
      Log.println("----- Knotentyp stimmt nicht �berein:" + path);
      return false;
    }

    String n1_ns = n1.getPrefix();
    String n2_ns = n2.getPrefix();
    //Log.println("----- Compare Node "+level+":"+n1_ns+" "+n2_ns);
    if (!((n1_ns == null && n2_ns == null) || (n1_ns != null && n2_ns != null && n1_ns.equals(n2_ns))))
    {
      Log.println("----- NameSpace stimmt nicht �berein:" + path);
      return false;
    }

    String n1_value = n1.getNodeValue();
    String n2_value = n2.getNodeValue();

    boolean special = false;
    special = specialValues(n1_value, n2_value, path);
    if (special)
      return true;

    //Log.println("----- Compare Node "+level+":"+n1_value+" "+n2_value);
    if (!((n1_value == null && n2_value == null)
      || (n1_value != null && n2_value != null && n1_value.equals(n2_value))))
    {
      Log.println("----- Wert stimmt nicht �berein:" + path);
      Log.println("----- Value1:\n" + n1_value);
      Log.println("----- Value2:\n" + n2_value);
      return false;
    }

    return true;
  }

  private boolean compareNodeAttriubtesWithoutSize(Node n1, Node n2, int level, String path)
  {
    return true;
  }

  private boolean compareNodeAttriubtes(Node n1, Node n2, int level, String path)
  {
    //Log.println("----- Compare NodeAttributes "+level+":"+n1.getNodeName()+" "+n2.getNodeName());
    Element n1elem = (Element) n1;
    Element n2elem = (Element) n2;

    NamedNodeMap nnm1 = n1.getAttributes();
    NamedNodeMap nnm2 = n2.getAttributes();

    int size1 = 0;
    int size2 = 0;

    boolean specialattrs = specialAttributesSize(path);

    if (!specialattrs)
    {

      if (nnm1 == null && nnm2 == null)
        return true;
      if (nnm1 == null || nnm2 == null)
      {
        Log.println("----- Anzahl der Attribute nicht gleich:" + path + ":" + getPathString(n1));
        return false;
      }
      size1 = nnm1.getLength();
      size2 = nnm2.getLength();

      if (size1 != size2)
      {
        Log.println("----- Anzahl der Attribute nicht gleich:" + path + ":" + getPathString(n1));
        return false;
      }

    }
    else
    {
      return compareNodeAttriubtesWithoutSize(n1, n2, level, path);
    }

    for (int counter = 0; counter < size1; counter++)
    {
      Node attribute_node1 = nnm1.item(counter);
      Node attribute_node2 = nnm2.item(counter);

      String attr1_name = attribute_node1.getNodeName();
      String attr2_name = attribute_node2.getNodeName();

      //Log.println("----- Checking:"+path+">"+attr1_name);

      String value1 = n1elem.getAttribute(attr1_name);
      String value2 = n2elem.getAttribute(attr2_name);

      boolean special = false;

      special = specialAttributes(value1, value2);
      if (special)
      {
        return special;
      }

      if (!value1.equals(value2))
      {
        Log.println("----- Keine �bereinstimmung gefunden:" + path + getPathString(n1));
        return false;
      }
      //Log.println("----- Compare NodeAttributes > "+level+":"+attribute_node1+" "+attribute_node2);

      /*boolean equal = compareNodes(attribute_node1,attribute_node2,level+1,path+attribute_node1.getNodeName()+"/",true);
      if(!equal)
      {
      	//Log.println("----- no match for:"+attribute_node1.getNodeName());
      	return false;
      }*/

    }

    return true;
  }

  private boolean checkNode(Node base, String name)
  {
    if (base.getNodeName().equals(name))
    {
      return true;
    }

    NodeList children = base.getChildNodes();
    int size = children.getLength();
    for (int counter = 0; counter < size; counter++)
    {
      boolean found = checkNode(children.item(counter), name);
      if (found)
        return true;
    }
    return false;
  }

  private void display_one(Node base)
  {
    int att_size = 0;
    if (base.getAttributes() != null)
    {
      att_size = base.getAttributes().getLength();
    }
    if (base.getNodeName().equals("#text"))
      Log.println(
        base.getNodeName()
          + base.getChildNodes().getLength()
          + ":"
          + att_size
          + " ("
          + base.getNodeValue()
          + ")");
    else
      Log.println(base.getNodeName() + base.getChildNodes().getLength() + ":" + att_size);
  }

  private void display(Node base)
  {
    display(base, 1);
  }

  private void display(Node base, int level)
  {
    String spacer = "";
    for (int counter = 0; counter < level; counter++)
    {
      spacer += "  ";
    }

    int att_size = 0;
    if (base.getAttributes() != null)
    {
      att_size = base.getAttributes().getLength();
    }
    if (base.getNodeName().equals("#text"))
      Log.println(
        spacer
          + base.getNodeName()
          + base.getChildNodes().getLength()
          + ":"
          + att_size
          + " ("
          + base.getNodeValue()
          + ")");
    else
      Log.println(spacer + base.getNodeName() + base.getChildNodes().getLength() + ":" + att_size);

    NodeList children = base.getChildNodes();
    int size = children.getLength();
    for (int counter = 0; counter < size; counter++)
    {
      display(children.item(counter), level + 1);
    }
  }

  private void filterTree(Node base)
  {
    ArrayList removeList = new ArrayList();

    NodeList children = base.getChildNodes();
    int size = children.getLength();
    for (int counter = 0; counter < size; counter++)
    {
      Node child1 = children.item(counter);
      if (child1.getNodeType() == Node.TEXT_NODE && child1.getNodeValue().trim().equals(""))
      {
        removeList.add(child1);
      }
    }

    size = removeList.size();
    for (int counter = 0; counter < size; counter++)
    {
      base.removeChild((Node) removeList.get(counter));
    }

    children = base.getChildNodes();
    size = children.getLength();
    for (int counter = 0; counter < size; counter++)
    {
      filterTree(children.item(counter));
    }

  }

  private String readFile(String filename) throws Exception
  {
    RandomAccessFile raf = new RandomAccessFile(filename, "r");
    if (raf.length() > Integer.MAX_VALUE)
      throw new IOException("file too big to fit in byte array.");

    byte[] result = new byte[(int) raf.length()];

    raf.read(result);

    return new String(result);

  }

  private String getPathString(Node n)
  {
    if (n.getNodeType() == Node.TEXT_NODE)
    {
      return n.getParentNode().getNodeName() + "(text)";
    }
    else
    {
      return n.getNodeName();
    }

  }

  private String replaceString(String input, String oldPart, String newPart) throws Exception
  {
    String erg = null;

    //First Part
    erg = input.substring(0, input.indexOf(oldPart));
    //Insert new Part
    erg += newPart;

    //insert REST
    erg += input.substring(input.indexOf(oldPart) + oldPart.length(), input.length());

    return erg;
  }

  private String replaceStringWithCheck(String input, String oldPart, String newPart) throws Exception
  {
    String erg = null;

    if (input.indexOf(oldPart) == -1)
      return input;

    return replaceString(input, oldPart, newPart);
  }

  private void printKeyStoreInformation(String keystore, String pw) throws Exception
  {
    KeyStore ks = KeyStore.getInstance("JKS", "SUN");
    ks.load(new FileInputStream(keystore), pw.toCharArray());
    Enumeration enum = ks.aliases();
    while (enum.hasMoreElements())
    {
      String certname = (String) enum.nextElement();
      Log.println("Cert:" + certname);
      sun.security.x509.X509CertImpl c = (sun.security.x509.X509CertImpl) ks.getCertificate(certname);
      Principal p = c.getIssuerDN();
      Log.println("  Issuer:" + p.getName());
      p = c.getSubjectDN();
      Log.println("  Subject:" + p.getName());
      Log.println("  Serial:" + c.getSerialNumber());
    }
  }

  private void printGraph(
    ArrayList names,
    ArrayList times,
    ArrayList values,
    ArrayList errors,
    String suitename)
  {
    long max = getMax(times, names);
    //Log.println("MAX:"+max);
    if (max == -1)
    {
      Log.println("Kein Graph m�glich !");
      return;
    }
    Log.println("names:" + names.size());
    Log.println("times:" + times.size());
    Log.println("values:" + values.size());
    Log.println("errors:" + errors.size());
    Log.println("#   | Status | Suite:" + suitename);
    int size = times.size();
    for (int counter = 0; counter < size; counter++)
    {
      String output = "";
      long value = ((Long) times.get(counter)).longValue();
      if (value != -1)
      {
        output = names.get(counter) + " |   " + values.get(counter) + "  | " + getElement(value, max);

      }
      else
      {
        output = names.get(counter) + " |   " + values.get(counter) + "  | " + ELEMS_ICON_BAD;
      }

      if (errors.get(counter) != null)
      {
        output += buildSpacer(70 - output.length()) + errors.get(counter);
      }
      Log.println(output);
    }

  }

  private String getElement(long value, long max)
  {
    boolean plus = false;
    int elems = (int) (((((double) value) / ((double) max)) * (ELEMS_MAX / 2)));
    if (elems > ELEMS_MAX)
    {
      elems = ELEMS_MAX;
      plus = true;
    }
    StringBuffer sb = new StringBuffer();
    for (int counter = 0; counter < elems; counter++)
    {
      sb.append(ELEMS_ICON);
    }
    for (int counter = 0; counter < (ELEMS_MAX - elems); counter++)
    {
      sb.append(" ");
    }
    if (plus)
      sb.append("> ");
    else
      sb.append("  ");

    sb.append(value + " ms");

    return sb.toString();
  }

  private long getMax(ArrayList times, ArrayList names)
  {
    int count = 0;
    double sum = 0;
    int size = times.size();
    for (int counter = 0; counter < size; counter++)
    {
      //Log.println(times.get(counter)+":"+names.get(counter));
      long value = ((Long) times.get(counter)).longValue();
      if (value != -1)
      {
        sum += value;
        count++;
      }
    }

    if (count == 0)
      return -1;

    return ((long) (sum / count));
  }

  private boolean specialAttributes(String value1, String value2)
  {
    //if(value1.startsWith("reference-") && value2.startsWith("reference-")) return true;
    if (value1.startsWith("signature-") && value2.startsWith("signature-"))
      return true;

    return false;
  }

  private boolean specialAttributesSize(String path)
  {
    if (path.endsWith("/xsl:template/(a)"))
      return true;
    return false;
  }

  private boolean specialValues(String value1, String value2, String path)
  {

    //Log.println(path);
    if (ignoreSignatureValue)
    {
      if (path.endsWith("/dsig:SignatureValue(text)/"))
      {
        return true;
      }
    }
    else
    {
      if (path.endsWith("/dsig:SignatureValue(text)/"))
      {
        String stripped_1 = strip(value1);
        String stripped_2 = strip(value2);
        return stripped_1.equals(stripped_2);
      }
    }

    if (path.endsWith("/dsig:X509Certificate(text)/"))
    {
      String stripped_1 = strip(value1);
      String stripped_2 = strip(value2);
      return stripped_1.equals(stripped_2);
    }

    if (path.endsWith("/dsig:Object(text)/"))
    {
      String stripped_1 = strip(value1);
      String stripped_2 = strip(value2);
      return stripped_1.equals(stripped_2);
    }

    if (path.endsWith("/Base64Content(text)/"))
    {
      String stripped_1 = strip(value1);
      String stripped_2 = strip(value2);
      return stripped_1.equals(stripped_2);
    }

    if (path.endsWith("/FailedReference(text)/"))
    {
      try
      {
        int stripped_1 = Integer.parseInt(value1);
        int stripped_2 = Integer.parseInt(value2);
        return stripped_1 == stripped_2;
      }
      catch (Exception e)
      {
        return false;
      }
    }

    return false;
  }

  private String strip(String input)
  {
    String output = replaceStringAll(input, " ", "");
    output = replaceStringAll(output, "\n", "");
    output = replaceStringAll(output, "\r", "");
    return output;
  }

  public static String replaceStringAll(String input, String oldPart, String newPart)
  {

    String erg = null;

    int pos = input.indexOf(oldPart);
    if (pos == -1)
      return input;

    while (true)
    {

      //First Part
      pos = input.indexOf(oldPart);
      if (pos == -1)
        break;
      erg = input.substring(0, pos);

      //Insert new Part
      erg += newPart;

      //insert REST
      erg += input.substring(input.indexOf(oldPart) + oldPart.length(), input.length());

      input = erg;
    }
    return erg;
  }

  private int getErrorNumber(Element root)
  {
    ArrayList result = new ArrayList();
    findNode(root, "ErrorCode", result);
    if (result.size() != 1)
      return -1;
    Node n = (Node) result.get(0);
    String text = (String) n.getChildNodes().item(0).getNodeValue();
    try
    {
      int error = Integer.parseInt(text);
      return error;
    }
    catch (NumberFormatException nfe)
    {
      Log.println(text + " ist keine g�ltige Fehlernummer");
      return -1;
    }
  }

  private void findNode(Node base, String name, ArrayList foundNodes)
  {
    findNode(base, name, foundNodes, -1);
  }

  private void findNode(Node base, String name, ArrayList foundNodes, int max_level)
  {
    findNode(base, name, foundNodes, max_level, 0);
  }

  private void findNode(Node base, String name, ArrayList foundNodes, int max_level, int level)
  {
    if (max_level != -1 && max_level <= level)
      return;
    //System.out.println("FINDNODE "+name);
    //System.out.println("CHECKING "+base.getNodeName());
    if (base.getNodeName().endsWith(name))
    {
      //System.out.println("ADD BASE !"+name);
      foundNodes.add(base);
    }

    NodeList children = base.getChildNodes();
    int size = children.getLength();
    for (int counter = 0; counter < size; counter++)
    {
      findNode(children.item(counter), name, foundNodes, max_level, level + 1);
    }
  }

  private String findErrorNode(Node n)
  {
    ArrayList al = new ArrayList();
    findNode(n, "ErrorResponse", al);
    if (al.size() != 0)
    {
      al.clear();
      findNode(n, "ErrorCode", al);
      String code = ((Node) al.get(0)).getChildNodes().item(0).getNodeValue();
      al.clear();
      findNode(n, "Info", al);
      String msg = ((Node) al.get(0)).getChildNodes().item(0).getNodeValue();

      return "ErrorCode: " + code + " / ErrorMsg: " + msg;
    }
    else
      return null;
  }

  private String buildSpacer(int length)
  {
    if (length <= 0)
      return "";
    else
    {
      StringBuffer output = new StringBuffer();
      for (int counter = 0; counter < length; counter++)
      {
        output.append(" ");
      }
      return output.toString();
    }
  }

}